Turn any static matplotlib script into an animated GIF by sweeping a variable
Project description
mpl-animator
Turn any static matplotlib script into an animated GIF or MP4 by sweeping a variable - no rewriting required.
Features
- Zero boilerplate - point it at an existing script, it figures out the rest
- AST-based dependency tracking - automatically identifies which variables and calculations need to update each frame
- Parallel rendering - renders frames across all CPU cores via
multiprocessing, falls back to sequential automatically - GIF and MP4 output - export as animated GIF (Pillow) or MP4 (ffmpeg); just add
--format mp4 - Axis rotation - animate
azimwithax.view_initto rotate 3D plots; animateanglewithax.set_theta_offsetto spin polar plots - Math expressions in ranges -
--range "0,2*pi"just works - 2D, 3D, and polar plots - handles
plot_surface,scatter3D, subplots, polar axes, and more - Single-file, standalone -
mpl_animator.pycan be dropped into any project with no install required; only depends onmatplotlib,numpy, andPillow(no exotic dependencies) - Library API - importable as a Python module for use in notebooks or pipelines
Install
pip install mpl-animator
Or just copy the file - no install needed:
# copy mpl_animator.py into your project, then use it directly
python mpl_animator.py my_plot.py --var t --range "0,1"
Usage
If installed via pip, use the mpl-animator command. If using the file directly, replace mpl-animator with python mpl_animator.py - everything else is identical.
# Basic: animate variable `f` from 3 to 60 (outputs GIF by default)
mpl-animator wave_static.py --var f --range "3,60"
# Export as MP4 instead of GIF (requires ffmpeg)
mpl-animator plot.py --var t --range "0,2*pi" --format mp4
# Math expressions in range, custom frame count and FPS
mpl-animator plot.py --var t --range "0,2*pi" --frames 60 --fps 30
# Rotate a 3D plot by animating the camera azimuth
mpl-animator my_3d_plot.py --var azim --range "0,360" --frames 72 --fps 20
# Control output quality and parallelism
mpl-animator plot.py --var alpha --range "0,1" --dpi 150 --workers 8
# Custom output filename
mpl-animator plot.py --var t --range "0,1" --out my_animation.gif
This generates a <script>_animated.py file. Run it to produce the output:
python wave_static_animated.py # parallel (default)
python wave_static_animated.py --sequential # single-threaded fallback
Examples
Example 1 - Wave & spectrum (2D)
wave_static.py plots a signal and its frequency spectrum for a fixed frequency f:
# wave_static.py (key lines)
f = 10.0 # <- variable to animate
t = np.linspace(0, 1, 1000)
y = np.sin(2*np.pi*f*t) + 0.4*np.sin(2*np.pi*2*f*t)
freqs = np.fft.rfftfreq(len(t), d=t[1]-t[0])
spectrum = np.abs(np.fft.rfft(y))
fig, (ax1, ax2) = plt.subplots(2, 1, figsize=(9, 6))
ax1.plot(t, y, 'royalblue', lw=1.5)
ax2.plot(freqs, spectrum, 'tomato', lw=1.5)
plt.show()
Animate f from 3 Hz to 60 Hz:
python mpl_animator.py examples/wave_static.py --var f --range "3,60" --frames 60 --fps 20
python wave_static_animated.py
The animator detects that y, spectrum depend on f, moves them into the per-frame update(), and keeps the figure/axes creation static - so only the data redraws each frame.
Example 2 - 3D Lissajous curve
lissajous_3d_static.py draws a 3D Lissajous figure for fixed frequency ratio a:
# lissajous_3d_static.py (key lines)
a = 3.0 # <- variable to animate
b = 2.0
c = 1.0
t = np.linspace(0, 2 * np.pi, 1000)
x = np.sin(a * t + delta)
y = np.sin(b * t)
z = np.sin(c * t)
fig = plt.figure(figsize=(8, 6))
ax = fig.add_subplot(111, projection='3d')
ax.scatter(x, y, z, c=colors, s=2, alpha=0.8)
ax.set_title(f"3D Lissajous a={a:.1f}, b={b:.1f}, c={c:.1f}")
plt.show()
Animate a from 1 to 6, sweeping through different curve topologies:
python mpl_animator.py examples/lissajous_3d_static.py --var a --range "1,6" --frames 80 --fps 20
python lissajous_3d_static_animated.py
For 3D plots the animator calls fig.clear() and recreates the axes each frame (required to preserve the projection='3d' state), then re-runs all drawing commands with the new value of a.
How it works
- Parses your script's AST to find which variables depend on the animated one
- Splits code into static (run once) and dynamic (recalculated per frame)
- Generates a new script with
FuncAnimation(sequential 2D GIF), PNG+stitch (sequential MP4 or 3D), andmultiprocessing(parallel) renderers
Library usage
from mpl_animator import animate
src = open("my_plot.py").read()
animated_code = animate(src, var="t", range_str="0,6.28", frames=60, fps=25)
open("my_plot_animated.py", "w").write(animated_code)
# Export as MP4
animated_code = animate(src, var="t", range_str="0,6.28", fmt="mp4")
Supported plot types
2D (plot, scatter, bar, hist, contour, imshow, ...), 3D (plot_surface, scatter3D, ...), polar, and anything else matplotlib draws.
Tests
pytest tests/ -v # fast tests
pytest tests/ -v -m slow # slow tests that generate actual GIFs/MP4s
Author: Basem Rajjoub
Built with the assistance of Claude Code
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file mpl_animator-0.1.2.tar.gz.
File metadata
- Download URL: mpl_animator-0.1.2.tar.gz
- Upload date:
- Size: 17.7 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.11.9
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
53eaecc77fcffe506480f74a583d72da019c742337195e476a4d5ef350701707
|
|
| MD5 |
9f5934878cae1a941db46e26ceffdddd
|
|
| BLAKE2b-256 |
837ba47dfbf65056935cb23a6467d0c081cd1d0bca1fbb33889787f880abf185
|
File details
Details for the file mpl_animator-0.1.2-py3-none-any.whl.
File metadata
- Download URL: mpl_animator-0.1.2-py3-none-any.whl
- Upload date:
- Size: 13.5 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.11.9
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
882e5257de4eb8f707845a9a6852b47005291dfc1b8bb15800a6aa1f9033a606
|
|
| MD5 |
287350b492689bd66353aa86ae656aef
|
|
| BLAKE2b-256 |
b4f8e23e379df092cc1f8c466b3c8b141ce4b054d1923bd51469021a9f3ff098
|