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 call ax.plot inside 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 Animation object 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))
FormatRequiresUse for
MP4ffmpeg installedPresentations and video
GIFPillowSmall loops, chat and docs
HTML/JSNothing extraNotebook and web embedding
PNG sequenceNothing extraFrame-by-frame editing
Matplotlib previewA GUI backendChecking 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.

Debugging plots: empty axes, missing data and overlap Backends and environment setup

Last refreshed 2026-09-18.