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

发布于 2021-01-29 14:09:58

我真的很难理解如何在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_())
关注者
0
被浏览
304
1 个回答
  • 面试哥
    面试哥 2021-01-29
    为面试而生,有面试问题,就找面试哥。

    第一个问题是,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_())
    


知识点
面圈网VIP题库

面圈网VIP题库全新上线,海量真题题库资源。 90大类考试,超10万份考试真题开放下载啦

去下载看看