33

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?

Trenton McKinney
  • 56,955
  • 33
  • 144
  • 158
Blink
  • 1,444
  • 5
  • 17
  • 25
  • 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 Jan 21 '15 at 18:44
  • @MylesBaker Here is an example of what it would look like (for the code above): http://media.giphy.com/media/3xz2BD48KS3fOGzAJ2/giphy.gif – Blink Jan 21 '15 at 18:48
  • 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 Jan 21 '15 at 18:51
  • 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 Jan 21 '15 at 18:53
  • Are you aware of matplotlib's `animation.Animate`? It can also save a movie. – tom10 Jan 21 '15 at 19:04
  • @tom10 I've tried the packages in the past and they've been more of a headache then they're worth. The final output needs to be a gif, so I figured doing frames + concatenating would be quickest. I will take another look at it; thanks! – Blink Jan 21 '15 at 19:08
  • See http://matplotlib.org/examples/animation/index.html Also, if you have imagemagik installed, mpl will save strait to a gif. – tacaswell Jan 22 '15 at 05:42

1 Answers1

58

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

Joe Kington
  • 275,208
  • 71
  • 604
  • 463
  • 2
    `set_data` solution is so elegant. Wish I could upvote 10 times. – Sibbs Gambling Aug 02 '17 at 00:53
  • 1
    If saving as video instead of .gif then `ani.save('test.mp4', writer='ffmpeg', codec='h264')` should replace the last line. If you want to find out which codecs are available then run `ffmpeg -codec` in the terminal. Given that you want to use ffmpeg as the writer. – JMDE Nov 09 '18 at 17:30
  • 1
    Getting this error `ValueError: Invalid file object: <_io.BufferedReader name=8> – Chaitanya Bapat Mar 02 '19 at 05:18
  • 3
    @JoeKington - This might be a silly question, but can I ask where your `num` arg is coming from? (`def update(num, x, y, line)`). I don't see it listed in `fargs=[x, y, line]`. Thanks! – S3DEV Apr 05 '20 at 21:03
  • @Joe can you add a dot ahead of the line? I am drawing a line shape in 3d and I want to put a head in front of the line using the last element of the each list, x[-1], y[-1], z[-1]> – Joonho Park Apr 07 '21 at 09:44