gpt4 book ai didi

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

转载 作者:行者123 更新时间:2023-12-02 04:45:20 24 4
gpt4 key购买 nike

我真的很难理解如何在 PyQt 中使用线程。我做了一个简单的例子来说明我想在用户界面中做什么。在下面的代码中,我希望用户输入股票行情(例如,您可以输入“bby”、“goog”或“v”)并绘制特定时期内股票的值(value)。问题是在更复杂的用户界面中,或者在很长一段时间内,用户界面在绘图更新时卡住。所以我制作了一个“绘图仪”类,当它收到某个信号时更新绘图(覆盖 Qthread.run 显然不是正确的方法 you're doing it wrong )。我想让这个“绘图仪”在主线程之外的另一个线程中运行。

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

如果您知道如何执行此操作,那将会有很大帮助! (我正在使用 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_())

最佳答案

第一个问题是,一旦线程启动,您就会丢失对线程的引用。要保留引用,请使用类变量,即 self.thread 而不是 thread

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

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

为了测试它,我需要更改一些小东西,例如使用 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_())

关于python - 如何使用 Qthread 通过 PyQt 更新 Matplotlib 图?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/41156260/

24 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com