5.4.18 · D3Scientific Computing (Python)

Worked examples — Animation — FuncAnimation

2,938 words13 min readBack to topic

Before anything, one promise: every symbol is earned. interval is a number of milliseconds between two flips of the flip-book. frames is the list of "page numbers" (or values) the robot walks through. FPS is how many pages flip per second. func (we'll call it update) is the rubber stamp that says "for this page, draw the picture like THIS". Keep those four pictures in your head.

Two more small pieces we'll lean on repeatedly, defined here, before first use:


The scenario matrix

Every question about a FuncAnimation lives in one of these cells. Our examples below are each tagged with the cell they land in, and together they touch all of them.

Axis Cases we must cover
Frame source integer N · explicit list/array · generator (unknown length)
Timing direction given interval → find FPS/total time · given FPS/time → find interval
Blit blit=True (must return artists) · blit=False (return value ignored)
Artist count one artist · many artists (line + dot)
Degenerate / limiting frames=0 (empty) · interval=0 (as-fast-as-possible) · one-frame animation · generator that never stops
Saving save to mp4 at chosen fps · why interval is ignored when saving · generator + save_count
Word / exam twist "I want an 8-second clip" real problem · "why is my window blank?" bug-hunt

The number that ties timing together, derived once in the parent, is:


Example 1 — integer frames → FPS and total time (cell: integer N · interval→FPS · one artist)

Forecast: guess the FPS before reading. Is 20 ms between frames fast or slow?

  1. Read off the numbers. interval = 20 ms, N = 200 frames. Why this step? Every timing problem starts by naming which number is interval and which is N, so we don't mix them.
  2. FPS from the first equation. . Why this step? "How many 20 ms slots fit in 1000 ms?" — that count is frames-per-second.
  3. Total time from the second equation. ms; convert with the bridge: s. Why this step? One pass is every frame's little wait, stacked end to end — in ms — then divided by 1000 to read it in seconds.

Verify: cross-check with the other form: s. ✓ Units: . ✓


Example 2 — the inverse: "I want an exact-length clip" (cell: given FPS/time→interval · word problem · saving)

Forecast: will interval even matter when you save the file?

  1. Frames = FPS × seconds. frames. Why this step? Rearranging gives — we know the two on the right.
  2. Interval from FPS. ms. Why this step? Same first equation, solved for interval: the slot length is "one second (1000 ms) split into FPS pieces."
  3. Saving passes fps, not interval. anim.save("clip.mp4", fps=60). Why this step? The parent note's key rule: when saving, matplotlib spaces frames at seconds and ignores interval (there's no live GUI timer to obey). See Saving figures and ffmpeg.

Verify: with , : s exactly. ✓ And ms ms s for live playback. ✓


Example 3 — array of frames feeding a moving dot (cell: explicit array · many artists · blit=True)

Figure — Animation — FuncAnimation

First fix the data, so every symbol below is concrete:

import numpy as np
x = np.linspace(0, 2*np.pi, 200)   # 200 evenly-spaced x-values from 0 to 2π
# y at any x is np.sin(x); see [[NumPy linspace and broadcasting]]

So ==x== is a NumPy array of 200 evenly spaced numbers from to (built by `linspace`), and np.sin(x) is the matching array of heights. x[:i] means "the first i entries of x" — a slice.

Forecast: if you return only the dot and forget the trail line — what happens on screen?

  1. Create both artists once, empty. line, = ax.plot([], [], 'b-') and dot, = ax.plot([], [], 'ro'). Why this step? Per the parent's rule, artists are built outside update and only mutated inside — see Artists in Matplotlib.
  2. Frame i slices the data. trail = points x[:i]; dot = the single point (x[i], sin(x[i])). Why this step? Growing the slice x[:i] with i is what makes the trail lengthen — look at the figure: the mint trail grows left-to-right while the coral dot rides its tip.
  3. Return BOTH artists. return line, dot. Why this step? With blit=True, matplotlib redraws exactly what you return. Omit the line and the trail freezes.

Verify (sanity, no arithmetic): at , trail is empty (x[:0]), dot at — a single starting point, matches "frame before motion." At the last , x[:i] is the whole curve. ✓ The figure's frames-side captions confirm the slice lengths .


Example 4 — a generator as frames, with save_count (cell: generator · saving · unknown length)

Forecast: can matplotlib know the length of a generator by itself?

  1. Count the yields. Values are up to but not including the first . Count . Why this step? A while t < 6.28 with step yields indices through (since but ). That's 126 values.
  2. Live playback is fine — the timer just keeps pulling from the generator until it's exhausted. See Generators in Python.
  3. Saving needs a count. A generator has no len. save_count=200 tells .save an upper bound on how many frames to pull. Why this step? The saver must pre-know (or cap) how many images to write; it can't ask a generator "how long are you?"

