0

I have a written a large data processing and plotting application using PyQt5 with Matplotlib and I am plagued by old plots reappearing when creating new ones. I have seen many questions answered regarding this but none of the suggested methods work for me.

I have reduced the problem down to the following simplified code:

from PyQt5.QtWidgets import (QApplication, QMainWindow, QDialog, QPushButton,
    QVBoxLayout, QGroupBox)
from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg as FigureCanvas
from matplotlib.figure import Figure
import matplotlib.pyplot as plt
import sys

class PlotCanvas(FigureCanvas):
    """ Subclass of the MPL FigureCanvas class. """
    def __init__(self, parent=None):
        self.parent = parent
        self.fig = Figure()
        self.ax = self.fig.add_subplot(111)
        self.fig.set_tight_layout(True)
        FigureCanvas.__init__(self, self.fig)

class PlotWindow(QDialog):
    """ Create plot in window and plot supplied data. """
    def __init__(self, parent, x, y):
        super().__init__()
        self.parent = parent
        self.x = x
        self.y = y
        self.set_up_gui()
        self.l2d = self.canvas.ax.plot(self.x, self.y)[0]

    def set_up_gui(self):
        """ Create GUI. """
        vbl = QVBoxLayout()
        self.canvas = PlotCanvas(self)
        vbl.addWidget(self.canvas)
        self.setLayout(vbl)

    def closeEvent(self, event):
        """ Delete figure on closing window. """
        plt.close(self.canvas.fig)
        super(PlotWindow, self).closeEvent(event)

class MainApp(QMainWindow):
    """ My main application class """
    def __init__(self, parent=None):
        super(QMainWindow, self).__init__()
        # Create some arbitrary data
        self.x = [1, 2, 3, 4, 5, 6, 7, 8, 9]
        self.y = [2, 4, 6, 4, 2, 4, 6, 8, 6]
        self.set_up_gui()

    def set_up_gui(self):
        """ Create GUI with 2 buttons for creating 2 plots. """
        btn1 = QPushButton()
        btn1.setText('Button 1')
        btn1.clicked.connect(self.plot1)
        btn2 = QPushButton()
        btn2.setText('Button 2')
        btn2.clicked.connect(self.plot2)
        vbl = QVBoxLayout()
        vbl.addWidget(btn1)
        vbl.addWidget(btn2)
        gb = QGroupBox()
        gb.setLayout(vbl)
        self.setCentralWidget(gb)

    def plot1(self):
        """ Plot using my PlotWindow class. """
        p = PlotWindow(self, self.x, self.y)
        p.show()

    def plot2(self):
        """ Plot using regular MPL plot call. """
        fig = plt.figure()
        plt.plot([10, 20, 30], [4, 8, 2])
        plt.show()
        plt.close(fig)

if __name__=='__main__':
    app = QApplication(sys.argv)
    main = MainApp()
    main.show()
    sys.exit(app.exec_())

The odd behavior is this:

  • Press button 1, plot 1 appears, press red X to close window.
  • Press button 2, plot 2 appears, press red X to close window.
  • Press button 1, plot 1 appears, press red X to close window.
  • Press button 2, plot 2 appears but so does plot 1, press red X to close plot 2, plot 1 also closes.
  • From here on, button 1 brings up plot 1, and button 2 brings up both plots.

Based on answers to similar questions (such as matplotlib.pyplot will not forget previous plots - how can I flush/refresh?), I have tried various things, such as following plt.show() with plt.cla() plt.clf() and plt.close() but none of them fix the problem. As you can see in my code, I have added the the plt.close() to the PyQt5 window close event and also the regular MPL plot but this seems to do nothing.

EDIT: Now that the question is answered, I have cleaned up some of the musings that followed and reduced it to the pertinent information.

I tried this code out on my work Linux machine (originally developed on Mac) and don't get the same double plot behavior. Which shows it's platform-dependent.

On Linux I can actually remove the plt.close() from the plot2 method and I can remove the closeEvent method from the PlotWindow class and it works fine without any double plots showing.

  • couple possible bugs... idk if your code is directly copied so maybe unhelpful. but when working with guis, the smallest thing can sometimes cause big issues... anyway. under plot1, you create a PlotWindow and pass in self.x twice, but it is expecting x and y. Also, in plot2, you use plt.figure(), but in PlotCanvas, you use matplotlib.figure.Figure(). Not sure why you'd use two different implementations – Andrew Pye Oct 27 '20 at 19:53
  • To be honest, it was a bit of an ordeal stripping it down to something that worked and I had to change some minor things a little, such as what data is being processed. Fixed the self.x repeat which, as you correctly expected was supposed to be x, y. As for using different implementations, the plt.figure() is my go-to quick way of plotting so was what I turned to to get a quick top-level plot without having to create a new plot window. I'm pretty sure I will have got the matplotlib.figure.Figure() usage from some example online. Have tried both implementations now and the problem remains. – Andrew Birch Oct 27 '20 at 20:33
  • This might be dumb, but have you tried using fig=plt.figure(1) in the plot2 creation? It may be that both calls are actually referring to the same figure window (even though you have tried to avoid that). add the "1" in there will (hopefully) force it to use a different figure – Andrew Pye Oct 27 '20 at 21:58
  • Just tried that Andrew and it makes no difference :-( – Andrew Birch Oct 27 '20 at 22:17

1 Answers1

0

Ok, through trial end error, I have fixed this problem. I don't claim to know exactly the mechanics of how it is fixed. Perhaps seeing how I fixed it, some clever person can explain why it fixes it.

If I change the closeEvent method in PlotCanvas to the following (where I have marked the added lines with asterisks):

    def closeEvent(self, event):
        """ Delete figure on closing window. """
        self.canvas.ax.cla() # ****
        self.canvas.fig.clf() # ****
        plt.close(self.canvas.fig)
        super(PlotWindow, self).closeEvent(event) 

and change the plot2 method to the following:

    def plot2(self):
        """ Plot using regular MPL call. """
        fig = plt.figure()
        ax = fig.add_subplot(111)
        ax.plot([10, 20, 30], [4, 8, 2])
        plt.show()
        ax.cla() # ****
        fig.clf() # ****
        plt.close(fig)

I now get no multiple plots appearing. I would have thought that axes would be closed along with the figure container when it is closed, but apparently not. I would also hope that I would get the same behavior independent of platform but it seems the MacOSX backend acts differently to others.