Animation with FuncAnimation¶
The matplotlib.animation module provides tools for creating animated visualizations. FuncAnimation is the primary class for creating animations by repeatedly calling a function.
Basic FuncAnimation¶
Minimal Example¶
import matplotlib.pyplot as plt
import matplotlib.animation as animation
import numpy as np
fig, ax = plt.subplots()
x = np.linspace(0, 2 * np.pi, 100)
line, = ax.plot(x, np.sin(x))
ax.set_ylim(-1.5, 1.5)
def update(frame):
"""Update function called for each frame."""
line.set_ydata(np.sin(x + frame * 0.1))
return line,
ani = animation.FuncAnimation(
fig=fig,
func=update,
frames=100,
interval=50, # milliseconds between frames
blit=True
)
plt.show()
Understanding the Components¶
# 1. Create figure and initial plot
fig, ax = plt.subplots()
line, = ax.plot([], []) # Empty line to update
# 2. Define init function (optional but recommended)
def init():
"""Set up the initial empty frame."""
ax.set_xlim(0, 2 * np.pi)
ax.set_ylim(-1.5, 1.5)
line.set_data([], [])
return line,
# 3. Define update function
def update(frame):
"""Update the plot for each frame."""
x = np.linspace(0, 2 * np.pi, 100)
y = np.sin(x + frame * 0.1)
line.set_data(x, y)
return line,
# 4. Create animation
ani = animation.FuncAnimation(
fig=fig,
func=update,
init_func=init,
frames=200,
interval=20,
blit=True
)
Key Parameters¶
| Parameter | Description | Default |
|---|---|---|
fig |
Figure to animate | Required |
func |
Function called each frame | Required |
frames |
Number of frames or iterable | None |
init_func |
Initialization function | None |
interval |
Delay between frames (ms) | 200 |
blit |
Optimize drawing | False |
repeat |
Loop animation | True |
repeat_delay |
Delay before repeat (ms) | 0 |
The Update Function¶
The update function receives the frame number (or value from frames iterable) and must return an iterable of artists to redraw.
Frame as Integer¶
def update(frame):
# frame is 0, 1, 2, ..., frames-1
line.set_ydata(np.sin(x + frame * 0.1))
return line,
Frame from Iterable¶
# Use custom frame values
frames = np.linspace(0, 4 * np.pi, 200)
def update(phase):
# phase comes from the frames iterable
line.set_ydata(np.sin(x + phase))
return line,
ani = animation.FuncAnimation(fig, update, frames=frames, interval=20)
Returning Multiple Artists¶
def update(frame):
line1.set_ydata(np.sin(x + frame * 0.1))
line2.set_ydata(np.cos(x + frame * 0.1))
title.set_text(f'Frame: {frame}')
return line1, line2, title
Blitting for Performance¶
Blitting redraws only the changed parts, significantly improving performance.
# With blit=True:
# - init_func must return all artists to animate
# - update must return all artists that changed
# - Artists must have set_animated(True) called
def init():
line.set_data([], [])
return line,
def update(frame):
line.set_ydata(np.sin(x + frame * 0.1))
return line,
ani = animation.FuncAnimation(
fig, update,
init_func=init,
frames=100,
blit=True # Enable blitting
)
Note: Blitting may not work in all backends (especially notebook environments). Set blit=False if you encounter issues.
Saving Animations¶
Save as GIF¶
ani.save('animation.gif', writer='pillow', fps=30)
Save as MP4¶
# Requires ffmpeg installed
ani.save('animation.mp4', writer='ffmpeg', fps=30)
Save Options¶
ani.save(
'animation.mp4',
writer='ffmpeg',
fps=30,
dpi=100,
bitrate=1800,
extra_args=['-vcodec', 'libx264']
)
Available Writers¶
# Check available writers
print(animation.writers.list())
# Common: ['pillow', 'ffmpeg', 'html']
Practical Examples¶
1. Traveling Wave¶
import matplotlib.pyplot as plt
import matplotlib.animation as animation
import numpy as np
fig, ax = plt.subplots()
x = np.linspace(0, 4 * np.pi, 200)
line, = ax.plot(x, np.sin(x))
ax.set_ylim(-1.5, 1.5)
ax.set_title('Traveling Wave')
def update(frame):
line.set_ydata(np.sin(x - frame * 0.1))
return line,
ani = animation.FuncAnimation(fig, update, frames=200, interval=30, blit=True)
plt.show()
2. Growing Circle¶
import matplotlib.pyplot as plt
import matplotlib.animation as animation
import numpy as np
fig, ax = plt.subplots()
ax.set_xlim(-2, 2)
ax.set_ylim(-2, 2)
ax.set_aspect('equal')
circle = plt.Circle((0, 0), 0.1, fill=False)
ax.add_patch(circle)
def update(frame):
radius = 0.1 + frame * 0.02
circle.set_radius(radius)
return circle,
ani = animation.FuncAnimation(fig, update, frames=80, interval=50, blit=True)
plt.show()
3. Real-Time Stock Price Simulation¶
import matplotlib.pyplot as plt
import matplotlib.animation as animation
import numpy as np
fig, ax = plt.subplots()
ax.set_xlim(0, 100)
ax.set_ylim(90, 110)
ax.set_xlabel('Time')
ax.set_ylabel('Price')
ax.set_title('Stock Price Simulation')
prices = [100]
line, = ax.plot([], [], 'b-')
def update(frame):
# Random walk
change = np.random.normal(0, 1)
prices.append(prices[-1] + change)
# Keep last 100 points
if len(prices) > 100:
prices.pop(0)
line.set_data(range(len(prices)), prices)
ax.set_ylim(min(prices) - 5, max(prices) + 5)
return line,
ani = animation.FuncAnimation(fig, update, interval=100, blit=False)
plt.show()
4. Scatter Plot Animation¶
import matplotlib.pyplot as plt
import matplotlib.animation as animation
import numpy as np
fig, ax = plt.subplots()
ax.set_xlim(-5, 5)
ax.set_ylim(-5, 5)
n_points = 50
positions = np.random.randn(n_points, 2)
velocities = np.random.randn(n_points, 2) * 0.1
scatter = ax.scatter(positions[:, 0], positions[:, 1])
def update(frame):
global positions
positions += velocities
# Bounce off walls
for i in range(2):
mask = np.abs(positions[:, i]) > 5
velocities[mask, i] *= -1
scatter.set_offsets(positions)
return scatter,
ani = animation.FuncAnimation(fig, update, frames=200, interval=30, blit=True)
plt.show()
5. Heatmap Evolution¶
import matplotlib.pyplot as plt
import matplotlib.animation as animation
import numpy as np
fig, ax = plt.subplots()
data = np.random.rand(10, 10)
im = ax.imshow(data, cmap='hot', vmin=0, vmax=1)
plt.colorbar(im)
def update(frame):
# Smooth random evolution
global data
data = data * 0.9 + np.random.rand(10, 10) * 0.1
im.set_array(data)
return im,
ani = animation.FuncAnimation(fig, update, frames=100, interval=100, blit=True)
plt.show()
6. Multiple Subplots Animation¶
import matplotlib.pyplot as plt
import matplotlib.animation as animation
import numpy as np
fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(10, 4))
x = np.linspace(0, 2 * np.pi, 100)
line1, = ax1.plot(x, np.sin(x))
line2, = ax2.plot(x, np.cos(x))
ax1.set_ylim(-1.5, 1.5)
ax2.set_ylim(-1.5, 1.5)
ax1.set_title('sin(x + φ)')
ax2.set_title('cos(x + φ)')
def update(frame):
phase = frame * 0.1
line1.set_ydata(np.sin(x + phase))
line2.set_ydata(np.cos(x + phase))
return line1, line2
ani = animation.FuncAnimation(fig, update, frames=100, interval=50, blit=True)
plt.tight_layout()
plt.show()
ArtistAnimation Alternative¶
For animations from a pre-computed list of frames:
import matplotlib.pyplot as plt
import matplotlib.animation as animation
import numpy as np
fig, ax = plt.subplots()
ax.set_xlim(0, 2 * np.pi)
ax.set_ylim(-1.5, 1.5)
x = np.linspace(0, 2 * np.pi, 100)
# Pre-compute all frames
frames = []
for phase in np.linspace(0, 2 * np.pi, 50):
line, = ax.plot(x, np.sin(x + phase), 'b-')
frames.append([line])
ani = animation.ArtistAnimation(fig, frames, interval=50, blit=True)
plt.show()
Embedding in Notebooks¶
Using HTML Display¶
from IPython.display import HTML
ani = animation.FuncAnimation(fig, update, frames=100, interval=50)
HTML(ani.to_jshtml())
Using to_html5_video¶
from IPython.display import HTML
HTML(ani.to_html5_video())
Common Pitfalls¶
1. Animation Object Not Stored¶
# WRONG: Animation is garbage collected
animation.FuncAnimation(fig, update, frames=100)
plt.show()
# CORRECT: Store reference
ani = animation.FuncAnimation(fig, update, frames=100)
plt.show()
2. Returning Wrong Type¶
# WRONG: Not returning iterable
def update(frame):
line.set_ydata(new_data)
return line # Missing comma!
# CORRECT: Return tuple/list
def update(frame):
line.set_ydata(new_data)
return line, # Note the comma
3. Blit Issues¶
# If animation doesn't update properly with blit=True:
# 1. Try blit=False
# 2. Ensure init_func returns all animated artists
# 3. Ensure update returns all changed artists
Performance Tips¶
- Use blitting when possible for smoother animations
- Reduce data points for complex plots
- Pre-compute data if the computation is expensive
- Lower frame rate (higher interval) for complex scenes
- Use tight_layout() before animation, not during
fig.tight_layout() # Call once before animation
ani = animation.FuncAnimation(...)