From f2efb22db9673700cd38eae6bb4629b76a2194c5 Mon Sep 17 00:00:00 2001 From: teddyrendahl Date: Mon, 11 Jun 2018 11:42:39 -0500 Subject: [PATCH 1/8] MAINT: Qt version agnostic import of Designer --- lib/matplotlib/backends/qt_compat.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/matplotlib/backends/qt_compat.py b/lib/matplotlib/backends/qt_compat.py index e02e881a3969..fde6b775c021 100644 --- a/lib/matplotlib/backends/qt_compat.py +++ b/lib/matplotlib/backends/qt_compat.py @@ -133,7 +133,7 @@ _log.info(cond + res) if QT_API == QT_API_PYQT5: try: - from PyQt5 import QtCore, QtGui, QtWidgets + from PyQt5 import QtCore, QtGui, QtWidgets, QtDesigner _getSaveFileName = QtWidgets.QFileDialog.getSaveFileName except ImportError: if _fallback_to_qt4: @@ -146,7 +146,7 @@ # needs to be if so we can re-test the value of QT_API which may # have been changed in the above if block if QT_API in [QT_API_PYQT, QT_API_PYQTv2]: # PyQt4 API - from PyQt4 import QtCore, QtGui + from PyQt4 import QtCore, QtGui, QtDesigner try: if sip.getapi("QString") > 1: From f0ddde15ba6c95dfcdd7c0fb6d5afc81779a0919 Mon Sep 17 00:00:00 2001 From: teddyrendahl Date: Mon, 11 Jun 2018 15:15:04 -0500 Subject: [PATCH 2/8] ENH: QtDesignerPlugin for FigureCanvasQT --- .../backends/qt_editor/figure_plugin.py | 85 +++++++++++++++++++ 1 file changed, 85 insertions(+) create mode 100644 lib/matplotlib/backends/qt_editor/figure_plugin.py diff --git a/lib/matplotlib/backends/qt_editor/figure_plugin.py b/lib/matplotlib/backends/qt_editor/figure_plugin.py new file mode 100644 index 000000000000..d517b91f7b1e --- /dev/null +++ b/lib/matplotlib/backends/qt_editor/figure_plugin.py @@ -0,0 +1,85 @@ +""" +Plugin for drag and drop matplotlib.Figure in QtDesigner +""" +from matplotlib.backends.backend_qt5 import FigureCanvasQT +from matplotlib.figure import Figure + +# Pyside and Pyside2 do not support the QtDesigner functionality that is +# contained in both PyQt4 and PyQt5. This feature will not be supported with +# those backends until this feature set is available in those libraries +from matplotlib.backends.qt_compat import QtDesigner, QtGui + + +class FigureDesignerPlugin(QtDesigner.QPyDesignerCustomWidgetPlugin): + """ + QtDesigner Plugin for a matplotlib FigureCanvas + + Notes + ----- + In order to load this plugin, set the ``PYQTDESIGNERPATH`` environment + variable to the directory that contains this file. + """ + def __init__(self): + QtDesigner.QPyDesignerCustomWidgetPlugin.__init__(self) + self.initialized = False + + def initialize(self, core): + """Mark the QtDesigner plugin as initialized""" + if self.initialized: + return + self.initialized = True + + def isInitialized(self): + """Whether the widget has been initialized""" + return self.initialized + + def createWidget(self, parent): + """Create a FigureCanvasQT instance""" + # Create the Canvas with a new Figure + fig = FigureCanvasQT(Figure()) + # Set the parent of the newly created widget + fig.setParent(parent) + return fig + + def name(self): + """Name of plugin displayed in QtDesigner""" + return "FigureCanvasQT" + + def group(self): + """Name of plugin group header in QtDesigner""" + return "Matplotlib Widgets" + + def isContainer(self): + """Whether to allow widgets to be dragged in inside QtCanvas""" + # Someday we may want to set this to True if we can drop in curve + # objects, but this first draft will not include that functionality + return False + + def toolTip(self): + """Short description of Widget""" + return "A matplotlib FigureCanvas" + + def whatsThis(self): + """Long explanation of Widget""" + return self.__doc__ + + def icon(self): + """Icon displayed alongside Widget selection""" + return QtGui.QIcon() + + def domXml(self): + """XML Description of the widget's properties""" + return ( + "\n" + " \n" + " {1}\n" + " \n" + " \n" + " {2}\n" + " \n" + "\n" + ).format(self.name(), self.toolTip(), self.whatsThis()) + + def includeFile(self): + """Include a link to this file for reference""" + return __file__ From 807be2c7d0ff579102ac74b7809e052635f0e592 Mon Sep 17 00:00:00 2001 From: teddyrendahl Date: Sun, 15 Jul 2018 12:52:27 -0700 Subject: [PATCH 3/8] MAINT: Move QtDesigner plugin to mpl-data directory --- lib/matplotlib/{backends/qt_editor => mpl-data}/figure_plugin.py | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename lib/matplotlib/{backends/qt_editor => mpl-data}/figure_plugin.py (100%) diff --git a/lib/matplotlib/backends/qt_editor/figure_plugin.py b/lib/matplotlib/mpl-data/figure_plugin.py similarity index 100% rename from lib/matplotlib/backends/qt_editor/figure_plugin.py rename to lib/matplotlib/mpl-data/figure_plugin.py From 18ccbbcc09978410d18138fcc83d9d488d9cbb17 Mon Sep 17 00:00:00 2001 From: teddyrendahl Date: Sun, 15 Jul 2018 16:40:56 -0700 Subject: [PATCH 4/8] MAINT: Use matplotlib icon --- lib/matplotlib/mpl-data/figure_plugin.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/lib/matplotlib/mpl-data/figure_plugin.py b/lib/matplotlib/mpl-data/figure_plugin.py index d517b91f7b1e..f761975325e1 100644 --- a/lib/matplotlib/mpl-data/figure_plugin.py +++ b/lib/matplotlib/mpl-data/figure_plugin.py @@ -1,6 +1,8 @@ """ Plugin for drag and drop matplotlib.Figure in QtDesigner """ +import os.path + from matplotlib.backends.backend_qt5 import FigureCanvasQT from matplotlib.figure import Figure @@ -65,7 +67,9 @@ def whatsThis(self): def icon(self): """Icon displayed alongside Widget selection""" - return QtGui.QIcon() + mpl_data = os.path.dirname(__file__) + mpl_icon = os.path.join(mpl_data, 'images/matplotlib_large.png') + return QtGui.QIcon(mpl_icon) def domXml(self): """XML Description of the widget's properties""" From e3855b8ac9cd650e0a61432536c7b55004417a98 Mon Sep 17 00:00:00 2001 From: teddyrendahl Date: Sun, 15 Jul 2018 16:41:26 -0700 Subject: [PATCH 5/8] MAINT: Point to Python module not file --- lib/matplotlib/mpl-data/figure_plugin.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/matplotlib/mpl-data/figure_plugin.py b/lib/matplotlib/mpl-data/figure_plugin.py index f761975325e1..2ffbbcc3d508 100644 --- a/lib/matplotlib/mpl-data/figure_plugin.py +++ b/lib/matplotlib/mpl-data/figure_plugin.py @@ -86,4 +86,4 @@ def domXml(self): def includeFile(self): """Include a link to this file for reference""" - return __file__ + return FigureCanvasQT.__module__ From 9c72bb969b97dbebed285439f4d1483dadfbed42 Mon Sep 17 00:00:00 2001 From: teddyrendahl Date: Wed, 18 Jul 2018 22:09:45 -0700 Subject: [PATCH 6/8] MAINT: Create a FigureCanvasQtAgg class loadable by Designer --- lib/matplotlib/backends/backend_qt5agg.py | 10 ++++++++++ lib/matplotlib/mpl-data/figure_plugin.py | 10 ++++------ 2 files changed, 14 insertions(+), 6 deletions(-) diff --git a/lib/matplotlib/backends/backend_qt5agg.py b/lib/matplotlib/backends/backend_qt5agg.py index ab8cbe4994b3..011459087639 100644 --- a/lib/matplotlib/backends/backend_qt5agg.py +++ b/lib/matplotlib/backends/backend_qt5agg.py @@ -5,6 +5,7 @@ import ctypes from matplotlib.transforms import Bbox +from matplotlib.figure import Figure from .backend_agg import FigureCanvasAgg from .backend_qt5 import ( @@ -88,3 +89,12 @@ def print_figure(self, *args, **kwargs): @_BackendQT5.export class _BackendQT5Agg(_BackendQT5): FigureCanvas = FigureCanvasQTAgg + + +class _FigureCanvasQTAgg(FigureCanvasQTAgg): + """Subclass of FigureCanvasQTAgg that accepts a QWidget as its sole arg + """ + def __init__(self, parent): + figure = Figure() + super().__init__(figure) + self.setParent(parent) diff --git a/lib/matplotlib/mpl-data/figure_plugin.py b/lib/matplotlib/mpl-data/figure_plugin.py index 2ffbbcc3d508..a1494ecc8c79 100644 --- a/lib/matplotlib/mpl-data/figure_plugin.py +++ b/lib/matplotlib/mpl-data/figure_plugin.py @@ -3,7 +3,7 @@ """ import os.path -from matplotlib.backends.backend_qt5 import FigureCanvasQT +from matplotlib.backends.backend_qt5agg import _FigureCanvasQTAgg from matplotlib.figure import Figure # Pyside and Pyside2 do not support the QtDesigner functionality that is @@ -38,14 +38,12 @@ def isInitialized(self): def createWidget(self, parent): """Create a FigureCanvasQT instance""" # Create the Canvas with a new Figure - fig = FigureCanvasQT(Figure()) - # Set the parent of the newly created widget - fig.setParent(parent) + fig = _FigureCanvasQTAgg(parent) return fig def name(self): """Name of plugin displayed in QtDesigner""" - return "FigureCanvasQT" + return "_FigureCanvasQTAgg" def group(self): """Name of plugin group header in QtDesigner""" @@ -86,4 +84,4 @@ def domXml(self): def includeFile(self): """Include a link to this file for reference""" - return FigureCanvasQT.__module__ + return _FigureCanvasQTAgg.__module__ From e816380a72a694c40adc4da61b5a6430f5e1205c Mon Sep 17 00:00:00 2001 From: teddyrendahl Date: Wed, 18 Jul 2018 22:10:49 -0700 Subject: [PATCH 7/8] MAINT: Use rcParams to find matplotlib icon --- lib/matplotlib/mpl-data/figure_plugin.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/lib/matplotlib/mpl-data/figure_plugin.py b/lib/matplotlib/mpl-data/figure_plugin.py index a1494ecc8c79..98a30196abcb 100644 --- a/lib/matplotlib/mpl-data/figure_plugin.py +++ b/lib/matplotlib/mpl-data/figure_plugin.py @@ -3,6 +3,7 @@ """ import os.path +import matplotlib from matplotlib.backends.backend_qt5agg import _FigureCanvasQTAgg from matplotlib.figure import Figure @@ -66,7 +67,8 @@ def whatsThis(self): def icon(self): """Icon displayed alongside Widget selection""" mpl_data = os.path.dirname(__file__) - mpl_icon = os.path.join(mpl_data, 'images/matplotlib_large.png') + mpl_icon = os.path.join(matplotlib.rcParams['datapath'], + 'images', 'matplotlib_large.png') return QtGui.QIcon(mpl_icon) def domXml(self): From f61524d7e7a91b12546ec5a308c9c94b8bfccf44 Mon Sep 17 00:00:00 2001 From: teddyrendahl Date: Wed, 18 Jul 2018 22:17:18 -0700 Subject: [PATCH 8/8] DOC: Instructions on how to use QtDesigner plugin --- doc/users/next_whats_new/qt_designer.rst | 68 ++++++++++++++++++++++++ 1 file changed, 68 insertions(+) create mode 100644 doc/users/next_whats_new/qt_designer.rst diff --git a/doc/users/next_whats_new/qt_designer.rst b/doc/users/next_whats_new/qt_designer.rst new file mode 100644 index 000000000000..859f1c7c60a3 --- /dev/null +++ b/doc/users/next_whats_new/qt_designer.rst @@ -0,0 +1,68 @@ +Using Matplotlib with QtDesigner +-------------------------------- +QtDesigner is a "WYSIWYG" editor for creating Qt user interfaces. In addition +to the widgets packaged in the Qt library, there is support for custom PyQt5 +widgets to be made available within this framework as well. The addition of the +``FigureDesignerPlugin`` makes it possible to include a ``FigureCanvasQt`` +widget by simply dragging and dropping in the QtDesigner interface. The +generated XML file can then be loaded using ``pyuic`` and populated with data +using standard ``matplotlib`` syntax. + +Environment Configuration +~~~~~~~~~~~~~~~~~~~~~~~~~ +Before using the ``FigureDesignerPlugin`` you need to make sure you are in a +compatible environment. First off, ``PyQt5`` has the option to be installed +without QtDesigner. Make sure the installation you were using has not excluded +the Designer. + +We will also need to set the ``$PYQTDESIGNERPATH`` environment variable to +properly locate our custom widget plugin. This informs the QtDesigner that we +want the ``FigureCanvas`` widget as an option while we are creating our screen. +On Linux this would look like the example below with your own path to the +``matplotlib`` source code substituted in place. + +.. code:: bash + + export PYQTDESIGNERPATH=$PYQTDESIGNERPATH:/path/to/matplotlib/lib/matplotlib/mpl-data + +For more information consult the `official PyQt +`_ +documentation. If you are unsure where to find the ``mpl-data`` folder you can +refer to the ``matplotlib.rcParams['datapath']`` + +Usage +~~~~~ +The general process for using the ``QtDesigner`` and ``matplotlib`` is +explained below: + +1. If your environment is configured correctly you should see the + ``FigureCanvasQt`` widget in the left hand column of your QtDesigner + interface. It can now be used as if it were any other widget, place it in + its desired location and give it a meaningful ``objectName`` for reference + later. The code below assumes you called it "example_plot" + +2. Once you are done creating your interface in Designer it is time to load our + the ``.ui`` file created and manipulate the ``matplotlib.Figure``. The + simplest way is to use the ``uic`` to load the ``.ui`` file into a custom + widget. This will make our ``FigureCanvasQt`` object we created in Designer + available to us. + + .. code:: python + + from PyQt5 import uic + from PyQt5.QtWidgets import QWidget + + # Create a QWidget to contain our created display + my_widget = QWidget() + # Load the UI we created in Designer + uic.loadUi('path/to/my_file.ui', widget) + # We now access to the Figure we created in Designer + my_widget.example_plot.figure + +3. Now use standard ``matplotlib`` syntax to add axes and data to the + ``Figure``. + + .. code:: python + + ax = my_widget.example_plot.figure.add_subplot(1,1,1) + ax.plot([1, 2, 3], [3, 2, 1])