86

I am currently trying to embed a graph I want to plot in a pyqt4 user interface I designed. As I am almost completely new to programming - I do not get how people did the embedding in the examples I found - this one (at the bottom) and that one.

It would be awesome if anybody could post a step-by-step explanation or at least a very small, very simple code only creating e.g. a graph and a button in one pyqt4 GUI.

Cœur
  • 37,241
  • 25
  • 195
  • 267
ari
  • 1,122
  • 1
  • 9
  • 15

3 Answers3

123

It is not that complicated actually. Relevant Qt widgets are in matplotlib.backends.backend_qt4agg. FigureCanvasQTAgg and NavigationToolbar2QT are usually what you need. These are regular Qt widgets. You treat them as any other widget. Below is a very simple example with a Figure, Navigation and a single button that draws some random data. I've added comments to explain things.

import sys
from PyQt4 import QtGui

from matplotlib.backends.backend_qt4agg import FigureCanvasQTAgg as FigureCanvas
from matplotlib.backends.backend_qt4agg import NavigationToolbar2QT as NavigationToolbar
from matplotlib.figure import Figure

import random

class Window(QtGui.QDialog):
    def __init__(self, parent=None):
        super(Window, self).__init__(parent)

        # a figure instance to plot on
        self.figure = Figure()

        # this is the Canvas Widget that displays the `figure`
        # it takes the `figure` instance as a parameter to __init__
        self.canvas = FigureCanvas(self.figure)

        # this is the Navigation widget
        # it takes the Canvas widget and a parent
        self.toolbar = NavigationToolbar(self.canvas, self)

        # Just some button connected to `plot` method
        self.button = QtGui.QPushButton('Plot')
        self.button.clicked.connect(self.plot)

        # set the layout
        layout = QtGui.QVBoxLayout()
        layout.addWidget(self.toolbar)
        layout.addWidget(self.canvas)
        layout.addWidget(self.button)
        self.setLayout(layout)

    def plot(self):
        ''' plot some random stuff '''
        # random data
        data = [random.random() for i in range(10)]

        # create an axis
        ax = self.figure.add_subplot(111)

        # discards the old graph
        ax.clear()

        # plot data
        ax.plot(data, '*-')

        # refresh canvas
        self.canvas.draw()

if __name__ == '__main__':
    app = QtGui.QApplication(sys.argv)

    main = Window()
    main.show()

    sys.exit(app.exec_())

Edit:

Updated to reflect comments and API changes.

  • NavigationToolbar2QTAgg changed with NavigationToolbar2QT
  • Directly import Figure instead of pyplot
  • Replace deprecated ax.hold(False) with ax.clear()
