30

I don't understand the best way to link a matplotlib figure to a form created from Qt Designer. I have a form I created in QtDesigner and then compiled to python through pyuic5. My main program is:

import app_framework as af
import matplotlib
from PyQt5 import QtWidgets
import sys

matplotlib.use('Qt5Agg')

app = QtWidgets.QApplication(sys.argv)
form = af.MyApp()
form.show()
app.exec_()

where myApp calls the app_framework.py form created from Qt Designer then converted by pyuic5 (design.py):

from PyQt5.QtWidgets import QApplication, QMainWindow
import design

class MyApp(QMainWindow, design.Ui_mainWindow):
   def __init(self):
       super(self.__class__, self).__init__()
       <button initializations>
       <function definitions for button callbacks>

I'm confused as to where in this framework I can link a matplotlib figure to a premade empty widget in QtDesigner, or something of that sort, so that I can plot new data in the GUI window as things happen (text entered, button push, etc.)

I've found some threads here on SO and matplotlib's site, but I'm not sure I understand the correct process for creating the space for this widget in the Qt Designer form then linking a plot, and/or creating a widget post hoc and then linking and plotting.

What I've done so far is create an empty QWidget inside Qt Creator and then after pyuic5 compile, I alter the design.py file as follows:

from PyQt5 import QtCore, QtGui, QtWidgets

# **** ADDED THIS
from matplotlib.figure import Figure
from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg as Canvas
# **** 

class Ui_mainWindow(object):
    def setupUi(self, mainWindow):
        <mainWindow setup stuff>
        self.centralwidget = QtWidgets.QWidget(mainWindow)

        # ****ALTERED THIS FROM self.plotWidget = QtWidgets.QWidget(self.centralWidget)
        self.plotWidget = MplWidget(self.centralWidget)
        # ***** 

        self.plotWidget.setGeometry(QtCore.QRect(20, 250, 821, 591))
        self.plotWidget.setObjectName("plotWidget")

  # **** ADDED THIS 
class MplCanvas(Canvas):
    def __init__(self):
        self.fig = Figure()
        self.ax = self.fig.add_subplot(111)
        Canvas.__init__(self, self.fig)
        Canvas.setSizePolicy(self, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Expanding)
        Canvas.updateGeometry(self)


class MplWidget(QtWidgets.QWidget):
    def __init__(self, parent=None):
        QtWidgets.QWidget.__init__(self, parent)
        self.canvas = MplCanvas()
 # ***********

and then app_framework.py as:

from PyQt5.QtWidgets import QApplication, QMainWindow
import design

class MyApp(QMainWindow, design.Ui_mainWindow):
   def __init(self):
       super(self.__class__, self).__init__()
       self.setupUi(self)
       self.pushButton_plotData.clicked.connect(self.plot_data)

    def plot_data(self):
        x=range(0, 10)
        y=range(0, 20, 2)
        self.plotWidget.canvas.ax.plot(x, y)
        self.plotWidget.canvas.draw()

I thought this would work, but when I click the plot push button, nothing happens. It doesn't lock up, it just doesn't plot anything. I'm guessing I'm missing something fundamental for plotting a matplotlib figure and/or canvas in this empty widget.

Community
  • 1
  • 1
launchpadmcquack
  • 1,021
  • 1
  • 12
  • 19

1 Answers1

28

I found the solution through the help of this SO post, which links to this free sample chapter of a book I'm probably gonna need to buy. As sort of mentioned in a number of other posts, one needs to "Promote" the blank QtWidget to a MplWidget using the header mplwidget. After doing this, and then running the pyuic5 command, "from mplwidget import MplWidget" will appear in the design.py file which can be updated anytime without worry about overwritting. Then, create an mplwidget.py file with:

# Imports
from PyQt5 import QtWidgets
from matplotlib.figure import Figure
from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg as Canvas
import matplotlib

# Ensure using PyQt5 backend
matplotlib.use('QT5Agg')

# Matplotlib canvas class to create figure
class MplCanvas(Canvas):
    def __init__(self):
        self.fig = Figure()
        self.ax = self.fig.add_subplot(111)
        Canvas.__init__(self, self.fig)
        Canvas.setSizePolicy(self, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Expanding)
        Canvas.updateGeometry(self)

# Matplotlib widget
class MplWidget(QtWidgets.QWidget):
    def __init__(self, parent=None):
        QtWidgets.QWidget.__init__(self, parent)   # Inherit from QWidget
        self.canvas = MplCanvas()                  # Create canvas object
        self.vbl = QtWidgets.QVBoxLayout()         # Set box for plotting
        self.vbl.addWidget(self.canvas)
        self.setLayout(self.vbl)

Then, the app framework can be as I had earlier. When run, and pushing the plot button, the figure appears as expected. I think I basically had done everything to "Promote" the QWidget by hand just missing the vbl stuff, but all that would be overwritten everytime the Qt Designer form is editted.

app_framework.py:

from PyQt5.QtWidgets import QApplication, QMainWindow
import design

class MyApp(QMainWindow, design.Ui_mainWindow):
   def __init(self):
       super(self.__class__, self).__init__()
       self.setupUi(self)
       self.pushButton_plotData.clicked.connect(self.plot_data)

    def plot_data(self):
        x=range(0, 10)
        y=range(0, 20, 2)
        self.plotWidget.canvas.ax.plot(x, y)
        self.plotWidget.canvas.draw()

main.py:

from PyQt5 import QtWidgets
import sys

# Local Module Imports
import app_framework as af

# Create GUI application
app = QtWidgets.QApplication(sys.argv)
form = af.MyApp()
form.show()
app.exec_()
Community
  • 1
  • 1
launchpadmcquack
  • 1,021
  • 1
  • 12
  • 19
  • 2
    On my Mac, I had to put the lines 'import matplotlib' and 'matplotlib.use('QT5Agg')' before the other matplotlib imports. As the code is written in the answer, it generated a warning saying: This call to matplotlib.use() has no effect because the backend has already been chosen; matplotlib.use() must be called *before* pylab, matplotlib.pyplot, or matplotlib.backends is imported for the first time. The backend was *originally* set to 'MacOSX' by the following code: ... ... from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg as Canvas – boudewijn21 Oct 15 '18 at 13:11
  • Exactly how does one "Promote the blank QtWidget to a MplWidget using the header mplwidget."? – Echeban Nov 17 '19 at 16:33
  • 3
    In Qt Designer drop a Widget object from the containers group onto your designer surface. Right click the object and select Promote to. A Promoted Widgets dialog appears allowing you to select the Base class name (default : QWidget ) and a Promoted class name. Chose MplWidget for your promoted class name and Qt Designer suggests a Header file name of mplwidget.h. When you build your .py file from the .ui file now you get the line of code 'from mplwidget import MplWidget' at the end of the .py file. Then you can write your mplwidget.py file as shown in this example. – Grant Johnson Apr 27 '20 at 14:03
  • If you are using packages and calling the Python Qt GUI from the base module, as I am while using PyDev in Eclipse, the promoted class name is still MplWidget, but the header file is the path without the extension: src.MyQtGui.mplwidget (no .py at the end) – Casey Jul 21 '20 at 01:37