当前位置: 首页 > 面试题库 >

如何使用Qthread通过PyQt更新Matplotlib图形?

徐欣德
2023-03-14
问题内容

我真的很难理解如何在PyQt中使用线程。我做了一个简单的示例,说明我想在UI中执行的操作。在下面的代码中,我希望用户输入一个股票行情自动收录器(例如,您可以输入“
bby”,“ goog”或“
v”)并绘制特定时期内的股票价值。问题是在更复杂的Ui中,或者很长一段时间UI冻结,而绘图正在更新。于是我做了一个“绘图仪”类更新时收到一定的信号(覆盖Qthread.run显然是不正确的做法情节你这样做是错误的)。我想让这个“绘图仪”在另一个线程之外运行。

一旦取消注释线程程序,程序就会停止工作。我试图移动新线程的启动以及“连接”,但没有任何反应。我认为即使阅读文档并查看Qt网站上的示例,我也不太了解Qthread的工作原理。

如果您知道如何执行此操作,将会很有帮助!(我正在使用Python 3.5和PyQt5)

from PyQt5.QtCore import *
from PyQt5.QtWidgets import *
from matplotlib.axes._subplots import Axes
from matplotlib.figure import Figure
from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg as FigureCanvas
import sys
from datetime import datetime, timedelta
import time
import quandl


class MyMplCanvas(FigureCanvas):
    """Ultimately, this is a QWidget (as well as a FigureCanvasAgg, etc.)."""
    send_fig = pyqtSignal(Axes, str, name="send_fig")

    def __init__(self, parent=None):
        self.fig = Figure()
        self.axes = self.fig.add_subplot(111)

        # We want the axes cleared every time plot() is called
        self.axes.hold(False)

        FigureCanvas.__init__(self, self.fig)
        self.setParent(parent)

        FigureCanvas.setSizePolicy(self, QSizePolicy.Expanding, QSizePolicy.Expanding)
        FigureCanvas.updateGeometry(self)

    def update_plot(self, axes):
        self.axes = axes
        self.draw()

class MainWindow(QMainWindow):
    send_fig = pyqtSignal(Axes, str, name="send_fig")

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

        self.main_widget = QWidget(self)
        self.myplot = MyMplCanvas(self.main_widget)
        self.editor = QLineEdit()
        self.display = QLabel("Vide")

        self.layout = QGridLayout(self.main_widget)
        self.layout.addWidget(self.editor)
        self.layout.addWidget(self.display)
        self.layout.addWidget(self.myplot)

        self.main_widget.setFocus()
        self.setCentralWidget(self.main_widget)

        self.move(500, 500)
        self.show()

        self.editor.returnPressed.connect(self.updatePlot)

        self.plotter = Plotter()
        self.send_fig.connect(self.plotter.replot)

        self.plotter.return_fig.connect(self.myplot.update_plot)


    def updatePlot(self):
        ticker = self.editor.text()
        self.editor.clear()
        self.display.setText(ticker)

        # thread = QThread()
        # self.plotter.moveToThread(thread)

        self.send_fig.emit(self.myplot.axes, ticker)

        # thread.start()


class Plotter(QObject):
    return_fig = pyqtSignal(Axes)

    @pyqtSlot(Axes, str)
    def replot(self, axes, ticker):  # A slot takes no params
        print(ticker)
        d = datetime.today() - timedelta(weeks=52)  # data from 1week ago
        data = quandl.get("YAHOO/"+ticker+".6", start_date=d.strftime("%d-%m-%Y"), end_date=time.strftime("%d-%m-%Y"))
        axes.plot(data)
        self.return_fig.emit(axes)


if __name__ == '__main__':
    app = QApplication(sys.argv)
    win = MainWindow()
    sys.exit(app.exec_())

问题答案:

第一个问题是,thread一旦启动,您将丢失对它的引用。要保留引用,请使用类变量,即self.thread代替thread

接下来,必须在执行任何操作之前启动线程。因此,您需要将self.thread.start()信号发射放在前面。

现在,它已经可以工作了,但是一旦您要启动新线程,就会出现下一个问题。因此,您需要先杀死旧的。由于旧的Plotter将无家可归,因此一个解决方案是每次要绘制时都创建一个新的Plotter以及一个新线程。这是下面的解决方案的工作方式。
另外,您也可以始终使用相同的绘图仪和线程。唯一要记住的是,始终只有一个工作程序(绘图仪)和一个线程,如果删除其中一个,则另一个很可悲。

