«

»

Jul 26

Embedding a matplotlib plot on a PyQt GUI

Matplotlib (http://matplotlib.sourceforge.net/) provides a great range of plotting tools and functionality. It is a nice alternative for Matlab. Making a plot with matplotlib in Python is not that difficult. But often you need to be able to embed a plot into a GUI with other components like push buttons and text fields and so on. This article shows you how to embed any Matplotlib on to a GUI made with PyQt.

First do File-> New and create a new Dialog form without buttons (we’ll add by ourselves). We’ll be making a simple GUI that plot some random numbers on a matplotlib graph with a push of a button. So first place a Push button on the dialog (drag and drop from the left pane). Now lets get ready to draw our plot. Since matplotlib is not part of PyQt it doesn’t know how to handle it, and it shouldn’t. All we need to do is make a place on our dialog for matplotlib to do it’s drawing. matplotlib will be a widget on the PyQt dialog. So select Widget (from Containers) from the left pane and drop in on our GUI. Now your PyQt would look like this:

Note in the Object Inspector (right-top) that the widget is of class QWidget. But our intention is to draw a matplotlib on it. So what we need to do is change the behavior of the widget. That is, instead of being of class type QWidget, we want it to be of a type that can draw a matplotlib graph. This is called promoting. We need to promote the widget to a type of our need. Doing that is pretty easy. Right-click on widget in Object Inspector (or on the widget itself) and select Promote to. Fill the two fields as shown:

You are allowed to use any name for the class name and header file name. This just tell PyQt that a class named MatplotlibWidget, contained in file matplotlibwidgetFile.py (no need to write the .py extension) will define the behavior of this widget. I purposely changed the class name and header file names to show that they do not need to be the same. Now click Add and Promote.  Take a note of your widget type now in Object Inspector:

Note how the class type of the widget is now changed to matplotlibWidget, what we promoted it to. Now all we need to do is create a matplotlibwidgetFile.py and define the widgets behavior. But before doing that we need to translate this GUI code to Python code. Save this GUI as PlotGUI.ui. PlotGUI.ui is just an XML. You will have something similar to this:

<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
 <class>Dialog</class>
 <widget class="QDialog" name="Dialog">
  <property name="geometry">
   <rect>
    <x>0</x>
    <y>0</y>
    <width>570</width>
    <height>449</height>
   </rect>
  </property>
  <property name="windowTitle">
   <string>Dialog</string>
  </property>
  <widget class="QPushButton" name="pushButton">
   <property name="geometry">
    <rect>
     <x>30</x>
     <y>20</y>
     <width>501</width>
     <height>41</height>
    </rect>
   </property>
   <property name="text">
    <string>Push To Plot</string>
   </property>
  </widget>
  <widget class="matplotlibWidget" name="widget" native="true">
   <property name="geometry">
    <rect>
     <x>40</x>
     <y>90</y>
     <width>501</width>
     <height>321</height>
    </rect>
   </property>
  </widget>
 </widget>
 <customwidgets>
  <customwidget>
   <class>matplotlibWidget</class>
   <extends>QWidget</extends>
   <header>matplotlibwidgetFile</header>
   <container>1</container>
  </customwidget>
 </customwidgets>
 <resources/>
 <connections/>
</ui>

To translate this to a .py file:

$ pyuic4 PlotGUI.ui > PlotGUI.py

You will have a Python file similar to this:

# -*- coding: utf-8 -*-

# Form implementation generated from reading ui file 'PlotGUI.ui'
#
# Created: Thu Jul 26 23:56:29 2012
#      by: PyQt4 UI code generator 4.8.3
#
# WARNING! All changes made in this file will be lost!

from PyQt4 import QtCore, QtGui

try:
    _fromUtf8 = QtCore.QString.fromUtf8
except AttributeError:
    _fromUtf8 = lambda s: s

class Ui_Dialog(object):
    def setupUi(self, Dialog):
        Dialog.setObjectName(_fromUtf8("Dialog"))
        Dialog.resize(570, 449)
        self.pushButton = QtGui.QPushButton(Dialog)
        self.pushButton.setGeometry(QtCore.QRect(30, 20, 501, 41))
        self.pushButton.setObjectName(_fromUtf8("pushButton"))
        self.widget = matplotlibWidget(Dialog)
        self.widget.setGeometry(QtCore.QRect(40, 90, 501, 321))
        self.widget.setObjectName(_fromUtf8("widget"))

        self.retranslateUi(Dialog)
        QtCore.QMetaObject.connectSlotsByName(Dialog)

    def retranslateUi(self, Dialog):
        Dialog.setWindowTitle(QtGui.QApplication.translate("Dialog", "Dialog", None, QtGui.QApplication.UnicodeUTF8))
        self.pushButton.setText(QtGui.QApplication.translate("Dialog", "Push To Plot", None, QtGui.QApplication.UnicodeUTF8))

from matplotlibwidgetFile import matplotlibWidget

Note the final line in the file. It includes the matplotlibwidgetFile and import matplotlibWidget, the class that is going to define the behavior of this widget. These two lines,

self.pushButton = QtGui.QPushButton(Dialog)
self.widget = matplotlibWidget(Dialog)

define the two objects in our GUI: push button and the widget. Note that pushButton and widget will be their identifiers. We will be using these two names to access them.

So far we have created a PyQt GUI, added a push button and widget to it, and we have specified that the widgets behavior will be handled by matplotlibWidget class. What we need to do now is to define that class then. It is pretty easy:

from PyQt4 import QtGui
from matplotlib.backends.backend_qt4agg import FigureCanvasQTAgg as FigureCanvas

from matplotlib.figure import Figure

class MplCanvas(FigureCanvas):

    def __init__(self):
        self.fig = Figure()
        self.ax = self.fig.add_subplot(111)

        FigureCanvas.__init__(self, self.fig)
        FigureCanvas.setSizePolicy(self, QtGui.QSizePolicy.Expanding,QtGui.QSizePolicy.Expanding)
        FigureCanvas.updateGeometry(self)


class matplotlibWidget(QtGui.QWidget):

    def __init__(self, parent = None):
        QtGui.QWidget.__init__(self, parent)
        self.canvas = MplCanvas()
        self.vbl = QtGui.QVBoxLayout()
        self.vbl.addWidget(self.canvas)
        self.setLayout(self.vbl)

What this file essentially doing is creating a canvas to draw and give it to our matplotlibWidget. So we now have defined how our plotting widget will be handled. All that is left is to actually draw something on it.

Here’s our main program that will draw random numbers on the matplotlib graph when we push the button:

import sys
from PlotGUI import *
import random

class GUIForm(QtGui.QDialog):

    def __init__(self, parent=None):
        QtGui.QWidget.__init__(self,parent)
        self.ui = Ui_Dialog()
        self.ui.setupUi(self)
        QtCore.QObject.connect(self.ui.pushButton, QtCore.SIGNAL('clicked()'), self.PlotFunc)

    def PlotFunc(self):
        randomNumbers = random.sample(range(0, 10), 10)
        self.ui.widget.canvas.ax.clear()
        self.ui.widget.canvas.ax.plot(randomNumbers)
        self.ui.widget.canvas.draw()


if __name__ == "__main__":
    app = QtGui.QApplication(sys.argv)
    myapp = GUIForm()
    myapp.show()
    sys.exit(app.exec_())

Everytime we push the button it will call PlotFunc() and a random set of integers are drawn on the graph. If we break out the code, the first execution is at:

if __name__ == "__main__":
    app = QtGui.QApplication(sys.argv)
    myapp = GUIForm()
    myapp.show()
    sys.exit(app.exec_())

This part basically creates an instance of an QApplication to run our GUI instance. Second it creates as instance of GUIForm. This is our class that we do our processing and pass data to our widget to draw. When we create an instance if GUIForm it calls the initializer:

    def __init__(self, parent=None):
        QtGui.QWidget.__init__(self,parent)
        self.ui = Ui_Dialog()
        self.ui.setupUi(self)
        QtCore.QObject.connect(self.ui.pushButton, QtCore.SIGNAL('clicked()'), self.PlotFunc)

Here it first creates an instance of Ui_Dialog. Note that Ui_Dialog is the class name in PlotGUI.py, the Python file that was generated from our GUI file. So this is basically making an instance of our GUI and then sets it up by calling setupUi(self). Final line is where we connect the push button signal to the PlotFunc. Then after the GUI is built myapp.show() will render the GUI on screen. app.exec_() starts a loop where any action on the GUI (clicks, etc) will be caught and handled and passing it to sys.exit() makes sure when we close the GUI is exits gracefully. The PlotFunc in pretty self explanatory:

It accesses the widget object (remember that it is the name it is named in PlotGUI.py) to make the drawing.  You will see something similar when you click the push button:

This is pretty much it for embedding a matplotlib graph on a PyQt GUI.

References:

[1] Matplotlib for Python Developers

[2] Introduction to Python Programming and Developing GUI Applications With PyQT

3 comments

  1. Wilhelmsen

    Even though I don’t know Python (I’m a C-programmer), this is amazing. Great article, keep it up!

  2. anonymous

    I found this very useful
    a slight modification to this code, that embeds a toolbar along with the canvas widget:

    https://gist.github.com/anonymous/5357307

  3. Nobody

    Perfect. Exactly what I was looking for. Thanks!

%d bloggers like this: