Animations (FuncAnimation)

Reviewed & published by Brayan K

FuncAnimation is the Matplotlib tool that turns a static plot into motion by calling an update function once per frame to redraw the figure step by step.

Part of the free Matplotlib course at LearnCodingFast — hands-on lessons with worked examples and the output they print, plus practice exercises and a quick quiz.

In this lesson you'll write update and init functions, control frames, interval, and blit, update line data with set_data, and learn how to save to a GIF or MP4.

What You'll Learn in This Lesson

1 Your First Animation

The pattern is always the same: create a figure, make an artist (here an empty line), and write an update(frame) function that changes that artist using set_data(). Hand the figure, the function, the frame count, and the delay to FuncAnimation, and store the result in a variable so it stays alive.

import matplotlib.pyplot as plt
import numpy as np
from matplotlib.animation import FuncAnimation

fig, ax = plt.subplots(figsize=(7, 4))
ax.set_xlim(0, 2 * np.pi)
ax.set_ylim(-1.1, 1.1)

# An empty line we will update each frame
line, = ax.plot([], [], color="royalblue")
x = np.linspace(0, 2 * np.pi, 200)

# update() runs once per frame, redrawing the line a bit further along
def update(frame):
    line.set_data(x[:frame], np.sin(x[:frame]))
    return (line,)

# 200 frames, one every 20 ms
anim = FuncAnimation(fig, update, frames=200, interval=20, blit=True)

ax.set_title("A Sine Wave Drawing Itself")
plt.show()

What you'll see: a blank set of axes that progressively traces out a blue sine wave from left to right as the frame number climbs, finishing as a full single period of the curve.

2 init_func, blit & a Travelling Wave

For smoother playback, set blit=True so Matplotlib only redraws what changed. That requires your functions to return the artists they touched, and it pairs with an init_func that paints the clean starting state. Here the whole wave is redrawn each frame, shifted a little, to make it travel.

import matplotlib.pyplot as plt
import numpy as np
from matplotlib.animation import FuncAnimation

fig, ax = plt.subplots(figsize=(7, 4))
ax.set_xlim(0, 10)
ax.set_ylim(-1.5, 1.5)

line, = ax.plot([], [], lw=2, color="darkorange")
x = np.linspace(0, 10, 500)

# init() sets the starting (blank) state; required when blit=True
def init():
    line.set_data([], [])
    return (line,)

# update() shifts a travelling wave by the frame number
def update(frame):
    y = np.sin(x - 0.1 * frame)
    line.set_data(x, y)
    return (line,)

anim = FuncAnimation(fig, update, init_func=init,
                     frames=100, interval=30, blit=True)

ax.set_title("A Travelling Wave")
plt.show()

What you'll see: a full orange sine wave that glides steadily to the right, each frame shifting it a fraction further so it appears to flow across the plot.

3 Animating a Point & Saving to a File

You can animate any artist, not just full curves — here a single marker traces a circle by recomputing its position from the frame number. Once the animation looks right, export it with anim.save(): the Pillow writer makes a GIF, and the FFmpeg writer makes an MP4 (FFmpeg must be installed for that).

import matplotlib.pyplot as plt
import numpy as np
from matplotlib.animation import FuncAnimation

fig, ax = plt.subplots(figsize=(6, 5))
ax.set_xlim(-1.2, 1.2)
ax.set_ylim(-1.2, 1.2)
ax.set_aspect("equal")

point, = ax.plot([], [], "o", color="crimson", markersize=12)

def update(frame):
    angle = 2 * np.pi * frame / 60
    point.set_data([np.cos(angle)], [np.sin(angle)])
    return (point,)

anim = FuncAnimation(fig, update, frames=60, interval=40, blit=True)

# Saving note: to write a GIF you would call
# anim.save("orbit.gif", writer="pillow", fps=25)
ax.set_title("A Point Orbiting in a Circle")
plt.show()

What you'll see: a single crimson dot that travels smoothly around a circular path, returning to its start after 60 frames. The commented save line shows how you would export it as a GIF.

🎯 Your Turn: Fill in the Blanks

Replace each ___ to update the line every frame and create the animation.

import matplotlib.pyplot as plt
import numpy as np
from matplotlib.animation import FuncAnimation

fig, ax = plt.subplots(figsize=(7, 4))
ax.set_xlim(0, 10)
ax.set_ylim(-1.1, 1.1)

line, = ax.plot([], [], color="seagreen")
x = np.linspace(0, 10, 300)

def update(frame):
    # Update the line's x and y data for this frame
    line.___(x, np.cos(x - 0.1 * frame))
    return (line,)

# Build the animation: 80 frames, 25 ms apart, with blitting
anim = ___(fig, update, frames=80, interval=25, blit=True)

ax.set_title("Cosine in Motion")
plt.show()

Hint: the method that swaps a line's data is set_data, and the constructor is FuncAnimation.

📋 Common Errors & Quick Reference

❌ The animation never plays or freezes instantly

You didn't keep a reference. Assign it: anim = FuncAnimation(...), or it gets garbage-collected.

❌ Nothing updates with blit=True

Your update must return the artists it changed, e.g. return (line,). blit redraws only what is returned.

❌ set_data error about scalar values

Pass sequences, not bare numbers: point.set_data([x], [y]) with one-element lists.

PieceWhat It Does
frames=How many frames to draw
interval=Delay between frames (ms)
blit=TrueRedraw only what changed
anim.save(...)Export to GIF or MP4

🏆 Mini Challenge: Growing Bars

Animate four bars rising from zero to their target heights, updating each bar with set_height() every frame.

import matplotlib.pyplot as plt
import numpy as np
from matplotlib.animation import FuncAnimation

