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]) 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/backends/qt_compat.py b/lib/matplotlib/backends/qt_compat.py index 759fe24a7a66..71e4b8459a8d 100644 --- a/lib/matplotlib/backends/qt_compat.py +++ b/lib/matplotlib/backends/qt_compat.py @@ -134,7 +134,7 @@ 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: @@ -148,7 +148,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: diff --git a/lib/matplotlib/mpl-data/figure_plugin.py b/lib/matplotlib/mpl-data/figure_plugin.py new file mode 100644 index 000000000000..98a30196abcb --- /dev/null +++ b/lib/matplotlib/mpl-data/figure_plugin.py @@ -0,0 +1,89 @@ +""" +Plugin for drag and drop matplotlib.Figure in QtDesigner +""" +import os.path + +import matplotlib +from matplotlib.backends.backend_qt5agg import _FigureCanvasQTAgg +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 = _FigureCanvasQTAgg(parent) + return fig + + def name(self): + """Name of plugin displayed in QtDesigner""" + return "_FigureCanvasQTAgg" + + 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""" + mpl_data = os.path.dirname(__file__) + mpl_icon = os.path.join(matplotlib.rcParams['datapath'], + 'images', 'matplotlib_large.png') + return QtGui.QIcon(mpl_icon) + + 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 _FigureCanvasQTAgg.__module__