1

I am trying to update several plots using a matplotlibs embedding in a QT widget. Right now I can have one plot update in the window. But when I try to switch to the other plot by clicking a button the program freezes.

This is a test script I am using to understand how to use the programming tool to integrate into a larger program. I modified the code from this question: How to embed matplotlib in pyqt - for Dummies

I have been stuck on this problem for a while now. I know I am missing something very simple.


import random
import sys
from PyQt4 import QtGui, QtCore

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

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

        # a figure instance to plot on
        self.figure1 = Figure()
        self.figure2 = Figure()
        self.current = "fig1"
        # this is the Canvas Widget that displays the `figure`
        # it takes the `figure` instance as a parameter to __init__
        self.canvas = FigureCanvas(self.figure1)
        self.ax1 = self.figure1.add_subplot(111)
        self.ax2 = self.figure2.add_subplot(111)
        self.line1, = self.ax1.plot([], [], 'r', lw=2)
        self.line2, = self.ax2.plot([], [], 'b', lw=2)
        # 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)
        self.update()


    def update(self):
        datax = [random.random() for i in range(10)]
        datay = [random.random() for i in range(10)]

        self.line1.set_xdata(datax)
        self.line1.set_ydata(datay)
        self.ax1.relim()
        self.ax1.autoscale_view()


        self.line2.set_xdata(datax)
        self.line2.set_ydata(datay)
        self.ax2.relim()
        self.ax2.autoscale_view()
        self.canvas.draw()
        QtCore.QTimer.singleShot(1, self.update)




    def plot(self):

        if self.current == "fig1":
            self.canvas = FigureCanvas(self.figure2)
            self.current = "fig2"
        elif self.current == "fig2":
            self.canvas = FigureCanvas(self.figure1)
            self.current = "fig1"


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

    main = Window()
    main.show()

    sys.exit(app.exec_())


When i click this button it should start plotting the other plot. I get no error messages.

eyllanesc
  • 235,170
  • 19
  • 170
  • 241
nagoldfarb
  • 66
  • 11
  • What do you mean with another plot? Where would that other plot be shown? – eyllanesc Jun 28 '19 at 01:00
  • i want to have a single window where the matplotlib figure is shown and when I click the button I want to have the figure switch to another matplot figure. So only one of my two plots is shown at a time in the window and I can flip between the two by hitting the button. – nagoldfarb Jun 28 '19 at 01:26

1 Answers1

0

Your logic of making the canvas change:

def plot(self):
    if self.current == "fig1":
        self.canvas = FigureCanvas(self.figure2)
        self.current = "fig2"
    elif self.current == "fig2":
        self.canvas = FigureCanvas(self.figure1)
        self.current = "fig1"

It is similar to the following to the following logic:

class Foo:
    def __init__(self):
        self.m_a = 0

    def setA(self, a):
        self.m_a = a

    def printA(self):
        print(self.m_a)


class Bar:
    def __init__(self):
        self.a = 5
        self.foo = Foo()
        self.foo.setA(self.a)

    def change(self):
        self.a = 6


if __name__ == "__main__":
    bar = Bar()
    bar.foo.printA()
    bar.change()
    bar.foo.printA()

And if the code is executed you get the following:

5
5

As you see a change of a variable does not imply that the other classes are notified because the name of a variable is a nickname, the important thing is the memory, in your case the displayed canvas occupies a memory place different from the canvas that you create when you press the button, the same happens with line1, line2, ax1 and ax2 that are associated with the initial canvas.


On the other hand in a GUI is not recommended to delete and create widgets, instead it is better to hide a widget and show the other that occupies the same space and for this we can use a QStackedWidget or QStackedLayout.

import sys
import random
from PyQt4 import QtGui, QtCore

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


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

        self.figure1 = Figure()
        self.canvas1 = FigureCanvas(self.figure1)
        self.ax1 = self.figure1.add_subplot(111)
        self.line1, = self.ax1.plot([], [], "r", lw=2)
        self.toolbar1 = NavigationToolbar(self.canvas1, self)

        self.figure2 = Figure()
        self.canvas2 = FigureCanvas(self.figure2)
        self.ax2 = self.figure2.add_subplot(111)
        self.line2, = self.ax2.plot([], [], "b", lw=2)
        self.toolbar2 = NavigationToolbar(self.canvas2, self)

        self.m_stacked_layout = QtGui.QStackedLayout()

        for canvas, toolbar in (
            (self.canvas1, self.toolbar1),
            (self.canvas2, self.toolbar2),
        ):
            widget = QtGui.QWidget()
            lay = QtGui.QVBoxLayout(widget)
            lay.addWidget(toolbar)
            lay.addWidget(canvas)
            self.m_stacked_layout.addWidget(widget)

        self.button = QtGui.QPushButton("Plot", clicked=self.onClicled)

        layout = QtGui.QVBoxLayout(self)
        layout.addLayout(self.m_stacked_layout)
        layout.addWidget(self.button)

        timer = QtCore.QTimer(self, timeout=self.update_plot, interval=1)
        timer.start()
        self.update_plot()

    @QtCore.pyqtSlot()
    def update_plot(self):
        datax = [random.random() for i in range(10)]
        datay = [random.random() for i in range(10)]

        self.line1.set_xdata(datax)
        self.line1.set_ydata(datay)
        self.ax1.relim()
        self.ax1.autoscale_view()

        self.line2.set_xdata(datax)
        self.line2.set_ydata(datay)
        self.ax2.relim()
        self.ax2.autoscale_view()
        self.canvas1.draw()
        self.canvas2.draw()

    @QtCore.pyqtSlot()
    def onClicled(self):
        ix = self.m_stacked_layout.currentIndex()
        self.m_stacked_layout.setCurrentIndex(0 if ix == 1 else 1)


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

    main = Window()
    main.show()

    sys.exit(app.exec_())
eyllanesc
  • 235,170
  • 19
  • 170
  • 241