fig, ax = plt.subplots(figsize=(7, 4))
categories = ["A", "B", "C", "D"]
heights = np.array([2, 4, 6, 8], dtype=float)

bars = ax.bar(categories, [0, 0, 0, 0], color="mediumpurple")
ax.set_ylim(0, 12)

def update(frame):
    # grow each bar toward its target height
    grow = (frame + 1) / 30
    for bar, target in zip(bars, heights):
        bar.set_height(min(target * grow, target))
    return bars

anim = FuncAnimation(fig, update, frames=30, interval=50, blit=False)

# To export: anim.save("bars.mp4", writer="ffmpeg", fps=20)
ax.set_title("Bars Growing to Their Targets")
plt.show()
import matplotlib.pyplot as plt
from matplotlib.animation import FuncAnimation

fig, ax = plt.subplots()
ax.set_xlim(0, 10)
ax.set_ylim(0, 10)
(dot,) = ax.plot([], [], "o")

frames = 5

def update(i):
    dot.set_data([i], [i])
    return (dot,)

anim = FuncAnimation(fig, update, frames=frames, blit=True, interval=200)

# Draw the frames by hand so this is testable without a video writer.
positions = []
for i in range(frames):
    update(i)
    x, y = dot.get_data()
    positions.append([int(x[0]), int(y[0])])

print("Frames:", frames)
print("Positions:", positions)
interval_ms = anim.event_source.interval
print("Interval in ms:", int(interval_ms))
print("Total run time in ms:", int(frames * interval_ms))
print("It moves diagonally:", all(p[0] == p[1] for p in positions))

# ✅ Expected output:
# Frames: 5
# Positions: [[0, 0], [1, 1], [2, 2], [3, 3], [4, 4]]
# Interval in ms: 200
# Total run time in ms: 1000
# It moves diagonally: True

❓ Frequently Asked Questions

Lesson complete — your plots can move now!

You built animations with FuncAnimation, wrote update and init functions, redrew artists with set_data and set_height, tuned frames, interval, and blit, and saw how to save to GIF or MP4.

Practice quiz

Which class builds an animation by calling an update function once per frame?

  • FuncAnimation
  • ArtistAnimation
  • AnimationWriter
  • FrameLoop

Answer: FuncAnimation. FuncAnimation from matplotlib.animation calls your update function once per frame.

Why must you assign the animation to a variable like anim = FuncAnimation(...)?

  • To set the frame rate
  • To enable blitting
  • So it isn't garbage-collected and silently stops
  • To save it as a GIF

Answer: So it isn't garbage-collected and silently stops. Without a kept reference, the animation object can be garbage-collected and freezes.

What does the interval argument to FuncAnimation control?

  • The number of frames
  • The delay between frames in milliseconds
  • The figure size
  • The line width

Answer: The delay between frames in milliseconds. interval is the delay between frames in milliseconds.

What is the purpose of blit=True?

  • It saves the animation
  • It loops the animation forever
  • It increases the frame count
  • It redraws only the parts that changed for smoother playback

Answer: It redraws only the parts that changed for smoother playback. blit=True redraws only changed artists, making playback smoother.

When blit=True, what must your update function do?

  • Return the iterable of artists it modified
  • Call plt.show() itself
  • Return None
  • Delete the figure

Answer: Return the iterable of artists it modified. With blitting, update must return the artists it changed, e.g. return (line,).

Which method updates a line's x and y data each frame?

  • line.refresh()
  • line.set_data(x, y)
  • line.redraw()
  • line.update_xy()

Answer: line.set_data(x, y). set_data(x, y) swaps the data on an existing line artist.

Which writer saves an animation as a GIF without extra installs?

  • ffmpeg
  • imagemagick
  • pillow
  • html5

Answer: pillow. anim.save('out.gif', writer='pillow') uses the bundled Pillow writer.

What is the role of init_func in FuncAnimation?

  • It sets the frame count
  • It chooses the colormap
  • It exports the file
  • It paints the clean starting state, useful with blitting

Answer: It paints the clean starting state, useful with blitting. init_func draws the initial blank state so blitting has a clean background.

What does the comma do in line, = ax.plot([], [])?

  • It unpacks the single line out of the returned list
  • It creates two lines
  • It enables animation
  • It is a syntax error

Answer: It unpacks the single line out of the returned list. plot returns a list; the comma unpacks the one line so you can call set_data on it.

Why does set_data expect [cos] and [sin] rather than bare floats?

  • To rotate the marker
  • Because set_data expects sequences, not scalars
  • To change the color
  • To speed up the animation

Answer: Because set_data expects sequences, not scalars. set_data needs sequences; recent Matplotlib raises an error on bare scalars.

Continue this course

Frequently asked questions

What is FuncAnimation and how does it work?

FuncAnimation, from matplotlib.animation, builds an animation by calling an update function once per frame. You give it the figure, the update function, how many frames to draw, and the delay between frames in milliseconds. Each call to update changes the plot a little, and Matplotlib stitches the frames into motion.

Why must I keep a reference to the animation object?

FuncAnimation only keeps running while a Python variable points to it. If you write FuncAnimation(...) without assigning it to a name like anim = FuncAnimation(...), the object can be garbage-collected and the animation silently freezes or never starts. Always store it in a variable.

What does blit=True do, and when do I need init_func?

blit=True is an optimization that redraws only the parts that changed each frame, making playback smoother. When you use it, your update (and init_func) must return the iterable of artists they modified, and init_func should set the starting blank state so Matplotlib knows the clean background.

How do I save an animation to a GIF or MP4?

Call anim.save(). For a GIF use the Pillow writer: anim.save('out.gif', writer='pillow', fps=25). For an MP4 you need FFmpeg installed: anim.save('out.mp4', writer='ffmpeg', fps=30). The fps argument controls how many frames play per second in the saved file.