使用Matplotlib和PyQt动态绘图 - 冻结窗口

时间:2015-11-19 14:25:30

标签: python qt matplotlib pyqt pyqt4

目标:

我想在pyQt4 GUI窗口中嵌入一个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 :(得分:1)

尝试实例化新线程时出错:

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

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

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

Thread(target=None)只创建一个不执行任何操作的线程,只是立即退出,因此是有效的,并且不会产生任何表明此问题的异常。

因为test()方法是在GUI线程中启动的,所以GUI被阻塞,直到方法返回。

答案 1 :(得分:1)

PySide / Qt无法从另一个线程更新小部件(QPixmap /显示项)。您不能直接在另一个线程中调用GUI项目的绘图。

请参阅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_())

答案 2 :(得分: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()
# ...