Home > Back-end >  PyQt5 and matplotlib, graph only updates when screen gets maximized or minimized
PyQt5 and matplotlib, graph only updates when screen gets maximized or minimized

Time:12-25

This a simple MRE of one section of my code, thank you in advance if you can help me. If you are willing to correct some mistakes or write this piece of code in a better way, please feel free to do so.

import sys
import random
import matplotlib
import matplotlib.pyplot as plt
from PyQt5 import QtCore, QtWidgets
from matplotlib.backends.backend_qt5agg import (
   FigureCanvasQTAgg as FigureCanvas,
   NavigationToolbar2QT)
from matplotlib.figure import Figure

matplotlib.use('Qt5Agg')


class MainWindow(QtWidgets.QMainWindow):

   def __init__(self):
       super().__init__()

       self.resize(500, 500)
       button = QtWidgets.QPushButton('Press me')
       self.widget_central = QtWidgets.QStackedWidget()
       self.widget_central.addWidget(button)
       self.setCentralWidget(self.widget_central)

       button.clicked.connect(self.screen)

   def screen(self):

       s = _screen()

       self.widget_central.addWidget(s)
       self.widget_central.setCurrentWidget(s)


class _screen(QtWidgets.QWidget):

   def __init__(self):
       super().__init__()

       lay = QtWidgets.QHBoxLayout()
       groupbox = QtWidgets.QGroupBox()
       lay_2 = QtWidgets.QVBoxLayout()
       splitter = QtWidgets.QSplitter(QtCore.Qt.Horizontal)

       self.slider = QtWidgets.QSlider()
       self.slider.setFocusPolicy(QtCore.Qt.NoFocus)
       self.slider.setGeometry(30, 40, 200, 30)
       self.slider.setRange(0, 100)
       self.slider.setValue(100)
       self.slider.setInvertedAppearance(True)
       self.slider.setTickPosition(QtWidgets.QSlider.TicksBelow)
       self.slider.setTickInterval(1)

       x_axis = list(range(10))
       y_axis = [random.randint(0, 10) for i in range(10)]

       self.area_plot = MplCanvas(self, width=5, height=4, dpi=100)
       toolbar = NavigationToolbar2QT(self.area_plot, self)
       self.area_plot.figure, self.ax = plt.subplots()
       self.ax.plot(x_axis, y_axis)

       lay_2.addWidget(toolbar)
       lay_2.addWidget(self.area_plot)
       groupbox.setLayout(lay_2)
       splitter.addWidget(self.slider)
       splitter.addWidget(groupbox)

       lay.addWidget(splitter)

       self.setLayout(lay)

       self.slider.valueChanged[int].connect(self.graph_uptade)

   def graph_uptade(self, value):

       QtWidgets.QApplication.processEvents()
       x_axis = list(range(10))
       y_axis = [value * random.randint(0, 10) for i in range(10)]

       self.ax.cla()
       self.ax.plot(x_axis, y_axis, 'r')
       self.area_plot.figure.canvas.draw_idle()


class MplCanvas(FigureCanvas):

   def __init__(self, parent=None, width=5, height=4, dpi=100):
       fig = Figure(figsize=(width, height), dpi=dpi)
       self.axes = fig.add_subplot(111)
       super(MplCanvas, self).__init__(fig)


app = QtWidgets.QApplication(sys.argv)
w = MainWindow()
w.show()
app.exec_()

There is a QSlider whose range varies from 0 to 100, every time the slider is activated the value is sent to a function that updates the graph (a matplotlib Figure) besed on the QSlider value. The graph actually updates and the program doesn't crash, nevertheless the changes are not visible in the main screen forthwith, to see the new plot I need to maximize the window, and if I update the graph again while maximized I'll have to minimaze the window to see the updated version and so on.

I've tried to use things like QApplication.ProcessEvents(), plt.cla() plt.plot() plt.draw(), aldo plt.draw_idle(), and some other things, but to no avail. Nothing seems to work for me in this regard.

I'd appreciate if you could help me.

CodePudding user response:

Your problem is that you defined the subplot using matplotlib.pyplot (plt.).

self.area_plot.figure, self.ax = plt.subplots() <-- here

If you register the subplot on your defined figure (and clean the code a bit up), it should work as expected:

import sys
import random
import matplotlib
import matplotlib.pyplot as plt
from PyQt5 import QtCore, QtWidgets
from matplotlib.backends.backend_qt5agg import (
   FigureCanvasQTAgg as FigureCanvas,
   NavigationToolbar2QT)
from matplotlib.figure import Figure

matplotlib.use('Qt5Agg')

class MainWindow(QtWidgets.QMainWindow):

   def __init__(self):
       super().__init__()

       self.resize(500, 500)
       button = QtWidgets.QPushButton('Press me')
       self.widget_central = QtWidgets.QStackedWidget()
       self.widget_central.addWidget(button)
       self.setCentralWidget(self.widget_central)

       button.clicked.connect(self.screen)

   def screen(self):

       s = _screen()

       self.widget_central.addWidget(s)
       self.widget_central.setCurrentWidget(s)


class _screen(QtWidgets.QWidget):

   def __init__(self):
       super().__init__()

       lay = QtWidgets.QHBoxLayout()
       groupbox = QtWidgets.QGroupBox()
       lay_2 = QtWidgets.QVBoxLayout()
       splitter = QtWidgets.QSplitter(QtCore.Qt.Horizontal)

       self.slider = QtWidgets.QSlider()
       self.slider.setFocusPolicy(QtCore.Qt.NoFocus)
       self.slider.setGeometry(30, 40, 200, 30)
       self.slider.setRange(0, 100)
       self.slider.setValue(100)
       self.slider.setInvertedAppearance(True)
       self.slider.setTickPosition(QtWidgets.QSlider.TicksBelow)
       self.slider.setTickInterval(1)

       x_axis = list(range(10))
       y_axis = [random.randint(0, 10) for i in range(10)]

       self.area_plot = MplCanvas(self, width=5, height=4, dpi=100)
       toolbar = NavigationToolbar2QT(self.area_plot, self)
       self.area_plot.figure
       self.area_plot.figure.clf() #remove the initial axis labels
       self.ax = self.area_plot.figure.add_subplot(111) #add subplot, retrieve axis object
       self.ax.plot(x_axis, y_axis)

       lay_2.addWidget(toolbar)
       lay_2.addWidget(self.area_plot)
       groupbox.setLayout(lay_2)
       splitter.addWidget(self.slider)
       splitter.addWidget(groupbox)

       lay.addWidget(splitter)

       self.setLayout(lay)

       self.slider.valueChanged[int].connect(self.graph_uptade)

   def graph_uptade(self, value):

       QtWidgets.QApplication.processEvents()
       x_axis = list(range(10))
       y_axis = [value * random.randint(0, 10) for i in range(10)]
       
       self.ax.cla()
       self.ax.plot(x_axis, y_axis, 'r')
       self.area_plot.figure.canvas.draw_idle()


class MplCanvas(FigureCanvas):

   def __init__(self, parent=None, width=5, height=4, dpi=100):
       fig = Figure(figsize=(width, height), dpi=dpi)
       self.axes = fig.add_subplot(111)
       super(MplCanvas, self).__init__(fig)


app = QtWidgets.QApplication(sys.argv)
w = MainWindow()
w.show()
app.exec_()

CodePudding user response:

You're overwriting the figure with the subplots(), if you want to update the axes you can just use those of the current figure.

Change this line:

        self.area_plot.figure, self.ax = plt.subplots()

To this:

        self.ax = self.area_plot.figure.axes[0]
  • Related