Avaris
  • 35,883
  • 7
  • 81
  • 72
  • 15
    Note that according to this [SO note](http://stackoverflow.com/questions/17973177/matplotlib-and-pyqt-dynamic-figure-runs-slow-after-several-loads-or-looks-messy#comment26295260_17973177), one should not import pyplot when embedding. It does funky things like running its own GUI with its own main loop.There is a [Matplotlib example](http://matplotlib.org/examples/user_interfaces/embedding_in_qt4.html) on embedding – Laurence Billingham Oct 01 '14 at 20:03
  • 9
    I have an error: MatplotlibDeprecationWarning: This class has been deprecated in 1.4 as it has no additional functionality over `NavigationToolbar2QT`. Please change your code to use `NavigationToolbar2QT` instead mplDeprecation) – GSandro_Strongs Oct 26 '15 at 22:22
  • 5
    @gs_developer_user3605534 replacing `NavigationToolbar2QTAgg` by `NavigationToolbar2QT` kills the message – SAAD May 20 '16 at 10:12
  • When I use this code, it creates two windows, one Qt main window and one matplotlib figure. How can I avoid that so that it creates just one Qt main window? I use python 3.4 32bit, matplotlib 1.5.3 and PyQt4 ( I also had to change `NavigationToolbar2QTAgg` to `NavigationToolbar2QT`). I found that one solution is using IPython – C Winkler May 11 '17 at 09:47
  • I'm using this to put a `QVBoxLayout` inside a main window. Is there a way to get the canvas and figure to fill a tall rectangle (approximately 1.5 times as tall as it is wide)? – Kajsa Jan 10 '18 at 22:06
  • @Kajsa, it should be doable with [heightForWidth](http://doc.qt.io/archives/qt-4.8/qlayoutitem.html#heightForWidth). – Avaris Jan 10 '18 at 23:00
  • With QT5, just change to: `from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg as FigureCanvas` and `from matplotlib.backends.backend_qt5agg import NavigationToolbar2QT as NavigationToolbar` – iperetta Oct 17 '18 at 21:26
  • How do I resize the canvas? – Neeraj Kumar Jul 23 '19 at 14:26
  • @NeerajKumar What do you mean "resize"? My example uses all the available space on the window. It'll grow/shrink with the window. – Avaris Jul 23 '19 at 15:11
  • I don't want it to take all the available space but just a portion of it. Also, instead of plotting a graph I want to plot an image there (like plt.imshow() ) . How do I do that? – Neeraj Kumar Jul 23 '19 at 15:23
  • `ax.imshow` instead of `ax.plot`. Regarding size, depends on what else is going to occupy rest of it. Qt layouts handle sizing widgets based on SizePolicy. You need to adjust that. – Avaris Jul 23 '19 at 19:32
86

Below is an adaptation of previous code for using under PyQt5 and Matplotlib 2.0. There are a number of small changes: structure of PyQt submodules, other submodule from matplotlib, deprecated method has been replaced...


import sys
from PyQt5.QtWidgets import QDialog, QApplication, QPushButton, QVBoxLayout

from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg as FigureCanvas
from matplotlib.backends.backend_qt5agg import NavigationToolbar2QT as NavigationToolbar
import matplotlib.pyplot as plt

import random

class Window(QDialog):
    def __init__(self, parent=None):
        super(Window, self).__init__(parent)

        # a figure instance to plot on
        self.figure = plt.figure()

        # this is the Canvas Widget that displays the `figure`
        # it takes the `figure` instance as a parameter to __init__
        self.canvas = FigureCanvas(self.figure)

        # this is the Navigation widget
        # it takes the Canvas widget and a parent
        self.toolbar = NavigationToolbar(self.canvas, self)

        # Just some button connected to `plot` method
        self.button = QPushButton('Plot')
        self.button.clicked.connect(self.plot)

        # set the layout
        layout = QVBoxLayout()
        layout.addWidget(self.toolbar)
        layout.addWidget(self.canvas)
        layout.addWidget(self.button)
        self.setLayout(layout)

    def plot(self):
        ''' plot some random stuff '''
        # random data
        data = [random.random() for i in range(10)]

        # instead of ax.hold(False)
        self.figure.clear()

        # create an axis
        ax = self.figure.add_subplot(111)

        # discards the old graph
        # ax.hold(False) # deprecated, see above

        # plot data
        ax.plot(data, '*-')

        # refresh canvas
        self.canvas.draw()

if __name__ == '__main__':
    app = QApplication(sys.argv)

    main = Window()
    main.show()

    sys.exit(app.exec_())
Pierre H.
  • 388
  • 1
  • 11
Anabar
  • 1,091
  • 9
  • 9
  • Very useful update, runs fine! Could you give a reference for `ax.hold` being deprecated? Also, why not use `ax.clear` to reuse the Axes instance? – Pierre H. Mar 22 '17 at 13:51
  • 4
    I think it's not best practice to import `matplotlib.pyplot` when embedding matplotlib into pyqt (please correct me if I'm wrong in this siutation). If I am correct, I embedded my matplotlib using this method on this SO post, which doesn't import pyplot: https://stackoverflow.com/questions/43947318/plotting-matplotlib-figure-inside-qwidget-using-qt-designer-form-and-pyqt5/44029435#44029435 – hhprogram Aug 29 '18 at 07:20
  • How do I plot images on the canvas? – Neeraj Kumar Jul 23 '19 at 14:28
  • Normally when I plot something using just matplotlib, the resulting interactive plot has the same tool menu as in your code with features like pan and zoom. However, when I use only matplotlib, I am able to activate those tools via keyboard shortcuts, for example, if I press p, then the pan tool activates/deactivates. Is there a way to have that functionality also in PyQt? – Stefano Jul 23 '22 at 00:25
9

For those looking for a dynamic solution to embed Matplotlib in PyQt5 (even plot data using drag and drop). In PyQt5 you need to use super on the main window class to accept the drops. The dropevent function can be used to get the filename and rest is simple:

def dropEvent(self,e):
        """
        This function will enable the drop file directly on to the 
        main window. The file location will be stored in the self.filename
        """
        if e.mimeData().hasUrls:
            e.setDropAction(QtCore.Qt.CopyAction)
            e.accept()
            for url in e.mimeData().urls():
                if op_sys == 'Darwin':
                    fname = str(NSURL.URLWithString_(str(url.toString())).filePathURL().path())
                else:
                    fname = str(url.toLocalFile())
            self.filename = fname
            print("GOT ADDRESS:",self.filename)
            self.readData()
        else:
            e.ignore() # just like above functions  

For starters the reference complete code gives this output: enter image description here

Trees
  • 1,245
  • 10
  • 20