Search code examples
pythonmatplotlibpyqtqtimer

How to use QTimer to update a matplotlib FigureCanvas defined in another class?


I am using python3 with PyQt5 and matplotlib FigureCanvasQTAgg. I need to update a plot every 1 second, so I use QTimer. The timer works properly, but the plot is not updated. Here is the code:

main.py

import UI_action
from PyQt5.QtWidgets import QApplication
import sys

if __name__ == '__main__':
    app = QApplication(sys.argv)
    main_window = UI_action.Connection()
    main_window.show()

    sys.exit(app.exec_())

UI_action.py

import UI_layout_test
from PyQt5.QtWidgets import QWidget
import mplwidget


class Connection(QWidget):
    def __init__(self):
        super(Connection, self).__init__()
        self.my_widget_ui = UI_layout_test.Ui_MainWindow()
        self.my_widget_ui.setupUi(self)
        self.plot = mplwidget.Plot()
        self.my_widget_ui.pushButton_start.clicked.connect(self.__slot_start)


def __slot_start(self):
    self.plot.plot()

UI_layout_test.py (generated by Qt Designer)

from PyQt5 import QtCore, QtGui, QtWidgets

class Ui_MainWindow(object):
    def setupUi(self, MainWindow):
        MainWindow.setObjectName("MainWindow")
        MainWindow.resize(692, 602)
        self.centralwidget = QtWidgets.QWidget(MainWindow)
        self.centralwidget.setObjectName("centralwidget")
        self.widget_fig = MplWidget(self.centralwidget)
        self.widget_fig.setGeometry(QtCore.QRect(30, 20, 631, 481))
        self.widget_fig.setObjectName("widget_fig")
        self.pushButton_start = QtWidgets.QPushButton(self.centralwidget)
        self.pushButton_start.setGeometry(QtCore.QRect(550, 520, 113, 32))
        self.pushButton_start.setObjectName("pushButton_start")

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

    def retranslateUi(self, MainWindow):
        _translate = QtCore.QCoreApplication.translate
        self.pushButton_start.setText(_translate("MainWindow", "Start"))

from mplwidget import MplWidget

mplwidget.py

from PyQt5 import QtWidgets
from PyQt5 import QtCore
import matplotlib
matplotlib.use('Qt5Agg')

from matplotlib.figure import Figure
from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg as FigureCanvas
from numpy import *


class MplCanvas(FigureCanvas):
    def __init__(self):
        self.fig = Figure(tight_layout=True)
        DPI = self.fig.get_dpi()
        self.fig.set_size_inches(850.0 / float(DPI), 720.0 / float(DPI))
        self.axis = self.fig.add_subplot(111, facecolor='white')

        self.axis.grid('on')
        self.axis.set_xlim(0, 10)
        self.axis.set_ylim(59.94, 60.06)
        self.axis.set_xlabel('Time (s)', fontsize=13)
        self.axis.set_ylabel('Frequency (Hz)', fontsize=13)

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


class MplWidget(QtWidgets.QWidget):
    def __init__(self, parent=None):
        QtWidgets.QWidget.__init__(self, parent)   # Inherit from QWidget
        self.canvas = MplCanvas()                  # Create canvas object
        self.vbl = QtWidgets.QVBoxLayout()         # Set box for plotting
        self.vbl.addWidget(self.canvas)
        self.setLayout(self.vbl)


class Plot(MplWidget):
    def __init__(self):
        MplWidget.__init__(self)
        self.i = 0
        self.timer = QtCore.QTimer()
        self.timer.timeout.connect(self.plot)
        self.data = [59.98, 59.98, 59.99, 60, 60, 60, 60, 60]
        print('initialization complete')

    def plot(self):
        if self.i <= 8:
            self.canvas.axis.plot(self.data[:self.i], '*-')
            self.canvas.draw()
            print(self.data[:self.i])
            self.i += 1
            self.timer.start(1000)

The output is:

initialization complete
[]
[59.98]
[59.98, 59.98]
[59.98, 59.98, 59.99]
[59.98, 59.98, 59.99, 60]
[59.98, 59.98, 59.99, 60, 60]
[59.98, 59.98, 59.99, 60, 60, 60]
[59.98, 59.98, 59.99, 60, 60, 60, 60]
[59.98, 59.98, 59.99, 60, 60, 60, 60, 60]

Based on the output, the function plot has been called and the timer did its job. However, there was no plot shown on the figure. How do I fix this? Thanks!


Solution

  • There are two instances of MplWidget in the programme.

    1. The first is the self.widget_fig = MplWidget(self.centralwidget) from the UI_layout_test. This is the one which is shown in the UI, but it is not used anywhere further in the code.
    2. The second is the one created through self.plot = Plot() inside Connection. This the one which is updated via the timer, but it is never actually added to the widget. Hence you do not see any of the changes applied to it.

    Solution: Decide for one those and remove the other.