为了测试它,我需要更改一些小东西,例如使用PyQt4而不是5并替换数据生成。这是工作代码。

from PyQt4.QtCore import *
from PyQt4.QtGui import *
from matplotlib.axes._subplots import Axes
from matplotlib.figure import Figure
from matplotlib.backends.backend_qt4agg import FigureCanvasQTAgg as FigureCanvas
import sys
from datetime import datetime, timedelta
import numpy as np



class MyMplCanvas(FigureCanvas):
    """Ultimately, this is a QWidget (as well as a FigureCanvasAgg, etc.)."""
    send_fig = pyqtSignal(Axes, str, name="send_fig")

    def __init__(self, parent=None):
        self.fig = Figure()
        self.axes = self.fig.add_subplot(111)

        # We want the axes cleared every time plot() is called
        self.axes.hold(False)

        FigureCanvas.__init__(self, self.fig)
        self.setParent(parent)

        FigureCanvas.setSizePolicy(self, QSizePolicy.Expanding, QSizePolicy.Expanding)
        FigureCanvas.updateGeometry(self)

    def update_plot(self, axes):
        self.axes = axes
        self.draw()

class MainWindow(QMainWindow):
    send_fig = pyqtSignal(Axes, str, name="send_fig")

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

        self.main_widget = QWidget(self)
        self.myplot = MyMplCanvas(self.main_widget)
        self.editor = QLineEdit()
        self.display = QLabel("Vide")

        self.layout = QGridLayout(self.main_widget)
        self.layout.addWidget(self.editor)
        self.layout.addWidget(self.display)
        self.layout.addWidget(self.myplot)

        self.main_widget.setFocus()
        self.setCentralWidget(self.main_widget)

        self.move(500, 500)
        self.show()

        self.editor.returnPressed.connect(self.updatePlot)

        # plotter and thread are none at the beginning
        self.plotter = None 
        self.thread = None



    def updatePlot(self):
        ticker = self.editor.text()
        self.editor.clear()
        self.display.setText(ticker)

        # if there is already a thread running, kill it first
        if self.thread != None and self.thread.isRunning():
            self.thread.terminate()

        # initialize plotter and thread
        # since each plotter needs its own thread
        self.plotter = Plotter()
        self.thread = QThread()
        # connect signals
        self.send_fig.connect(self.plotter.replot)
        self.plotter.return_fig.connect(self.myplot.update_plot)
        #move to thread and start
        self.plotter.moveToThread(self.thread)
        self.thread.start()
        # start the plotting
        self.send_fig.emit(self.myplot.axes, ticker)



class Plotter(QObject):
    return_fig = pyqtSignal(Axes)

    @pyqtSlot(Axes, str)
    def replot(self, axes, ticker):  # A slot takes no params
        print(ticker)
        d = datetime.today() - timedelta(weeks=52)  # data from 1week ago
        # do some random task
        data = np.random.rand(10000,10000)
        axes.plot(data.mean(axis=1))
        self.return_fig.emit(axes)


if __name__ == '__main__':
    app = QApplication(sys.argv)
    win = MainWindow()
    sys.exit(app.exec_())

这是提到的第二个选项的解决方案,即创建单个工作程序和线程,并在程序的整个运行时使用它们。

from PyQt4.QtCore import *
from PyQt4.QtGui import *
from matplotlib.figure import Figure
from matplotlib.backends.backend_qt4agg import FigureCanvasQTAgg as FigureCanvas
import sys
import numpy as np



class MyMplCanvas(FigureCanvas):

    def __init__(self, parent=None):
        self.fig = Figure()
        self.axes = self.fig.add_subplot(111)
        # plot empty line 
        self.line, = self.axes.plot([],[], color="orange")

        FigureCanvas.__init__(self, self.fig)
        self.setParent(parent)

        FigureCanvas.setSizePolicy(self, QSizePolicy.Expanding, QSizePolicy.Expanding)
        FigureCanvas.updateGeometry(self)


