Matplotlib与PyQt动态绘图 - 窗口冻结

1 投票
3 回答
3277 浏览
提问于 2025-05-10 15:35

目标:

我想在一个pyQt4的窗口里嵌入一个Matplotlib的图表。这个图表需要实时更新。

问题:

窗口在绘图完成之前会一直卡住。我希望图表能够实时更新。

背景:

我们有一个数值算法正在处理一些数据,我想让图表显示这个算法是如何影响数据集的。这个算法大约每0.5秒完成一次迭代——图表必须在每次迭代时更新。

测试代码:

算法被替换成了test(),这个函数会随机绘制一个点,重复100次。下面的代码展示了存在的问题:

import sys
from PlotGUI import *
import threading
from random import randint
import time

class GUIForm(QtGui.QDialog):

    def __init__(self, parent=None):
        QtGui.QWidget.__init__(self,parent)
        self.ui = Ui_Dialog()
        self.ui.setupUi(self)
        QtCore.QObject.connect(self.ui.pushButton, QtCore.SIGNAL('clicked()'), self.startSim)
        self.cPlot = None # custom plotter
        self.instantiatePlot()        

    def instantiatePlot(self):
        self.cPlot = CustomPlotter(self.ui.widget.canvas) 
        self.cPlot.prepareFigure()

    def startSim(self):
        self.cPlot.clear();        
        draw_thread = threading.Thread(target=self.cPlot.test())
        draw_thread.start()

class CustomPlotter():
    def __init__(self, canvas):
        print 'constructor'
        self.canvas = canvas        

    def prepareFigure(self):
        ax = self.canvas.ax

        ax.set_ylim([-1,101])
        #ax.set_xlim([dt[0],dt[1]])
        ax.set_ylim([-1, 10])
        self.canvas.draw()

    def clear(self):
        self.canvas.ax.clear()

    def test(self):
        canvas = self.canvas 
        ax = canvas.ax
        for x in range(0,100):
            y = randint(0,9)
            ax.plot(x, y, 'ro')
            print x
            canvas.draw()
            time.sleep(1)
            #canvas.show()
            #canvas.update()

if __name__ == "__main__":
    app = QtGui.QApplication(sys.argv)
    myapp = GUIForm()
    myapp.show()
    sys.exit(app.exec_())

提前感谢。这是为了做一些原型设计,所以我对所有能提供快速解决方案的选项/替代方案都很感兴趣。

相关文章:

  • 暂无相关问题
暂无标签

3 个回答

0

来自matplotlib图库的embedding_in_qt4.py示例应该已经足够了,对吧?

# ...
class MyMplCanvas(FigureCanvas):
    """Ultimately, this is a QWidget (as well as a FigureCanvasAgg, etc.)."""

    def __init__(self, parent=None, width=5, height=4, dpi=100):
        fig = Figure(figsize=(width, height), dpi=dpi)
        self.axes = fig.add_subplot(111)
        # We want the axes cleared every time plot() is called
        self.axes.hold(False)

        self.compute_initial_figure()

        #
        FigureCanvas.__init__(self, fig)
        self.setParent(parent)

        FigureCanvas.setSizePolicy(self,
                                   QtGui.QSizePolicy.Expanding,
                                   QtGui.QSizePolicy.Expanding)
        FigureCanvas.updateGeometry(self)

    def compute_initial_figure(self):
        pass
# ...
class MyDynamicMplCanvas(MyMplCanvas):
    """A canvas that updates itself every second with a new plot."""

    def __init__(self, *args, **kwargs):
        MyMplCanvas.__init__(self, *args, **kwargs)
        timer = QtCore.QTimer(self)
        timer.timeout.connect(self.update_figure)
        timer.start(1000)

    def compute_initial_figure(self):
        self.axes.plot([0, 1, 2, 3], [1, 2, 0, 4], 'r')

    def update_figure(self):
        # Build a list of 4 random integers between 0 and 10 (both inclusive)
        l = [random.randint(0, 10) for i in range(4)]

        self.axes.plot([0, 1, 2, 3], l, 'r')
        self.draw()
# ...
1

当你尝试创建一个新的线程时,出现了一个错误:

draw_thread = threading.Thread(target=self.cPlot.test())

这段代码会立即在当前线程中执行测试方法,然后把结果(None)作为target传递。你可能想要做的是:

draw_thread = threading.Thread(target=self.cPlot.test)

Thread(target=None) 只是创建了一个什么都不做的线程,它会立刻退出,所以这是有效的,不会产生任何异常来提示这个问题。

因为test()方法是在GUI线程中启动的,所以在这个方法返回之前,图形界面会被阻塞,无法响应其他操作。

1

在使用PySide或Qt的时候,不能从另一个线程更新界面上的小部件(比如图片或显示的内容)。也就是说,你不能在一个线程里直接去操作界面上的元素。

可以看看matplotlib.animation.FuncAnimation这个功能。详细信息可以访问这个链接:http://matplotlib.org/api/animation_api.html

import numpy as np

# ========== Matplotlib [PySide] ==========
import matplotlib
matplotlib.use("Qt4Agg")
matplotlib.rcParams["backend.qt4"] = "PySide"

import matplotlib.animation as mplanimation
from matplotlib.figure import Figure
from matplotlib.backends.backend_qt4agg import FigureCanvasQTAgg as FigureCanvas

class InteractivePlotWidget(FigureCanvas):

    def __init__(self):
        super().__init__(Figure(tight_layout=True))
        self.axes = self.figure.add_subplot(111)

        self.setSizePolicy(QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Expanding)

        self.name = ""
        self.data = np.zeros(shape=(100, 2))
    # end __init__

    def plot_data(self, interval=0):
        data = np.array([(i, np.sin(i)) for i in range(interval)])
        try:
            self.axes.lines[0].set_data(data[:,0], data[:,1])
        except IndexError:
            self.axes.plot(data, label=self.name) # Lots of overhead. Do once.

        self.axes.relim()
        self.axes.autoscale_view(True, True, True)
        return self.axes.lines  # animation handles draw
        # manually trigger draw
        # self.draw_idle()
        # self.flush_events()
    # end plot_data
# end class InteractivePlotWidget

if __name__ == "__main__":
    QtGui.QApplication([])

    w = InteractivePlotWidget()
    w.show()

    # Create and start the animation (timer)
    anim = mplanimation.FuncAnimation(w.figure, w.plot_data, interval=0)
    anim._start()

    sys.exit(QtGui.qApp.exec_())

撰写回答