Skip to main content
 首页 » 编程设计

python之matplotlib 和 pyqt4 : update plot in one thread, 画在另一个

2025年05月04日20telwanggs

我正在使用 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()