class MainWindow(QMainWindow):
    send_fig = pyqtSignal(str)

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

        self.main_widget = QWidget(self)
        self.myplot = MyMplCanvas(self.main_widget)
        self.editor = QLineEdit()
        self.display = QLabel("Vide")

        self.layout = QGridLayout(self.main_widget)
        self.layout.addWidget(self.editor)
        self.layout.addWidget(self.display)
        self.layout.addWidget(self.myplot)

        self.main_widget.setFocus()
        self.setCentralWidget(self.main_widget)
        self.show()

        # plotter and thread are none at the beginning
        self.plotter = Plotter()
        self.thread = QThread()

        # connect signals
        self.editor.returnPressed.connect(self.start_update)
        self.send_fig.connect(self.plotter.replot)
        self.plotter.return_fig.connect(self.plot)
        #move to thread and start
        self.plotter.moveToThread(self.thread)
        self.thread.start()

    def start_update(self):
        ticker = self.editor.text()
        self.editor.clear()
        self.display.setText(ticker)
        # start the plotting
        self.send_fig.emit(ticker)


    # Slot receives data and plots it
    def plot(self, data):
        # plot data
        self.myplot.line.set_data([np.arange(len(data)), data])
        # adjust axes
        self.myplot.axes.set_xlim([0,len(data) ])
        self.myplot.axes.set_ylim([ data.min(),data.max() ])
        self.myplot.draw()


class Plotter(QObject):
    return_fig = pyqtSignal(object)

    @pyqtSlot(str)
    def replot(self, ticker):
        print(ticker)
        # do some random task
        data = np.random.rand(10000,10000)
        data = data.mean(axis=1)
        self.return_fig.emit(data)


if __name__ == '__main__':
    app = QApplication(sys.argv)
    win = MainWindow()
    sys.exit(app.exec_())


 类似资料:
  • 问题内容: 我读了这篇文章《如何真正地,真正地使用QThreads》。完整说明,它说而不是子类qthread和重新实现run(),应使用moveToThread(QThread *)使用moveToThread将QObject推送到QThread实例上 这是C ++示例,但我不知道如何将其转换为python代码。 我一直在使用这种方法来生成qthread,但是如您所见,它使用的是不推荐的方式。我如

  • 本文向大家介绍PyQt 线程类 QThread使用详解,包括了PyQt 线程类 QThread使用详解的使用技巧和注意事项,需要的朋友参考一下 PyQt中的线程类 QtCore.QThread ,使用时继承QThread类 启动界面的线程暂称为UI线程。界面执行命令时都在自己的UI线程中。 如果在UI线程中执行网络连接和数据库操作等耗时的操作,界面会被卡住,Windows下有可能会出现“无响应”的

  • 问题内容: 嗨,我已经发送到GUI的线程工作程序链接,并直接从QThread更新GUI信息。像这儿: 在主要我只是添加 这样的解决方案在PyQt5中非常不好吗?我是PyQt的新手。谢谢。如果我的解决方案不好,请帮助我修复它。 问题答案: 您不能也不能从辅助线程更新GUI,必须通过信号和插槽来完成: 为此,我们将通过class创建一个信号,并将指示参数的类型,然后将其连接到所需的插槽,如下所示: 线

  • 问题内容: 我在这里重新绘制图形时遇到问题。我允许用户在时间刻度(x轴)中指定单位,然后重新计算并调用此函数plots()。我希望该图仅进行更新,而不是将另一个图附加到该图上。 问题答案: 你基本上有两个选择: 精确执行当前操作,但在重新配置数据之前先致电和。这是最慢但最简单,最可靠的选择。 除了重新绘制外,你还可以更新绘图对象的数据。你需要在代码中进行一些更改,但这比每次重新绘制都快得多。但是,

  • 问题内容: 我正在尝试学习如何在PyQt Gui应用程序中使用QThreads。我有一些可以运行一段时间的东西,(通常)可以在其中更新Gui的点,但是我想将主要工作拆分为自己的线程(有时东西会卡住,最终有一个取消/重试按钮,如果Gui被冻结(因为主循环被阻塞),则该按钮显然不起作用)。 我已阅读https://mayaposch.wordpress.com/2011/11/01/how-to-re

  • 我正在尝试使用PyQt4中的matplotlib绘图库来绘制日志绘图。我在类中添加了两行代码: 但后来轴消失了,下面出现了一个错误。我想问您如何使用PyQt4中的matplotlib库来绘制loglot plot。如下图所示。Python 2.7。5,matplotlib 1.3。0,PyQt 4.10。4.