使用PyQt和matplotlib快速重绘

时间:2015-09-21 15:00:37

标签: python matplotlib pyqt blit

我正在尝试使用实时数据可视化创建GUI-app。

我想出的解决方案是使用众所周知的技巧进行部分重绘:

fig, axes = plt.subplots(nrows=2, ncols=1)

axes[0].set_{labels, limits}
axes[1].set_{labels, limits}    

fig.show()
fig.canvas.draw()
N = 2
graph_0 = axes[0].scatter([] * N, [] * N, s=[np.pi*5**2] * N, animated=True)
graph_1 = axes[1].scatter([] * N, [] * N, s=[np.pi*5**2] * N, animated=True)
bg_0 = fig.canvas.copy_from_bbox(axes[0].bbox)
bg_1 = fig.canvas.copy_from_bbox(axes[1].bbox)

while True:

    # Acquire new data
    tmp = [1, 2]  # *For example*

    # Clear plots
    fig.canvas.restore_region(bg_0)
    fig.canvas.restore_region(bg_1)

    # Set data to be visualized
    graph_0.set_offsets([(i+1, d) for i, d in enumerate(tmp)])
    graph_1.set_offsets([(i+1, d) for i, d in enumerate(tmp)])

    # Redraw
    axes[0].draw_artist(graph_0)
    axes[1].draw_artist(graph_1)
    fig.canvas.blit(axes[0].bbox)
    fig.canvas.blit(axes[1].bbox)

这个解决方案没问题,一切正常。

之后我尝试使用相同的方法但使用PyQt后端(还添加一些控制元素)。

以下是代码:

import sys
from PyQt4 import QtGui
from PyQt4.uic import loadUiType

import numpy as np
import matplotlib
import matplotlib.style
import matplotlib.pyplot as plt
from matplotlib.backends.backend_qt4agg import (
    FigureCanvasQTAgg as FigureCanvas)

Ui_MainWindow, QMainWindow = loadUiType('dialog.ui')


class Main(QMainWindow, Ui_MainWindow):
    def __init__(self, ):
        super(Main, self).__init__()
        self.setupUi(self)

        self.layout_mpl = QtGui.QVBoxLayout()
        self.widget_mpl.setLayout(self.layout_mpl)

        # Assign callbacks
        self.button.clicked.connect(self.update_figure)
        self.prepare_figure()

    def prepare_figure(self):
        self.fig, self.axes = plt.subplots(nrows=2, ncols=1)
        self.canvas = FigureCanvas(self.fig)
        self.layout_mpl.addWidget(self.canvas)
        # self.canvas.draw()

        self.axes[0].set_{labels, limits}
        self.axes[1].set_{labels, limits}    

        self.graph = [None] * 2
        N = 5
        self.graph[0] = self.axes[0].scatter(
            [] * N, [] * N, s=[np.pi*5**2] * N, animated=True)
        self.graph[1] = self.axes[1].scatter(
            [] * N, [] * N, s=[np.pi*5**2] * N, animated=True)

        self.canvas.draw()
        self.bg = self.canvas.copy_from_bbox(self.fig.bbox)

        # self.bg = [None] * 2
        # self.bg[0] = self.canvas.copy_from_bbox(self.fig.axes[0].bbox)
        # self.bg[1] = self.canvas.copy_from_bbox(self.fig.axes[1].bbox)


    def update_figure(self):
        # self.canvas.restore_region(self.bg[0])
        # self.canvas.restore_region(self.bg[1])
        self.canvas.restore_region(self.bg)   

        # Set data to be visualized
        self.graph[0].set_offsets([(i+1, d) for i, d in
                                   enumerate(np.random.rand(5)*100+100)])
        self.graph[1].set_offsets([(i+1, d) for i, d in
                                   enumerate(np.random.rand(5)*100+100)])

        # Redraw
        self.axes[0].draw_artist(self.graph[0])
        self.axes[1].draw_artist(self.graph[1])

        self.canvas.blit(self.axes[0].bbox)
        self.canvas.blit(self.axes[1].bbox)


if __name__ == '__main__':
    app = QtGui.QApplication(sys.argv)
    main = Main()
    main.show()
    sys.exit(app.exec_())

您可能会看到我在评论行下使用的不同调用组合。

因此,我希望每次点击button时都会更新这些图。它实际上有效,除了一个阻塞问题:

a)申请刚刚开始

enter image description here

b)按钮已被点击一次

enter image description here

c)将焦点切换到另一个窗口并返回(此图是带偏移的图,但点放在旧坐标中)

enter image description here

有人可以给出任何指示吗?

提前致谢!

使用的版本:Python 3.4.3,matplotlib 1.4.3,PyQt 4.10.4。

2 个答案:

答案 0 :(得分:2)

问题是你在完全重新调整尺寸之前的某个时刻抓住背景,以便最终在屏幕上结束。你需要做的是

  • 使用draw_idle让Qt决定何时实际触发完整重绘
  • 使用mpl事件堆栈在绘制后抓取背景

只是相关的方法:

    def prepare_figure(self):
        self.fig = Figure()
        self.axes = self.fig.add_subplot(111)

        self.axes.set_xlim((0, 6))
        self.axes.set_xlabel('seq.index')
        self.axes.set_ylim((0, 100))
        self.axes.set_ylabel('observation')

        N = 5
        self.graph = self.axes.scatter(
            [] * N, [] * N, s=[np.pi*5**2] * N,
            color='seagreen', animated=True)

        self.canvas = FigureCanvas(self.fig)
        # w = self.widget_mpl.width()
        # h = self.widget_mpl.height()
        # self.canvas.setFixedSize(w, h)

        self.layout_mpl.addWidget(self.canvas)

        # hook up to mpls event handling framework for draw events
        # this is emitted after the canvas has finished a full redraw
        self.canvas.mpl_connect('draw_event', self._draw_event)

        # ask the canvas to kindly draw it self some time in the future
        # when Qt thinks it is convenient
        self.canvas.draw_idle()

    def _draw_event(self, evt):
        # after drawing, grab the new background
        self.bg = self.canvas.copy_from_bbox(self.axes.bbox)

查看animation模块中的blitting代码,了解完成此项工作所需的全部功能(还有一个重新调整大小的事件),并且在重新绘制力之后,您将需要重新启动最后的数据。

答案 1 :(得分:0)

解决方案是不断检查和更新axes.bbox尺寸:

http://matplotlib.org/1.3.1/examples/old_animation/animation_blit_qt4.html