在 pyqt5 界面中动态更新 matplotlib 画布

Posted

技术标签:

【中文标题】在 pyqt5 界面中动态更新 matplotlib 画布【英文标题】:Dynamically update matplotlib canvas in a pyqt5 interface 【发布时间】:2019-08-19 18:06:29 【问题描述】:

我有一个非常简单的程序,必须保留结构(这是练习的条件)。

我们从一个由按钮和 Canvas 组成的界面开始,如上图所示。

单击按钮后,将启动一个后台任务,该任务调用一个名为animation 的函数。在这里,我们开始一个每次waiting_time 生成随机数据的过程。 我们想在每次有新的 x 和 y 变量时更新绘图。

代码如下:

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_())

要运行代码,必须在同一个文件夹中包含名称为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)

【问题讨论】:

允许更改哪些位?例如,您是否允许更改 RandomDataGeneration 的实现? 原则上,我们可以更改RandomDataGeneration中除了data_generation之外的所有内容。 update_plot() 已经是我的“贡献”了。 【参考方案1】:

由于您正在创建一个 pyqt5 应用程序,我猜您最好的选择是使用 QThread 作为阻塞部分,并在每次生成新数据时发出一个信号。一种方法是使RandomDataGeneration 成为QThread 的子类并实现run,例如

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()

要使用线程,您可以继承QMainWindow 并在其中创建RandomDataGeneration 的实例。子类化QMainWindow 有一个额外的优势,您可以在其中移动 gui 设置和信号槽连接,例如

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)

主要部分就变成了

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

【讨论】:

以上是关于在 pyqt5 界面中动态更新 matplotlib 画布的主要内容,如果未能解决你的问题,请参考以下文章

在发送 http 请求时让 PyQt5 标签动态更新

Pyqt+matplotlib 实现实时画图案例

PyQt5快速入门PyQt5布局管理

Python GUI界面开发环境配置:Pycharm+PyQt5

根据从导入包中打印的标准输出更新 PyQt 进度条(PyQt5)

从 Matplotlib 图中提取信息并将其显示在 PyQt5 GUI 中