Animations and interactive figures
Build a frame-by-frame animation, export it to video, and add sliders and pan/zoom without fighting the event loop.
FuncAnimation and blitting
FuncAnimation calls your update function once per frame. With blit=True it redraws only the artists you return, which is what keeps a smooth animation possible.
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.animation import FuncAnimation
fig, ax = plt.subplots(figsize=(7, 3))
x = np.linspace(0, 2 * np.pi, 300)
(line,) = ax.plot([], [], lw=2)
ax.set_xlim(0, 2 * np.pi)
ax.set_ylim(-1.2, 1.2)
def init():
line.set_data([], [])
return (line,)
def update(frame):
line.set_data(x, np.sin(x + frame / 8))
return (line,)
anim = FuncAnimation(fig, update, frames=120, init_func=init,
blit=True, interval=30, repeat=True)- Update the data on existing artists with
set_data; never callax.plotinside the update function. - Return a tuple of artists from the update function when
blit=True. - Fix the axis limits in advance: blitting cannot redraw the ticks and labels.
- Keep a reference to the returned
Animationobject or it is garbage collected and nothing plays.
Saving to video or GIF
anim.save("wave.mp4", fps=30, dpi=150) # requires ffmpeg on PATH
anim.save("wave.gif", writer="pillow", fps=15) # no ffmpeg needed
from matplotlib.animation import PillowWriter, FFMpegWriter
anim.save("wave.mp4", writer=FFMpegWriter(fps=30, bitrate=1800))
anim.save("wave.gif", writer=PillowWriter(fps=12))
# in a notebook, render a playable clip inline
from matplotlib.animation import HTMLWriter
anim.save("wave.html", writer=HTMLWriter(fps=30))| Format | Requires | Use for |
|---|---|---|
| MP4 | ffmpeg installed | Presentations and video |
| GIF | Pillow | Small loops, chat and docs |
| HTML/JS | Nothing extra | Notebook and web embedding |
| PNG sequence | Nothing extra | Frame-by-frame editing |
| Matplotlib preview | A GUI backend | Checking while you develop |
Widgets and interactivity
from matplotlib.widgets import Slider, RadioButtons
fig, ax = plt.subplots(figsize=(7, 4))
fig.subplots_adjust(bottom=0.25)
line, = ax.plot(x, np.sin(x))
ax.set_ylim(-1.5, 1.5)
ax_slider = fig.add_axes([0.2, 0.1, 0.6, 0.03])
freq = Slider(ax_slider, "frequency", 0.5, 4.0, valinit=1.0)
def on_change(value):
line.set_ydata(np.sin(value * x))
fig.canvas.draw_idle()
freq.on_changed(on_change)
# pan, zoom and save are available from the GUI toolbar with
# an interactive backend; a static backend has no toolbar at all⚠️
Animations and widgets only respond with an interactive backend (or
%matplotlib widget in a notebook with ipympl). A missing toolbar or a frozen figure usually means you are on Agg.FAQ
Why is my animation blank or static?
Usually the
Animation object was not stored in a variable and was collected, or the update function returns nothing while blit=True is set.How do I make a smooth animation with lots of data?
Lower the frame rate, reduce the number of points per frame, use
set_data rather than replotting, and enable blit=True. For very large scenes, pre-render image frames and animate imshow.Related
Debugging plots: empty axes, missing data and overlap Backends and environment setup
Last refreshed 2026-09-18.