如何使用Qthread通过PyQt更新Matplotlib图形? [英] How to use a Qthread to update a Matplotlib figure with PyQt?

查看:46
本文介绍了如何使用Qthread通过PyQt更新Matplotlib图形?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

I'm really having a hard time to understand how to use Threads in PyQt. I made a simple example of what I would like to do in my UI. In the code you can see below, I want the user to enter a stock ticker (you can enter "bby", "goog" or "v" for example) and plot the value of the stock over a certain period. The thing is in more complex Ui or for a long period of time the UI freeze while the plot is beeing updated. So I made a "Plotter" class that update the plot when it receives a certain signal(overriding Qthread.run apparently was not the right way you're doing it wrong). I'd like to make this "Plotter" run in another thread than the main.

一旦我取消注释线程行,程序就会停止工作.我试图移动新线程的启动以及连接",但没有任何效果.我想即使在阅读了 文档 之后,我也不太了解 Qthread 的工作原理并查看 Qt 网站上的示例.

As soon as I uncomment the thread lines the program stops working. I've tried to move the launch of the new thread and also the "connect" but nothing is working. I think I'm not understanding well how Qthread works even after reading the documentation and looking at the examples on the Qt website.

如果您知道如何做,将会有很大帮助!(我正在使用 Python 3.5 和 PyQt5)

If any af you know how to do this it would help a lot! (I'm working with Python 3.5 and 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.

The first problem is that you lose the reference to thread once it's started. To keep a reference use a class variable, i.e. self.thread instead of thread.

接下来,必须在执行任何操作之前启动线程.所以你需要把self.thread.start()放在信号发射的前面.

Next, the thread has to be started before doing anything. So you need to put self.thread.start() in front of the signal emission.

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

Now, it would work already, but a next problem occurs once you want to start a new thread. So, you need to first kill the old one. Since the old Plotter would then be homeless, a solution is to create a new Plotter as well as a new thread each time you want to plot. This is the way the solution below works.
Alternatively, you could also always use the same plotter and thread. The only thing to remember is that there is always exactly one worker (plotter) and one thread, if you delete one of them, the other is sad.

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

In order to test it, I needed to change some small things, like using PyQt4 instead of 5 and replace the data generation. Here is the working code.

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

<小时>

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


Here is a solution for the second option mentionned, i.e. create a single worker and a thread and use those throughout the program's runtime.

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

这篇关于如何使用Qthread通过PyQt更新Matplotlib图形?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