Animation — FuncAnimation
WHY does this tool exist?
WHAT is it, precisely?
HOW the frame loop actually runs (derive the timing)
There is no equation to "prove," but we can derive the frame schedule from first principles so nothing is magic.

Worked Example 1 — a travelling sine wave (the canonical pattern)
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.animation import FuncAnimation
fig, ax = plt.subplots()
ax.set_xlim(0, 2*np.pi) # Why: set limits ONCE so blitting can cache background
ax.set_ylim(-1.1, 1.1)
x = np.linspace(0, 2*np.pi, 400)
line, = ax.plot([], [], lw=2) # Why: create the artist ONCE, empty for now
def init():
line.set_data([], []) # Why: clean background; defines what "before frame 0" looks like
return line, # Why: blit needs the changed artists
def update(frame): # frame = 0,1,2,... from frames=...
y = np.sin(x - 0.1*frame) # Why: shift phase by frame → wave moves right
line.set_data(x, y) # Why: MUTATE existing artist, don't create a new one
return line, # Why: tell blit what to redraw
anim = FuncAnimation(fig, update, frames=200, init_func=init,
interval=20, blit=True) # 1000/20 = 50 FPS
plt.show()
# anim.save("wave.mp4", fps=50) # or "wave.gif"Why the comma in line, = ... and return line,? ax.plot returns a list of lines; line, unpacks the single element. return line, makes a 1-tuple — blit expects an iterable of artists.
Worked Example 2 — frames as a generator (variable data per frame)
def frame_gen(): # Why: when each frame needs custom values, not just 0..N
t = 0.0
while t < 6.28:
yield t # this value becomes `frame` in update
t += 0.05
def update(t):
line.set_data(x, np.sin(x - t))
return line,
anim = FuncAnimation(fig, update, frames=frame_gen,
init_func=init, interval=30, blit=True, save_count=200)Why save_count? A generator has unknown length; when saving, matplotlib needs to know how many frames to grab — save_count caps it.
Worked Example 3 — animating MORE than one artist (e.g. a moving dot + trail)
line, = ax.plot([], [], 'b-')
dot, = ax.plot([], [], 'ro')
def update(i):
xi = x[:i]
line.set_data(xi, np.sin(xi)) # growing trail
dot.set_data([x[i]], [np.sin(x[i])]) # Why: lists, because set_data wants sequences
return line, dot # Why: return ALL changed artists for blitWhy return both? Blit redraws exactly what you return; forget one and it'll appear frozen.
Recall Feynman: explain to a 12-year-old
Imagine a flip-book. Every page is almost the same drawing, but the ball is nudged a little. Flip the pages fast and the ball looks like it's flying. FuncAnimation is a robot that holds your flip-book. You hand it ONE rubber-stamp function that says "for page number n, put the ball here," and a stopwatch that says "flip every 20 milliseconds." The robot does all the flipping. The trick: you don't draw a new ball each time — you just slide the same ball, which is why it's so fast.
Active recall
What does FuncAnimation repeatedly call to produce each frame?
func, once per frame, on the same persistent figure.With blit=True, what must init_func and func return?
return line,).Convert interval=25 ms to FPS.
Why create the line artist OUTSIDE the update function?
set_data each frame instead of piling up new artists (keeps blitting valid and memory flat).Why must you keep a reference like anim = FuncAnimation(...)?
What does the trailing comma do in line, = ax.plot(...)?
ax.plot returns into the variable line.When frames is a generator, why pass save_count?
save_count tells matplotlib how many frames to capture when saving.How do you save an animation to mp4?
anim.save("out.mp4", fps=...) (needs ffmpeg) or .gif (needs pillow/imagemagick).Total time for N=200 frames at interval=20ms (one pass)?
Connections
- Matplotlib Figure and Axes — the canvas FuncAnimation updates.
- Artists in Matplotlib —
Line2D,set_data, blitting. - Generators in Python — used for the
framesargument. - Event Loops / GUI backends — the timer that drives frames.
- NumPy linspace and broadcasting — generating per-frame arrays.
- Saving figures and ffmpeg — exporting to mp4/gif.
Concept Map
Hinglish (regional understanding)
Intuition Hinglish mein samjho
Socho ek flip-book — har page pe drawing thodi si change hoti hai, aur fast flip karo to motion dikhta hai. FuncAnimation bilkul wahi karta hai: tum ek hi update(frame) function dete ho, aur matplotlib usko baar-baar call karta hai, har baar ek naya frame. Ek hi figure update hoti hai, naya figure har baar nahi banta — isliye fast aur smooth.
Sabse important pattern: artist (jaise line, = ax.plot([], [])) ko bahar ek hi baar banao, aur update ke andar sirf line.set_data(x, y) se uska data badlo. Galti ye hoti hai ki log ax.plot() ko update ke andar likh dete hain — isse har frame pe naya line ban jaata hai, memory badhti hai, aur blit toot jaata hai. interval milliseconds me hota hai: interval=20 matlab 1000/20 = 50 FPS.
blit=True use karo speed ke liye — par tab init aur update dono ko changed artists return karne padte hain (return line,). Ek classic bug: agar tum FuncAnimation(...) ko kisi variable me store nahi karte (anim = ...), to Python use garbage-collect kar deta hai aur animation freeze/blank ho jaata hai. Save karne ke liye anim.save("wave.mp4", fps=50) ya .gif. Bas itna yaad rakho: Figure, Init, Blit-return, Store — aur tumhari animation chal padegi.