Search code examples
pythonmatplotlibplotblit

Live plotting nicely with blit or Animation from matplotlib


Trying to be short and fast . I have a scatter plot I want to do in a for loop in live. So I have this :

import numpy as np
import matplotlib.pyplot as plt
from matplotlib.animation import FuncAnimation
import random
import time

x = range(1000,12000,100)
sample = random.sample(range(110),110)

plt.style.use("seaborn")
t_start = time.time()
for idx,measurement in enumerate(sample):
    plt.scatter(x[idx],measurement , color='black')
    print('Mean Frame Rate: %.3gFPS' % ((idx + 1) / (time.time() - t_start)))
    plt.pause(0.05)

plt.show()

It is actually working but it's getting slower and slower when I get to high x . And actually it's not very aesthetic for a live plot of measurement i think.

I know people uses blit() or animation.FuncAnimation() but honestly I tried and think it's not working in my case with only this kind of data .

[EDIT]

When i say it's slowing down when I get to high x , my fps is droping from 30 at start to 5-6 at the end . I read here super_solution the same problem but couldn't apply the solution ...


Solution

  • most of the linked code relies on things not needing to be redrawn like the axis or that the number of lines and points on screen remain unchanged.

    in your case the axis change and all the points change their positions every second and new points are added, this means matplotlib has to rasterize the entire screen on each frame.

    one thing you can do to speed up this rasterization is to reduce the number of "axes" that you have to "refresh" on every frame, by lumping scatter points in 1 call.

    import numpy as np
    import matplotlib
    matplotlib.use('QTAgg')
    import matplotlib.pyplot as plt
    from matplotlib.animation import FuncAnimation
    import random
    import time
    
    x = range(1000,12000,100)
    sample = random.sample(range(110),110)
    xs = np.array(x)
    ys = np.array(sample)
    
    plt.style.use("seaborn")
    s = None
    t_start = time.time()
    for idx,measurement in enumerate(sample):
        t1 = time.perf_counter()
        if s is not None:
            s.remove()  # remove all scatter points
        s = plt.scatter(xs[:idx],ys[:idx] , color='black')  # new scatter contains all points
        t2 = time.perf_counter()
        plt.pause(max(0.0001, 0.05-(t2-t1)))
        print('Mean Frame Rate: %.3gFPS' % (1/(time.perf_counter()-t1)))
    
    plt.show()
    

    the frame rate is always consistent at 20 fps, but this has its limits, practically only a few thousands points can be drawn each frame before the frame-rate starts decreasing below 20 fps.

    sleep is actually unreliable for getting a certain fps as it will sleep for at least the required duration, you should have the sleep interval corrected to get an exact framerate.