Search code examples
pythonmatplotlibpyqtblit

Fast redrawing with PyQt and matplotlib


I'm trying to create GUI-app with real-time data visualization.

The solution I came up with is to use well-known trick with partial redrawing:

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)

This solution is OK and everything works fine.

After that I tried to implement the same approach but using PyQt backend (to add also some control elements).

Here is the code:

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_())

You might see different combinations of calls I used under commented lines.

So, I expect on every click of button the plots to be updated. It actually works, except one blocking issue:

a) Application just started

enter image description here

b) Button has been clicked once

enter image description here

c) Switched focus to another window and back (this plot is the one with offset, but the points are placed in old coordinates)

enter image description here

Could someone give any directions, please?

Thanks in advance!

Versions used: Python 3.4.3, matplotlib 1.4.3, PyQt 4.10.4 .


Solution

  • The solution is to constanly check and update axes.bbox sizes:

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