Keep Sharp

Update Matplotlib Plot

This tip is about how to update matplotlib plot, it is based on this great tutorial: Speeding up Matplotlib

I learned two ways of updating matplotlib plot, both require first manually change the content of objects that to be updated. For example, lineObj.set_ydata() for line object, vlinesObj.set_paths() for vlines object. After that, you can do either of the following two things to update the plot:

  • simpler but slower: use canvas.draw() to redraw every objects inside the canvas.

  • faster but require more knowledge of how things work underhood: since canvas.draw() may do some repetitive work of redrawing some objects like axis, legends which may don't need to be updated, there should be many ways to lower the amount of work in this updation step if you are a master of matplotlib. The way i learned is first restore canvas from a prestored background using canvas.restore_region(), and then only update the objects i want to update using ax.draw_artist(obj) rather than a whole canvas, and at last use canvas.update() to update the canvas.

Test:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
#! /usr/bin/env python

import matplotlib
import matplotlib.pyplot as plt
matplotlib.style.use("ggplot")

import numpy as np
import time

def newfig(l):
    fig, ax = plt.subplots(figsize=(10,8))
    fig.show()
    fig.tight_layout()

    #obj, = ax.plot(np.random.rand(l))  # normal line plot
    obj = ax.vlines(np.arange(l), [0], np.ones(l), alpha=0.5, linewidths=400./l)   # vlines return line collection

    obj.remove() # if using restore from bg method, bg should not contain lines; comment this line if use canvas.draw() method
    fig.canvas.draw()
    bg = fig.canvas.copy_from_bbox(ax.bbox)

    return fig, ax, obj, bg


class LiveFig:

    def __init__(self, l):
        self.fig, self.ax, self.obj, self.bg = newfig(l)

    def update(self, h):
        ## update data
        #self.obj.set_ydata(h)  # for normal line plot

        segs = np.zeros((len(h), 2, 2)) # for vlines
        segs[:, :, 0] = np.arange(len(h))[:, np.newaxis]
        segs[:, 1, 1] = h
        self.obj.set_paths(segs)

        ############################################################
        ## way1: update canvas by default, if use this then should not do obj.remove() above

        #  self.fig.canvas.draw()
        ############################################################

        ############################################################
        ## way2: update canvas partially, faster but fig cannot be resized, if use this
        ## then should do obj.remove() above before saving background

        self.fig.canvas.restore_region(self.bg)
        self.ax.draw_artist(self.obj)
        self.fig.canvas.update()
        ############################################################


        self.fig.canvas.flush_events()  # entering Qt event loop, both methods require this


if __name__ == "__main__":
    l1 = 300
    fig1 = LiveFig(l1)

    l2 = 400
    fig2 = LiveFig(l2)

    tstart = time.time()
    numfrm = 0
    tdelta = 10
    while time.time() - tstart < tdelta:
        h1 = np.random.rand(l1)
        fig1.update(h1)

        h2 = np.random.rand(l2)
        fig2.update(h2)

        numfrm += 1

    print "fps: ", numfrm / tdelta
    raw_input("Press any key to exit.")

Example:

Gist updateMatplotlibPlot.py

Comments