Verify: ✓ and ✓, so exactly 126 frames. Since , save_count=200 captures all of them with room to spare. ✓


Example 5 — blit=False: the return value stops mattering (cell: blit off · one artist)

Forecast: will forgetting the return break a blit=False animation?

  1. With blit=False, the whole axes is redrawn each frame. The return value is ignored. Why this step? Blitting is the only feature that reads your returned artists (it needs to know what to copy over the cached background). No blit → nothing reads the return.
  2. So return line, is harmless but optional here. Keeping it means the same code works if you later flip blit=True. Why this step? Future-proofing: one habit (always return artists) works for both modes.
  3. Cost: slower, because ticks/grid/labels are re-rendered every frame — the exact expense blitting was invented to skip.

Verify (behavioural): a blit=False animation with no return statement still animates correctly (proves the return is ignored); a blit=True one with no return raises/freezes (proves blit reads it). This is a logic check, not arithmetic — the asymmetry is the whole point.


Example 6 — degenerate: zero frames, one frame, interval 0 (cell: empty / single / limiting)

Figure — Animation — FuncAnimation

Forecast: does frames=0 crash, freeze, or show a static picture?

  1. frames=0 → one pass shows nothing move. update is never called with a frame value; you see only whatever init_func drew (the clean background we defined at the top). s of motion. Why this step? With the second equation gives : no frames means no elapsed animation time. The figure's left panel shows just the clean init_func background.
  2. frames=1 → a single flip. update runs once (frame 0), then (because repeat=True by default — the loop-when-frames-run-out switch we defined at the top) loops that one frame forever, so it looks like a still image. . Why this step? It's the smallest non-empty case; useful to test that your artists appear at all.
  3. interval=0 → "as fast as the backend allows". is undefined mathematically; in practice the GUI timer fires as quickly as the event loop can, so real FPS is capped by your machine, not the formula. Why this step? This is the limiting behaviour of the first equation: as , FPS , but hardware clamps it. Never divide by literal zero in your head — read it as a limit.

Verify: (a) s ✓. (b) s ms for the single frame ✓. (c) limit check: as interval shrinks ms, FPS rises , confirming FPS as interval . ✓


Example 7 — the never-ending generator (cell: generator that never stops · limiting)

Forecast: will live playback ever stop? Will saving hang forever?

  1. Live playback runs indefinitely. The generator never raises StopIteration, so the timer keeps pulling new t values every interval ms until you close the window. Why this step? FuncAnimation stops a pass only when the frame source is exhausted; an infinite source is never exhausted. With repeat irrelevant (there's no "end" to loop from), it's just an endless stream.
  2. Saving must be capped. .save walks the generator to write images. With no length and no save_count, matplotlib cannot know when to stop — it would try to pull frames forever. Why this step? This is exactly why save_count exists (Example 4): it is mandatory for an infinite generator. anim.save("out.mp4", fps=30, save_count=300) writes the first 300 frames and stops.
  3. Fix pattern. Either make the generator finite (add a stop condition) or always pass save_count when saving. Why this step? Both give the saver a definite endpoint; the finite generator also lets live playback end naturally.

Verify: with save_count=300 at fps=30, the saved clip length is s — a definite, finite file even though the generator is infinite. ✓


Example 8 — the classic "window is blank" bug (cell: exam twist · degenerate reference-loss)

Forecast: is the bug in update, in interval, or somewhere else entirely?

  1. Spot the missing assignment. The call result was not stored: no anim = .... Why this step? FuncAnimation returns an object that owns the timer. Nothing else keeps it alive.
  2. Python garbage-collects it. Once the unreferenced object is collected, its timer dies — no more frames. Why this step? This ties to the event loop: the timer is a live object; drop the reference, drop the heartbeat.
  3. Fix: anim = FuncAnimation(...) and keep anim in scope (module-level or returned from your function).

Verify (behavioural): identical code that only adds anim = animates; removing it reproduces the blank window every time — isolating the reference as the sole cause. ✓


Active recall

Connections

  • Animation — FuncAnimation — parent: the machine these scenarios exercise.
  • Matplotlib Figure and Axes — the canvas being updated each frame.
  • Artists in MatplotlibLine2D, set_data, blit-return.
  • Generators in Python — Examples 4 & 7's frame source.
  • Event Loops / GUI backends — the timer behind Examples 6 & 8.
  • NumPy linspace and broadcasting — building the per-frame arrays (Example 3's x).
  • Saving figures and ffmpeg — Examples 2, 4 & 7's .save.

Concept Map

integer N

generator

live

saving

frame source

known length

needs save_count

timing eqns FPS and T

uses interval

uses fps only

blit true returns artists

store anim reference