简体   繁体   中英

Dynamically update matplotlib canvas in a pyqt5 interface

I have a very simple program and the structure must be preserved (it is a condition of the exercise).

We start with an interface formed by a button and a Canvas, like shown in the figure above.

在此处输入图片说明

Once the button is clicked, a background task is initiated, which calls a function called animation . In here we start a process that generates random data every time waiting_time . We want to update the plot everytime there is a new x and y variable.

The code is the following:

from PyQt5 import QtCore, QtWidgets

from mplwidget import MplWidget
import threading
import time
import numpy as np
import sys

class RandomDataGeneration():
    """
    Mandatory Class. This Class must exist.
    """
    def __init__(self):
        pass

    def data_generation(self):

        while True:
            waiting_time = np.random.randint(1,4) # waiting time is given by a random number.
            print(waiting_time)
            time.sleep(waiting_time)
            self.x = np.random.rand(10)
            self.y = np.random.rand(10)
            print(self.x)
            print(self.y)
            #self.update_plot()

    def update_plot(self):

        self.MplWidget.canvas.axes.clear()
        self.MplWidget.canvas.axes.set_title('GRAPH')
        self.MplWidget.canvas.axes.plot(x, y, marker='.', linestyle='')
        self.MplWidget.canvas.axes.legend(('random'), loc='upper right')
        self.MplWidget.canvas.draw()


def animation():
    """
    This function initiates the RandomDataGeneration
    """
    app = RandomDataGeneration()
    app.data_generation()


class Ui_MainWindow():

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

    def start_download(self):

        download_info = threading.Thread(target=animation)
        download_info.start()

    def setupUi(self, MainWindow):
        MainWindow.setObjectName("MainWindow")
        MainWindow.resize(1280, 1024)
        self.centralwidget = QtWidgets.QWidget(MainWindow)
        self.centralwidget.setObjectName("centralwidget")
        self.pushButton = QtWidgets.QPushButton(self.centralwidget)
        self.pushButton.setGeometry(QtCore.QRect(880, 80, 221, 32))
        self.pushButton.setObjectName("pushButton")
        self.MplWidget = MplWidget(self.centralwidget)
        self.MplWidget.setGeometry(QtCore.QRect(49, 39, 771, 551))
        self.MplWidget.setObjectName("MplWidget")
        MainWindow.setCentralWidget(self.centralwidget)
        self.retranslateUi(MainWindow)
        QtCore.QMetaObject.connectSlotsByName(MainWindow)

    def retranslateUi(self, MainWindow):
        _translate = QtCore.QCoreApplication.translate
        MainWindow.setWindowTitle(_translate("MainWindow", "MainWindow"))
        self.pushButton.setText(_translate("MainWindow", "PushButton"))

        self.pushButton.clicked.connect(self.start_download)


if __name__ == "__main__":
    app = QtWidgets.QApplication(sys.argv)
    MainWindow = QtWidgets.QMainWindow()
    ui = Ui_MainWindow()
    ui.setupUi(MainWindow)
    MainWindow.show()
    sys.exit(app.exec_())

To run the code is necessary to have in the same folder the following code with name mplwidget.py .

# ------------------------------------------------------
# -------------------- mplwidget.py --------------------
# ------------------------------------------------------
from PyQt5.QtWidgets import*

from matplotlib.backends.backend_qt5agg import FigureCanvas

from matplotlib.figure import Figure


class MplWidget(QWidget):

    def __init__(self, parent = None):

        QWidget.__init__(self, parent)

        self.canvas = FigureCanvas(Figure())

        vertical_layout = QVBoxLayout()
        vertical_layout.addWidget(self.canvas)

        self.canvas.axes = self.canvas.figure.add_subplot(111)
        self.setLayout(vertical_layout)

Since you are creating a pyqt5 application I guess your best bet is to use a QThread for the blocking part and emit a signal every time new data is generated. One way would be to make RandomDataGeneration a subclass of QThread and implement run , eg

class RandomDataGeneration(QtCore.QThread):
    """
    Mandatory Class. This Class must exist.
    """
    new_data = QtCore.pyqtSignal()

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

    def data_generation(self):

        while True:
            waiting_time = np.random.randint(1,4) # waiting time is given by a random number.
            print(waiting_time)
            time.sleep(waiting_time)
            self.x = np.random.rand(10)
            self.y = np.random.rand(10)
            print(self.x)
            print(self.y)
            self.new_data.emit()

    def run(self):
        self.data_generation()

To use the thread you could subclass QMainWindow and create an instance of RandomDataGeneration in there. Subclassing QMainWindow has an additional advantage that you can move the gui setup and the signal-slot connections in there as well, eg

class MyMainWindow(QtWidgets.QMainWindow):

    def __init__(self):
        super().__init__()
        self.ui = Ui_MainWindow()
        self.ui.setupUi(self)

        self.download_thread = RandomDataGeneration(self)
        self.download_thread.new_data.connect(self.plot_data)
        self.ui.pushButton.clicked.connect(self.start_download)

    def start_download(self):
        if not self.download_thread.isRunning():
            self.download_thread.start()

    def plot_data(self):
        self.ui.MplWidget.update_plot(self.download_thread.x, self.download_thread.y)

The main part then becomes

if __name__ == "__main__":
    app = QtWidgets.QApplication(sys.argv)
    MainWindow = MyMainWindow()
    MainWindow.show()
    sys.exit(app.exec_())

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM