28
votes

I want to produce a set of frames that can be used to animate a plot of a growing line. In the past, I have always used plt.draw() and set_ydata() to redraw the y-data as it changed over time. This time, I wish to draw a "growing" line, moving across the graph with time. Because of this, set_ydata doesn't work (xdata is changing length). For example,

import numpy as np
import matplotlib.pyplot as plt

x = np.linspace(0, 10, 100)
y = np.sin(x)

plt.figure()
for n in range(len(x)):
    plt.plot(x[:n], y[:n], color='k')
    plt.axis([0, 10, 0, 1])
    plt.savefig('Frame%03d.png' %n)

While this works, it becomes very slow as it scales. Is there a faster way to do this?

1
Do you have a sample (link) to a graphic you like you're trying to recreate? There are a bunch of animation packages in Python you can use. - Myles Baker
@MylesBaker Here is an example of what it would look like (for the code above): media.giphy.com/media/3xz2BD48KS3fOGzAJ2/giphy.gif - Blink
Do you want to update the graph limits as more data is exposed? (I.e. redraw the graph)? Or is your domain and range known? - Myles Baker
Domain and range are known. Preferable I would like to set the figure, axis, and labels, then just update the data. If it was possible to just add the newest data point and connect them, that would be fine too, e.g., plot(x[n-1,n], y[n-1,n], color='k'). - Blink
Are you aware of matplotlib's animation.Animate? It can also save a movie. - tom10

1 Answers

44
votes

A couple of notes:

First off, the reason that things become progressively slower is that you're drawing more and more and more overlapping lines in the same position.

A quick fix is to clear the plot each time:

import numpy as np
import matplotlib.pyplot as plt

x = np.linspace(0, 10, 100)
y = np.sin(x)

plt.figure()
for n in range(len(x)):
    plt.cla()
    plt.plot(x[:n], y[:n], color='k')
    plt.axis([0, 10, 0, 1])
    plt.savefig('Frame%03d.png' %n)

Better yet, however, update both the x and y data at the same time:

import numpy as np
import matplotlib.pyplot as plt

x = np.linspace(0, 10, 100)
y = np.sin(x)

fig, ax = plt.subplots()
line, = ax.plot(x, y, color='k')

for n in range(len(x)):
    line.set_data(x[:n], y[:n])
    ax.axis([0, 10, 0, 1])
    fig.canvas.draw()
    fig.savefig('Frame%03d.png' %n)

And if you'd like to use the animation module (side note: blit=True may not work properly on some backends (e.g. OSX), so try blit=False if you have issues):

import numpy as np
import matplotlib.pyplot as plt
import matplotlib.animation as animation

x = np.linspace(0, 10, 100)
y = np.sin(x)

fig, ax = plt.subplots()
line, = ax.plot(x, y, color='k')

def update(num, x, y, line):
    line.set_data(x[:num], y[:num])
    line.axes.axis([0, 10, 0, 1])
    return line,

ani = animation.FuncAnimation(fig, update, len(x), fargs=[x, y, line],
                              interval=25, blit=True)
ani.save('test.gif')
plt.show()

enter image description here