我正在使用 PyQt4 和 matplotlib 创建一个实时数据监视器 GUI 来创建绘图。 GUI 一次显示多个绘图(大约 6 或 7 个)。为了给 GUI 线程更多的时间和稍微更好的响应时间,我只执行绘图 canvas.draw() 和我在更新绘图数据的线程中执行的所有其他绘图命令。因此,在非 GUI 线程中,我执行诸如 line.set_ydata、ax.set_ylim 和其他可能需要更新的命令。
两个线程可以通过在初始化时传递给两个线程的字典访问图形和 Canvas 对象。当非 GUI 线程获取数据并更新绘图时,它会通知 GUI 线程使用 Qts 信号(自动连接)重绘 Canvas 。我的线程经验告诉我,我应该使用锁或确保非 GUI 线程以某种方式在重绘时被阻塞,但在我的编码匆忙中,我从来没有把它放进去,直到现在才忘记它。这种情况的关键是我想看到情节的每次更新,而不是在其他线程更新的中间重绘,甚至错过更新(如果这有意义的话)。目前,我认为我只是幸运地把握了时机,事情似乎进展顺利。
另一件可能有帮助的事情是,我通过使用 moveToThread 将 QObject 移动到 QThread 来创建线程。
我的问题是:
- 我只是走运还是 Qt 只是在做一些神奇的事情?
- 在 matplotlib canvas/figure 上完成分块的最佳方法是什么?
我可能应该注意到,这是我第一次尝试使 GUI 响应更快(将 matplotlib 命令移到数据线程中),我可能会转向 blit 动画风格的绘图,只更新绘图中发生变化的部分。但我仍然很好奇我到底有多幸运。
感谢您的帮助。
更新/澄清/评论的延续:我希望整个监控系统能够由可能只熟悉 matlab 和 matplotlib 的科学家轻松更改/更新。我并不完全反对改用 pyqwt 来提高速度。就每秒帧数而言,我根本不需要太多,因为标称的每 0.5 秒(最快 0.2 秒)才绘制一次数据。 GUI 响应能力似乎“吃掉了**”,因为有太多的情节。我已经用 matplotlib blitting 对我的代码进行了概念验证,它似乎有很大帮助,如果需要,pyqwt 将会发生。我之前的问题仍然有效。
请您参考如下方法:
我偶然发现了类似的问题。我有很多图要画 (~250),所以我的 GUI 窗口最终会显示为卡在 Windows 中。 我修改了图形类以作为单独的线程执行绘图。 结果 - 我的 GUI 不再挂起,绘图窗口在绘图完成后显示。要使用它,您需要创建 PlotDialog 实例并使用 plot_data 参数调用 draw_plots 方法。 plot_data 是一个字典列表,每个字典代表子图。每个子图都有以下键(和相应的数据):title、xlabel、ylabel 和data。希望这是有道理的。
这是我的代码:
import math
from PyQt4 import QtCore, QtGui
from matplotlib.figure import Figure
from matplotlib.backends.backend_qt4agg import FigureCanvasQTAgg as FigureCanvas
from matplotlib.backends.backend_qt4agg import NavigationToolbar2QT as NavigationToolbar
class MyFigure(Figure, QtCore.QThread):
def __init__(self, parent, *args, **kwargs):
QtCore.QThread.__init__(self, parent)
Figure.__init__(self, *args, **kwargs)
self.plot_data = list()
def start_plotting_thread(self, plot_data, on_finish=None):
""" Start plotting """
self.plot_data = plot_data
if on_finish is not None:
self.finished.connect(on_finish)
self.start()
def run(self):
""" Run as a thread """
# Figure out rows and columns
total_plots = len(self.plot_data)
columns = int(math.sqrt(total_plots))
if columns < 1:
columns = 1
rows = int(total_plots / columns)
if (total_plots % columns) > 0:
rows += 1
if rows < 1:
rows = 1
# Plot Data
for plot_index, _plot_data in enumerate(self.plot_data):
plot_number = plot_index + 1
args = (rows, columns, plot_number)
kwargs = {
'title': _plot_data['title'],
'xlabel': _plot_data['xlabel'],
'ylabel': _plot_data['ylabel']
}
figure = self.add_subplot(*args, **kwargs)
figure.plot(_plot_data['data'])
class PlotDialog(QtGui.QDialog):
def __init__(self, parent):
super(PlotDialog, self).__init__(parent, QtCore.Qt.WindowMinMaxButtonsHint | QtCore.Qt.WindowCloseButtonHint)
self.figure = MyFigure(self)
self.canvas = FigureCanvas(self.figure)
self.toolbar = NavigationToolbar(self.canvas, self)
self.layout = QtGui.QGridLayout()
self.setLayout(self.layout)
layout = [
[self.canvas],
[self.toolbar],
]
for row_index, columns in enumerate(layout):
if type(columns) is list:
for column_index, widget in enumerate(columns):
if widget is not None:
self.layout.addWidget(widget, row_index, column_index)
def draw_plots(self, plot_data):
""" Plot Plots """
self.figure.start_plotting_thread(plot_data, on_finish=self.finish_drawing_plots)
def finish_drawing_plots(self):
""" Finish drawing plots """
self.canvas.draw()
self.show()
