如何在PyQt5和matplotlib中同时处理和显示数据
How to process data and display it simultaneously in PyQt5 and matplotlib
我正在尝试扩展 here 中关于如何将 matplotlib
图集成到 PyQt5 window 中的示例,并在应用程序 运行 中更新它。
我对上面链接的代码所做的更改是情节更新时的条件,即我向应用程序添加了一个按钮,该按钮链接到一个名为 simulate
的函数,而不是计时器并运行 for
循环。在循环内部,我们调用 update_plot
函数,其他一切保持不变。
def simulate(self, ydata):
for k in trange(0, 500, 10):
tmp = np.random.uniform(0, 1, size=(2, 1000))
self.xdata = tmp[0]
self.ydata = tmp[1]
self.update_plot()
完整代码为
import sys
from PyQt5.QtWidgets import QVBoxLayout, QSizePolicy, QWidget, QPushButton, QHBoxLayout, QSpacerItem
from PyQt5 import QtWidgets
from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg as FigureCanvas
from matplotlib.figure import Figure
import random
import numpy as np
from time import sleep
import sys
import matplotlib
matplotlib.use('Qt5Agg')
from matplotlib.figure import Figure
class MplCanvas(FigureCanvas):
def __init__(self, parent=None, width=5, height=4, dpi=100):
fig = Figure(figsize=(width, height), dpi=dpi)
self.axes = fig.add_subplot(111)
super(MplCanvas, self).__init__(fig)
class MainWindow(QtWidgets.QMainWindow):
def __init__(self):
super(MainWindow, self).__init__()
widget = QWidget(self)
self.setCentralWidget(widget)
vlay = QVBoxLayout(widget)
hlay = QHBoxLayout()
vlay.addLayout(hlay)
pybutton = QPushButton('Fit!', self)
pybutton.clicked.connect(self.simulate)
hlay2 = QHBoxLayout()
hlay2.addWidget(pybutton)
hlay2.addItem(QSpacerItem(1000, 10, QSizePolicy.Expanding))
vlay.addLayout(hlay2)
self.canvas = MplCanvas(self, width=5, height=4, dpi=100)
vlay.addWidget(self.canvas)
self.n_data = 999
self.xdata = list(range(self.n_data))
self.ydata = [random.uniform(0, 1) for i in range(self.n_data)]
# We need to store a reference to the plotted line
# somewhere, so we can apply the new data to it.
self._plot_ref = None
self.update_plot()
self.show()
def simulate(self):
for k in range(0, 5):
print(k)
tmp = np.random.uniform(0, 1, size=(2,self.n_data))
self.xdata = tmp[0]
self.ydata = tmp[1]
self.update_plot()
sleep(0.5)
def update_plot(self):
# Drop off the first y element, append a new one.
#self.ydata = self.ydata[1:] + [random.randint(0, 10)]
# Note: we no longer need to clear the axis.
if self._plot_ref is None:
plot_refs = self.canvas.axes.plot(self.xdata, self.ydata, 'r')
self._plot_ref = plot_refs[0]
else:
self._plot_ref.set_ydata(self.ydata)
# Trigger the canvas to update and redraw.
self.canvas.draw()
if __name__ == "__main__":
app = QtWidgets.QApplication(sys.argv)
w = MainWindow()
app.exec_()
现在发生的事情是,无论何时单击按钮 self.simulate()
都会被调用,因此 for
循环开始......正如您在函数中看到的那样,我调用 self.update_plot
每次,但显示的图只有在循环结束后才会更新,即函数完成...
有人能解释一下这是怎么回事吗?
我认为 Qt 只在主事件循环空闲时才刷新显示。因此,当您使用 运行 simulate
方法时,它无法刷新。快速修复是在 update_plot()
.
之后添加 app.processEvents()
如果您有多个 GUI 项目需要经常更新,那么 processEvents 技巧将不会很好地发挥作用,您将不得不开始研究线程。特别是 QThreads,因为它们被设计为与 Qt 配合得很好。
[编辑:使用 QThread:]
使用 QThread 的代码如下。我借用了 this answer and this answer 的方法。设置线程有很多开销。而且您必须 非常 小心处理数据。将数据传入和传出线程和工作者的首选方法是使用槽和信号。还有信号量系统,我没用过
虽然这段代码很乏味,但它提供了一个高性能的 GUI。当 simulate
为 运行 时,您可以在屏幕上移动 window,情节将继续更新。
import sys
from PyQt5.QtWidgets import (QVBoxLayout, QSizePolicy, QWidget, QPushButton,
QHBoxLayout, QSpacerItem)
from PyQt5 import QtWidgets
from PyQt5.QtCore import QThread, QObject, pyqtSignal, pyqtSlot
from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg as FigureCanvas
from matplotlib.figure import Figure
import random
import numpy as np
from time import sleep
import sys
import matplotlib
matplotlib.use('Qt5Agg')
from matplotlib.figure import Figure
class Worker(QObject):
dataReady = pyqtSignal(object)
finished = pyqtSignal()
def __init__(self, n_data):
super().__init__()
self.n_data = n_data
def simulate(self):
for k in range(0, 5):
print(k)
tmp = np.random.uniform(0, 1, size=(2,self.n_data))
self.dataReady.emit(tmp)
sleep(0.5)
self.finished.emit()
class MplCanvas(FigureCanvas):
def __init__(self, parent=None, width=5, height=4, dpi=100):
fig = Figure(figsize=(width, height), dpi=dpi)
self.axes = fig.add_subplot(111)
super(MplCanvas, self).__init__(fig)
class MainWindow(QtWidgets.QMainWindow):
def __init__(self):
super(MainWindow, self).__init__()
widget = QWidget(self)
self.setCentralWidget(widget)
vlay = QVBoxLayout(widget)
hlay = QHBoxLayout()
vlay.addLayout(hlay)
pybutton = QPushButton('Fit!', self)
pybutton.clicked.connect(self.simulate)
hlay2 = QHBoxLayout()
hlay2.addWidget(pybutton)
hlay2.addItem(QSpacerItem(1000, 10, QSizePolicy.Expanding))
vlay.addLayout(hlay2)
self.canvas = MplCanvas(self, width=5, height=4, dpi=100)
vlay.addWidget(self.canvas)
self.n_data = 999
self.xdata = list(range(self.n_data))
self.ydata = [random.uniform(0, 1) for i in range(self.n_data)]
# We need to store a reference to the plotted line
# somewhere, so we can apply the new data to it.
self._plot_ref = None
self.update_plot([self.xdata, self.ydata])
self.show()
def simulate(self):
# now simulate from MainWindow will call run simulate in Worker
self.thread = QThread()
self.worker = Worker(n_data=self.n_data)
self.worker.dataReady.connect(self.update_plot)
self.worker.moveToThread(self.thread)
self.worker.finished.connect(self.thread.quit)
self.thread.started.connect(self.worker.simulate)
self.thread.start()
@pyqtSlot(object)
def update_plot(self, data):
# Drop off the first y element, append a new one.
#self.ydata = self.ydata[1:] + [random.randint(0, 10)]
print('got new data')
xdata = data[0]
ydata = data[1]
# Note: we no longer need to clear the axis.
if self._plot_ref is None:
plot_refs = self.canvas.axes.plot(xdata, ydata, 'r')
self._plot_ref = plot_refs[0]
else:
self._plot_ref.set_ydata(ydata)
# Trigger the canvas to update and redraw.
self.canvas.draw()
if __name__ == "__main__":
app = QtWidgets.QApplication(sys.argv)
w = MainWindow()
app.exec_()
我正在尝试扩展 here 中关于如何将 matplotlib
图集成到 PyQt5 window 中的示例,并在应用程序 运行 中更新它。
我对上面链接的代码所做的更改是情节更新时的条件,即我向应用程序添加了一个按钮,该按钮链接到一个名为 simulate
的函数,而不是计时器并运行 for
循环。在循环内部,我们调用 update_plot
函数,其他一切保持不变。
def simulate(self, ydata):
for k in trange(0, 500, 10):
tmp = np.random.uniform(0, 1, size=(2, 1000))
self.xdata = tmp[0]
self.ydata = tmp[1]
self.update_plot()
完整代码为
import sys
from PyQt5.QtWidgets import QVBoxLayout, QSizePolicy, QWidget, QPushButton, QHBoxLayout, QSpacerItem
from PyQt5 import QtWidgets
from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg as FigureCanvas
from matplotlib.figure import Figure
import random
import numpy as np
from time import sleep
import sys
import matplotlib
matplotlib.use('Qt5Agg')
from matplotlib.figure import Figure
class MplCanvas(FigureCanvas):
def __init__(self, parent=None, width=5, height=4, dpi=100):
fig = Figure(figsize=(width, height), dpi=dpi)
self.axes = fig.add_subplot(111)
super(MplCanvas, self).__init__(fig)
class MainWindow(QtWidgets.QMainWindow):
def __init__(self):
super(MainWindow, self).__init__()
widget = QWidget(self)
self.setCentralWidget(widget)
vlay = QVBoxLayout(widget)
hlay = QHBoxLayout()
vlay.addLayout(hlay)
pybutton = QPushButton('Fit!', self)
pybutton.clicked.connect(self.simulate)
hlay2 = QHBoxLayout()
hlay2.addWidget(pybutton)
hlay2.addItem(QSpacerItem(1000, 10, QSizePolicy.Expanding))
vlay.addLayout(hlay2)
self.canvas = MplCanvas(self, width=5, height=4, dpi=100)
vlay.addWidget(self.canvas)
self.n_data = 999
self.xdata = list(range(self.n_data))
self.ydata = [random.uniform(0, 1) for i in range(self.n_data)]
# We need to store a reference to the plotted line
# somewhere, so we can apply the new data to it.
self._plot_ref = None
self.update_plot()
self.show()
def simulate(self):
for k in range(0, 5):
print(k)
tmp = np.random.uniform(0, 1, size=(2,self.n_data))
self.xdata = tmp[0]
self.ydata = tmp[1]
self.update_plot()
sleep(0.5)
def update_plot(self):
# Drop off the first y element, append a new one.
#self.ydata = self.ydata[1:] + [random.randint(0, 10)]
# Note: we no longer need to clear the axis.
if self._plot_ref is None:
plot_refs = self.canvas.axes.plot(self.xdata, self.ydata, 'r')
self._plot_ref = plot_refs[0]
else:
self._plot_ref.set_ydata(self.ydata)
# Trigger the canvas to update and redraw.
self.canvas.draw()
if __name__ == "__main__":
app = QtWidgets.QApplication(sys.argv)
w = MainWindow()
app.exec_()
现在发生的事情是,无论何时单击按钮 self.simulate()
都会被调用,因此 for
循环开始......正如您在函数中看到的那样,我调用 self.update_plot
每次,但显示的图只有在循环结束后才会更新,即函数完成...
有人能解释一下这是怎么回事吗?
我认为 Qt 只在主事件循环空闲时才刷新显示。因此,当您使用 运行 simulate
方法时,它无法刷新。快速修复是在 update_plot()
.
app.processEvents()
如果您有多个 GUI 项目需要经常更新,那么 processEvents 技巧将不会很好地发挥作用,您将不得不开始研究线程。特别是 QThreads,因为它们被设计为与 Qt 配合得很好。
[编辑:使用 QThread:] 使用 QThread 的代码如下。我借用了 this answer and this answer 的方法。设置线程有很多开销。而且您必须 非常 小心处理数据。将数据传入和传出线程和工作者的首选方法是使用槽和信号。还有信号量系统,我没用过
虽然这段代码很乏味,但它提供了一个高性能的 GUI。当 simulate
为 运行 时,您可以在屏幕上移动 window,情节将继续更新。
import sys
from PyQt5.QtWidgets import (QVBoxLayout, QSizePolicy, QWidget, QPushButton,
QHBoxLayout, QSpacerItem)
from PyQt5 import QtWidgets
from PyQt5.QtCore import QThread, QObject, pyqtSignal, pyqtSlot
from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg as FigureCanvas
from matplotlib.figure import Figure
import random
import numpy as np
from time import sleep
import sys
import matplotlib
matplotlib.use('Qt5Agg')
from matplotlib.figure import Figure
class Worker(QObject):
dataReady = pyqtSignal(object)
finished = pyqtSignal()
def __init__(self, n_data):
super().__init__()
self.n_data = n_data
def simulate(self):
for k in range(0, 5):
print(k)
tmp = np.random.uniform(0, 1, size=(2,self.n_data))
self.dataReady.emit(tmp)
sleep(0.5)
self.finished.emit()
class MplCanvas(FigureCanvas):
def __init__(self, parent=None, width=5, height=4, dpi=100):
fig = Figure(figsize=(width, height), dpi=dpi)
self.axes = fig.add_subplot(111)
super(MplCanvas, self).__init__(fig)
class MainWindow(QtWidgets.QMainWindow):
def __init__(self):
super(MainWindow, self).__init__()
widget = QWidget(self)
self.setCentralWidget(widget)
vlay = QVBoxLayout(widget)
hlay = QHBoxLayout()
vlay.addLayout(hlay)
pybutton = QPushButton('Fit!', self)
pybutton.clicked.connect(self.simulate)
hlay2 = QHBoxLayout()
hlay2.addWidget(pybutton)
hlay2.addItem(QSpacerItem(1000, 10, QSizePolicy.Expanding))
vlay.addLayout(hlay2)
self.canvas = MplCanvas(self, width=5, height=4, dpi=100)
vlay.addWidget(self.canvas)
self.n_data = 999
self.xdata = list(range(self.n_data))
self.ydata = [random.uniform(0, 1) for i in range(self.n_data)]
# We need to store a reference to the plotted line
# somewhere, so we can apply the new data to it.
self._plot_ref = None
self.update_plot([self.xdata, self.ydata])
self.show()
def simulate(self):
# now simulate from MainWindow will call run simulate in Worker
self.thread = QThread()
self.worker = Worker(n_data=self.n_data)
self.worker.dataReady.connect(self.update_plot)
self.worker.moveToThread(self.thread)
self.worker.finished.connect(self.thread.quit)
self.thread.started.connect(self.worker.simulate)
self.thread.start()
@pyqtSlot(object)
def update_plot(self, data):
# Drop off the first y element, append a new one.
#self.ydata = self.ydata[1:] + [random.randint(0, 10)]
print('got new data')
xdata = data[0]
ydata = data[1]
# Note: we no longer need to clear the axis.
if self._plot_ref is None:
plot_refs = self.canvas.axes.plot(xdata, ydata, 'r')
self._plot_ref = plot_refs[0]
else:
self._plot_ref.set_ydata(ydata)
# Trigger the canvas to update and redraw.
self.canvas.draw()
if __name__ == "__main__":
app = QtWidgets.QApplication(sys.argv)
w = MainWindow()
app.exec_()