
Manimce Best Practices
- 354 installs
- 42.8k repo stars
- Updated July 24, 2026
- calesthio/openmontage
This is a copy of manimce-best-practices by adithya-s-k - installs and ranking accrue to the original listing.
Use this skill when working with manimce best practices.
About
Skill for working with manimce best practices. Use when you need manimce best practices functionality in your application.
- Specialized for manimce best practices
- Integrated with Claude Code
- Streamlines workflow
Manimce Best Practices by the numbers
- 354 all-time installs (skills.sh)
- Data as of Jul 28, 2026 (Skillselion catalog sync)
npx skills add https://github.com/calesthio/openmontage --skill manimce-best-practicesAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 354 |
|---|---|
| repo stars | ★ 42.8k |
| Last updated | July 24, 2026 |
| Repository | calesthio/openmontage ↗ |
What it does
Use this skill when working with manimce best practices.
Files
How to use
Read individual rule files for detailed explanations and code examples:
Core Concepts
- rules/scenes.md - Scene structure, construct method, and scene types
- rules/mobjects.md - Mobject types, VMobject, Groups, and positioning
- rules/animations.md - Animation classes, playing animations, and timing
Creation & Transformation
- rules/creation-animations.md - Create, Write, FadeIn, DrawBorderThenFill
- rules/transform-animations.md - Transform, ReplacementTransform, morphing
- rules/animation-groups.md - AnimationGroup, LaggedStart, Succession
Text & Math
- rules/text.md - Text mobjects, fonts, and styling
- rules/latex.md - MathTex, Tex, LaTeX rendering, and coloring formulas
- rules/text-animations.md - Write, AddTextLetterByLetter, TypeWithCursor
Styling & Appearance
- rules/colors.md - Color constants, gradients, and color manipulation
- rules/styling.md - Fill, stroke, opacity, and visual properties
Positioning & Layout
- rules/positioning.md - move_to, next_to, align_to, shift methods
- rules/grouping.md - VGroup, Group, arrange, and layout patterns
Coordinate Systems & Graphing
- rules/axes.md - Axes, NumberPlane, coordinate systems
- rules/graphing.md - Plotting functions, parametric curves
- rules/3d.md - ThreeDScene, 3D axes, surfaces, camera orientation
Animation Control
- rules/timing.md - Rate functions, easing, run_time, lag_ratio
- rules/updaters.md - Updaters, ValueTracker, dynamic animations
- rules/camera.md - MovingCameraScene, zoom, pan, frame manipulation
Configuration & CLI
- rules/cli.md - Command-line interface, rendering options, quality flags
- rules/config.md - Configuration system, manim.cfg, settings
Shapes & Geometry
- rules/shapes.md - Circle, Square, Rectangle, Polygon, and geometric primitives
- rules/lines.md - Line, Arrow, Vector, DashedLine, and connectors
Working Examples
Complete, tested example files demonstrating common patterns:
- examples/basic_animations.py - Shape creation, text, lagged animations, path movement
- examples/math_visualization.py - LaTeX equations, color-coded math, derivations
- examples/updater_patterns.py - ValueTracker, dynamic animations, physics simulations
- examples/graph_plotting.py - Axes, functions, areas, Riemann sums, polar plots
- examples/3d_visualization.py - ThreeDScene, surfaces, 3D camera, parametric curves
Scene Templates
Copy and modify these templates to start new projects:
- templates/basic_scene.py - Standard 2D scene template
- templates/camera_scene.py - MovingCameraScene with zoom/pan
- templates/threed_scene.py - 3D scene with surfaces and camera rotation
Quick Reference
Basic Scene Structure
from manim import *
class MyScene(Scene):
def construct(self):
# Create mobjects
circle = Circle()
# Add to scene (static)
self.add(circle)
# Or animate
self.play(Create(circle))
# Wait
self.wait(1)Render Command
# Basic render with preview
manim -pql scene.py MyScene
# Quality flags: -ql (low), -qm (medium), -qh (high), -qk (4k)
manim -pqh scene.py MySceneKey Differences from 3b1b/ManimGL
| Feature | Manim Community | 3b1b/ManimGL |
|---|---|---|
| Import | from manim import * | from manimlib import * |
| CLI | manim | manimgl |
| Math text | MathTex(r"\pi") | Tex(R"\pi") |
| Scene | Scene | InteractiveScene |
| Package | manim (PyPI) | manimgl (PyPI) |
Jupyter Notebook Support
Use the %%manim cell magic:
%%manim -qm MyScene
class MyScene(Scene):
def construct(self):
self.play(Create(Circle()))Common Pitfalls to Avoid
1. Version confusion - Ensure you're using manim (Community), not manimgl (3b1b version) 2. Check imports - from manim import * is ManimCE; from manimlib import * is ManimGL 3. Outdated tutorials - Video tutorials may be outdated; prefer official documentation 4. manimpango issues - If text rendering fails, check manimpango installation requirements 5. PATH issues (Windows) - If manim command not found, use python -m manim or check PATH
Installation
# Install Manim Community
pip install manim
# Check installation
manim checkhealthUseful Commands
manim -pql scene.py Scene # Preview low quality (development)
manim -pqh scene.py Scene # Preview high quality
manim --format gif scene.py # Output as GIF
manim checkhealth # Verify installation
manim plugins -l # List plugins"""
3D Visualization Patterns for Manim Community
Demonstrates ThreeDScene, 3D axes, surfaces, and camera control.
Adapted from 3b1b patterns for ManimCE.
Run with: manim -pql 3d_visualization.py SceneName
"""
from manim import *
import numpy as np
class Basic3DScene(ThreeDScene):
"""Basic 3D scene with shapes."""
def construct(self):
# Set camera orientation
self.set_camera_orientation(phi=60 * DEGREES, theta=-45 * DEGREES)
# 3D shapes
sphere = Sphere(radius=1, color=BLUE)
cube = Cube(side_length=1.5, color=RED, fill_opacity=0.7)
cone = Cone(base_radius=0.8, height=1.5, color=GREEN)
# Position shapes
sphere.shift(LEFT * 3)
cone.shift(RIGHT * 3)
self.play(Create(sphere), Create(cube), Create(cone))
self.wait()
# Rotate camera
self.begin_ambient_camera_rotation(rate=0.3)
self.wait(4)
self.stop_ambient_camera_rotation()
class ThreeDAxesExample(ThreeDScene):
"""3D coordinate axes and plotting."""
def construct(self):
self.set_camera_orientation(phi=70 * DEGREES, theta=-45 * DEGREES)
# Create 3D axes
axes = ThreeDAxes(
x_range=[-3, 3, 1],
y_range=[-3, 3, 1],
z_range=[-2, 2, 1],
x_length=6,
y_length=6,
z_length=4,
)
# Axis labels
x_label = axes.get_x_axis_label(r"x")
y_label = axes.get_y_axis_label(r"y")
z_label = axes.get_z_axis_label(r"z")
self.play(Create(axes))
self.add_fixed_orientation_mobjects(x_label, y_label, z_label)
self.wait()
# Add a point
point = Dot3D(axes.c2p(2, 1, 1.5), color=RED, radius=0.1)
self.play(Create(point))
# Camera rotation
self.begin_ambient_camera_rotation(rate=0.2)
self.wait(5)
class ParametricSurfaceExample(ThreeDScene):
"""3D parametric surface visualization."""
def construct(self):
self.set_camera_orientation(phi=60 * DEGREES, theta=-60 * DEGREES)
axes = ThreeDAxes(
x_range=[-3, 3],
y_range=[-3, 3],
z_range=[-2, 2],
)
# Saddle surface: z = x^2 - y^2
surface = Surface(
lambda u, v: axes.c2p(u, v, u ** 2 - v ** 2),
u_range=[-2, 2],
v_range=[-2, 2],
resolution=(20, 20),
fill_opacity=0.7,
)
surface.set_color_by_gradient(BLUE, GREEN, YELLOW)
self.play(Create(axes))
self.play(Create(surface), run_time=2)
self.begin_ambient_camera_rotation(rate=0.15)
self.wait(5)
class SphereVisualization(ThreeDScene):
"""Sphere with parametric representation."""
def construct(self):
self.set_camera_orientation(phi=70 * DEGREES, theta=30 * DEGREES)
# Parametric sphere
sphere = Surface(
lambda u, v: np.array([
np.cos(v) * np.sin(u),
np.sin(v) * np.sin(u),
np.cos(u)
]),
u_range=[0, PI],
v_range=[0, 2 * PI],
resolution=(20, 40),
)
sphere.set_color_by_gradient(BLUE_E, BLUE, TEAL)
self.play(Create(sphere), run_time=2)
# Animate camera
self.begin_ambient_camera_rotation(rate=0.2)
self.wait(5)
class Function3DPlot(ThreeDScene):
"""Plotting z = f(x, y) surfaces."""
def construct(self):
self.set_camera_orientation(phi=65 * DEGREES, theta=-45 * DEGREES)
axes = ThreeDAxes(
x_range=[-3, 3],
y_range=[-3, 3],
z_range=[-1, 1],
)
# Sine wave surface
surface = Surface(
lambda u, v: axes.c2p(
u, v,
np.sin(np.sqrt(u ** 2 + v ** 2))
),
u_range=[-3, 3],
v_range=[-3, 3],
resolution=(30, 30),
)
surface.set_color_by_gradient(PURPLE, RED, ORANGE)
self.play(Create(axes))
self.play(Create(surface), run_time=2)
self.begin_ambient_camera_rotation(rate=0.1)
self.wait(6)
class VectorField3D(ThreeDScene):
"""3D vector field visualization."""
def construct(self):
self.set_camera_orientation(phi=60 * DEGREES, theta=-45 * DEGREES)
axes = ThreeDAxes(
x_range=[-3, 3],
y_range=[-3, 3],
z_range=[-3, 3],
)
# Create arrows representing a vector field
arrows = VGroup()
for x in np.arange(-2, 3, 1):
for y in np.arange(-2, 3, 1):
for z in np.arange(-2, 3, 1):
# Vector field: F = (-y, x, z)
start = axes.c2p(x, y, z)
direction = np.array([-y, x, z]) * 0.3
end = start + direction
arrow = Arrow3D(
start=start,
end=end,
color=interpolate_color(
BLUE, RED,
(z + 2) / 4
),
)
arrows.add(arrow)
self.play(Create(axes))
self.play(LaggedStart(*[Create(a) for a in arrows], lag_ratio=0.02))
self.begin_ambient_camera_rotation(rate=0.1)
self.wait(5)
class CameraMovement3D(ThreeDScene):
"""Demonstrating 3D camera controls."""
def construct(self):
# Start with a default view
self.set_camera_orientation(phi=75 * DEGREES, theta=-45 * DEGREES)
# Create a 3D object
torus = Torus(
major_radius=2,
minor_radius=0.5,
color=BLUE,
fill_opacity=0.8
)
self.play(Create(torus))
self.wait()
# Move camera to different angles
self.move_camera(phi=30 * DEGREES, theta=0, run_time=2)
self.wait()
self.move_camera(phi=90 * DEGREES, theta=90 * DEGREES, run_time=2)
self.wait()
# Zoom by adjusting frame
self.move_camera(zoom=1.5, run_time=1)
self.wait()
self.move_camera(zoom=0.7, run_time=1)
self.wait()
class Line3DExample(ThreeDScene):
"""3D lines and curves."""
def construct(self):
self.set_camera_orientation(phi=70 * DEGREES, theta=-45 * DEGREES)
axes = ThreeDAxes()
# 3D helix
helix = ParametricFunction(
lambda t: np.array([
np.cos(t),
np.sin(t),
t / 4
]),
t_range=[0, 4 * PI],
color=YELLOW,
)
# Line in 3D
line = Line3D(
start=axes.c2p(-2, -2, -1),
end=axes.c2p(2, 2, 1),
color=RED,
)
self.play(Create(axes))
self.play(Create(helix), run_time=2)
self.play(Create(line))
self.begin_ambient_camera_rotation(rate=0.15)
self.wait(5)
class TextIn3D(ThreeDScene):
"""Text and math in 3D scenes."""
def construct(self):
self.set_camera_orientation(phi=60 * DEGREES, theta=-45 * DEGREES)
axes = ThreeDAxes()
# 3D text (stays fixed to camera)
title = Text("3D Visualization", font_size=48)
title.to_corner(UL)
self.add_fixed_in_frame_mobjects(title)
# Math label fixed to camera
equation = MathTex(r"z = x^2 + y^2")
equation.to_corner(UR)
self.add_fixed_in_frame_mobjects(equation)
# Surface
paraboloid = Surface(
lambda u, v: axes.c2p(u, v, u ** 2 + v ** 2),
u_range=[-1.5, 1.5],
v_range=[-1.5, 1.5],
resolution=(15, 15),
)
paraboloid.set_color_by_gradient(BLUE, GREEN)
self.play(Write(title), Write(equation))
self.play(Create(axes))
self.play(Create(paraboloid))
self.begin_ambient_camera_rotation(rate=0.1)
self.wait(5)
class AnimatedSurface(ThreeDScene):
"""Surface that changes over time."""
def construct(self):
self.set_camera_orientation(phi=65 * DEGREES, theta=-45 * DEGREES)
axes = ThreeDAxes(
x_range=[-3, 3],
y_range=[-3, 3],
z_range=[-2, 2],
)
# Time parameter
time = ValueTracker(0)
# Animated wave surface
surface = always_redraw(
lambda: Surface(
lambda u, v: axes.c2p(
u, v,
np.sin(np.sqrt(u ** 2 + v ** 2) - time.get_value())
),
u_range=[-3, 3],
v_range=[-3, 3],
resolution=(25, 25),
).set_color_by_gradient(BLUE, TEAL)
)
self.add(axes, surface)
# Animate
self.play(
time.animate.set_value(4 * PI),
run_time=8,
rate_func=linear
)
class MultipleObjects3D(ThreeDScene):
"""Combining multiple 3D objects."""
def construct(self):
self.set_camera_orientation(phi=60 * DEGREES, theta=-30 * DEGREES)
# Create various 3D shapes
sphere = Sphere(radius=0.5, color=RED).shift(LEFT * 2 + UP)
cube = Cube(side_length=0.8, color=BLUE).shift(RIGHT * 2)
cylinder = Cylinder(
radius=0.4,
height=1.2,
color=GREEN
).shift(DOWN + LEFT)
# Arrows connecting them
arrow1 = Arrow3D(
start=sphere.get_center(),
end=cube.get_center(),
color=YELLOW
)
arrow2 = Arrow3D(
start=cube.get_center(),
end=cylinder.get_center(),
color=YELLOW
)
self.play(
Create(sphere),
Create(cube),
Create(cylinder),
)
self.play(Create(arrow1), Create(arrow2))
self.begin_ambient_camera_rotation(rate=0.2)
self.wait(5)
# Attention Visualization Package
# Converted from 3b1b ManimGL to ManimCE
"""
Attention Visualization Helpers - Converted from 3b1b ManimGL to ManimCE
Original: videos/_2024/transformers/helpers.py
Contains utility functions and classes for attention visualization.
"""
from manim import *
import numpy as np
import warnings
import random
import itertools as it
from typing import Optional, Tuple
# =============================================================================
# UTILITY FUNCTIONS
# =============================================================================
def softmax(logits, temperature=1.0):
"""Numerically stable softmax function."""
logits = np.array(logits)
with warnings.catch_warnings():
warnings.filterwarnings('ignore')
logits = logits - np.max(logits)
exps = np.exp(np.divide(logits, temperature, where=temperature != 0))
if np.isinf(exps).any() or np.isnan(exps).any() or temperature == 0:
result = np.zeros_like(logits)
result[np.argmax(logits)] = 1
return result
return exps / np.sum(exps)
def value_to_color(
value,
low_positive_color=BLUE_E,
high_positive_color=BLUE_B,
low_negative_color=RED_E,
high_negative_color=RED_B,
min_value=0.0,
max_value=10.0
):
"""Map a numeric value to a color based on sign and magnitude."""
# Clamp alpha between 0 and 1
alpha = max(0, min(1, abs(value - min_value) / (max_value - min_value))) if max_value != min_value else 0.5
if value >= 0:
return interpolate_color(low_positive_color, high_positive_color, alpha)
else:
return interpolate_color(low_negative_color, high_negative_color, alpha)
def get_paragraph(words, line_len=40, font_size=48):
"""Handle word wrapping for text display."""
words = list(map(str.strip, words))
word_lens = list(map(len, words))
lines = []
lh, rh = 0, 0
while rh < len(words):
rh += 1
if sum(word_lens[lh:rh]) > line_len:
rh -= 1
lines.append(words[lh:rh])
lh = rh
lines.append(words[lh:])
text = "\n".join([" ".join(line).strip() for line in lines])
return Text(text, font_size=font_size)
def random_bright_color(hue_range=(0.0, 1.0)):
"""Generate a random bright color within a hue range."""
import colorsys
hue = random.uniform(*hue_range)
rgb = colorsys.hsv_to_rgb(hue, 0.7, 0.9)
return rgb_to_color(rgb)
# =============================================================================
# CUSTOM MOBJECT CLASSES
# =============================================================================
class NumericEmbedding(VGroup):
"""
A vertical vector of decimal numbers representing an embedding.
Displays values with color coding based on magnitude.
"""
def __init__(
self,
values: Optional[np.ndarray] = None,
length: int = 7,
num_decimal_places: int = 1,
value_range: Tuple[float, float] = (-9.9, 9.9),
show_ellipsis: bool = True,
ellipsis_row: int = -2,
dark_color=GREY_C,
light_color=WHITE,
bracket_color=GREY_B,
**kwargs
):
super().__init__(**kwargs)
self.value_range = value_range
self.dark_color = dark_color
self.light_color = light_color
if values is None:
values = np.random.uniform(*value_range, size=length)
self.values = values
self.length = len(values)
# Create decimal number entries
self.elements = VGroup()
for i, val in enumerate(values):
if show_ellipsis and i == (ellipsis_row % len(values)):
entry = MathTex(r"\vdots")
else:
entry = DecimalNumber(
val,
num_decimal_places=num_decimal_places,
include_sign=True,
font_size=36
)
# Color based on value
alpha = abs(val) / max(abs(value_range[0]), abs(value_range[1]))
entry.set_color(interpolate_color(dark_color, light_color, alpha))
self.elements.add(entry)
self.elements.arrange(DOWN, buff=0.15)
# Add brackets
self.left_bracket = MathTex(r"\left[")
self.right_bracket = MathTex(r"\right]")
self.left_bracket.stretch_to_fit_height(self.elements.get_height() * 1.1)
self.right_bracket.stretch_to_fit_height(self.elements.get_height() * 1.1)
self.left_bracket.next_to(self.elements, LEFT, buff=0.1)
self.right_bracket.next_to(self.elements, RIGHT, buff=0.1)
self.left_bracket.set_color(bracket_color)
self.right_bracket.set_color(bracket_color)
self.add(self.left_bracket, self.elements, self.right_bracket)
def get_brackets(self):
return VGroup(self.left_bracket, self.right_bracket)
def get_entries(self):
return self.elements
class WeightMatrix(VGroup):
"""
A matrix of decimal numbers with color-coded entries.
Used to represent weight matrices in neural networks.
"""
def __init__(
self,
values: Optional[np.ndarray] = None,
shape: Tuple[int, int] = (6, 8),
value_range: Tuple[float, float] = (-9.9, 9.9),
num_decimal_places: int = 1,
show_ellipsis: bool = True,
ellipsis_row: int = -2,
ellipsis_col: int = -2,
low_positive_color=BLUE_E,
high_positive_color=BLUE_B,
low_negative_color=RED_E,
high_negative_color=RED_B,
**kwargs
):
super().__init__(**kwargs)
self.shape = shape
self.value_range = value_range
self.low_positive_color = low_positive_color
self.high_positive_color = high_positive_color
self.low_negative_color = low_negative_color
self.high_negative_color = high_negative_color
if values is None:
values = np.random.uniform(*value_range, size=shape)
self.values = values
# Create matrix entries
self.rows = VGroup()
n_rows, n_cols = shape
for i in range(n_rows):
row = VGroup()
for j in range(n_cols):
if show_ellipsis and i == (ellipsis_row % n_rows):
entry = MathTex(r"\vdots")
elif show_ellipsis and j == (ellipsis_col % n_cols):
entry = MathTex(r"\cdots")
else:
val = values[i, j]
entry = DecimalNumber(
val,
num_decimal_places=num_decimal_places,
include_sign=True,
font_size=24
)
entry.set_color(value_to_color(
val,
low_positive_color,
high_positive_color,
low_negative_color,
high_negative_color,
0, max(abs(value_range[0]), abs(value_range[1]))
))
row.add(entry)
row.arrange(RIGHT, buff=0.2)
self.rows.add(row)
self.rows.arrange(DOWN, buff=0.15)
# Add brackets
self.left_bracket = MathTex(r"\left[")
self.right_bracket = MathTex(r"\right]")
self.left_bracket.stretch_to_fit_height(self.rows.get_height() * 1.1)
self.right_bracket.stretch_to_fit_height(self.rows.get_height() * 1.1)
self.left_bracket.next_to(self.rows, LEFT, buff=0.1)
self.right_bracket.next_to(self.rows, RIGHT, buff=0.1)
self.add(self.left_bracket, self.rows, self.right_bracket)
def get_entries(self):
entries = VGroup()
for row in self.rows:
for entry in row:
if isinstance(entry, DecimalNumber):
entries.add(entry)
return entries
def get_rows(self):
return self.rows
class ContextAnimation(LaggedStart):
"""
Animation showing context flow from source words to target word.
Creates arcing lines that flash from sources to target.
"""
def __init__(
self,
target,
sources,
direction=UP,
time_width=2,
min_stroke_width=1,
max_stroke_width=5,
strengths=None,
run_time=3,
path_arc=PI / 2,
**kwargs,
):
arcs = VGroup()
if strengths is None:
strengths = np.random.random(len(sources)) ** 2
for source, strength in zip(sources, strengths):
sign = direction[1] * (-1) ** int(source.get_x() < target.get_x())
arc = Line(
source.get_edge_center(direction),
target.get_edge_center(direction),
path_arc=sign * path_arc,
)
arc.set_stroke(
color=random_bright_color(hue_range=(0.1, 0.3)),
width=interpolate(min_stroke_width, max_stroke_width, strength)
)
arcs.add(arc)
arcs.shuffle()
lag_ratio = 0.5 / max(len(arcs), 1)
super().__init__(
*[
ShowPassingFlash(arc, time_width=time_width)
for arc in arcs
],
lag_ratio=lag_ratio,
run_time=run_time,
**kwargs,
)
class NeuralNetwork(VGroup):
"""
Visual representation of a neural network with layers and connections.
"""
def __init__(
self,
layer_sizes=[6, 12, 6],
neuron_radius=0.1,
v_buff=0.3,
h_buff=1.5,
max_stroke_width=2.0,
**kwargs
):
super().__init__(**kwargs)
self.max_stroke_width = max_stroke_width
# Create layers
self.layers = VGroup()
for n in layer_sizes:
layer = VGroup(*[
Circle(radius=neuron_radius, color=WHITE, fill_opacity=random.random())
for _ in range(n)
])
layer.arrange(DOWN, buff=v_buff)
self.layers.add(layer)
self.layers.arrange(RIGHT, buff=h_buff)
# Create connections
self.lines = VGroup()
for l1, l2 in zip(self.layers, self.layers[1:]):
layer_lines = VGroup()
for n1 in l1:
for n2 in l2:
line = Line(
n1.get_center(),
n2.get_center(),
buff=neuron_radius
)
line.set_stroke(
color=value_to_color(random.uniform(-10, 10)),
width=max_stroke_width * random.random(),
opacity=random.random() ** 2
)
layer_lines.add(line)
self.lines.add(layer_lines)
self.add(self.lines, self.layers)
class AttentionPattern(VGroup):
"""
Visual representation of attention weights between tokens.
Shows which tokens attend to which with varying line widths.
"""
def __init__(
self,
n_tokens=8,
token_labels=None,
attention_weights=None,
**kwargs
):
super().__init__(**kwargs)
if token_labels is None:
token_labels = [f"T{i}" for i in range(n_tokens)]
if attention_weights is None:
# Random attention pattern
attention_weights = softmax(np.random.randn(n_tokens, n_tokens), temperature=0.5)
# Create token representations
self.tokens = VGroup()
for label in token_labels:
token = VGroup(
Square(side_length=0.8, color=BLUE, fill_opacity=0.3),
Text(label, font_size=24)
)
token[1].move_to(token[0])
self.tokens.add(token)
self.tokens.arrange(RIGHT, buff=0.5)
# Create attention lines (simplified - just showing strongest connections)
self.attention_lines = VGroup()
for i in range(n_tokens):
for j in range(n_tokens):
if attention_weights[i, j] > 0.1: # Threshold
line = Line(
self.tokens[i].get_bottom(),
self.tokens[j].get_bottom(),
path_arc=-0.5,
)
line.set_stroke(
color=YELLOW,
width=attention_weights[i, j] * 5,
opacity=attention_weights[i, j]
)
self.attention_lines.add(line)
self.add(self.tokens, self.attention_lines)
# =============================================================================
# ANIMATION HELPERS
# =============================================================================
class RandomizeMatrixEntries(Animation):
"""Animation that smoothly randomizes matrix entries."""
def __init__(self, matrix, **kwargs):
self.matrix = matrix
self.entries = matrix.get_entries()
self.start_values = [
entry.get_value() if hasattr(entry, 'get_value') else 0
for entry in self.entries
]
self.target_values = np.random.uniform(
matrix.value_range[0],
matrix.value_range[1],
len(self.entries)
)
super().__init__(matrix, **kwargs)
def interpolate_mobject(self, alpha: float) -> None:
for index, entry in enumerate(self.entries):
if hasattr(entry, 'set_value'):
start = self.start_values[index]
target = self.target_values[index]
entry.set_value(interpolate(start, target, alpha))
def show_attention_flow(scene, source_mobs, target_mob, weights=None, run_time=2):
"""Helper to animate attention flow from multiple sources to a target."""
if weights is None:
weights = np.random.random(len(source_mobs))
weights = weights / weights.sum()
arrows = VGroup()
for source, weight in zip(source_mobs, weights):
arrow = CurvedArrow(
source.get_top(),
target_mob.get_top(),
angle=-TAU/4
)
arrow.set_stroke(width=weight * 5, color=YELLOW)
arrow.set_opacity(weight)
arrows.add(arrow)
scene.play(
LaggedStart(*[Create(a) for a in arrows], lag_ratio=0.2),
run_time=run_time
)
return arrows
"""
Attention Mechanism Visualization - Converted from 3b1b ManimGL to ManimCE
Original: videos/_2024/transformers/attention.py
Demonstrates the attention mechanism used in transformers.
Run with: manim -pql scenes.py SceneName
"""
from manim import *
import numpy as np
import sys
from pathlib import Path
# Add parent directory to path for helpers import
sys.path.insert(0, str(Path(__file__).parent))
from helpers import (
NumericEmbedding, WeightMatrix, ContextAnimation,
NeuralNetwork, AttentionPattern, softmax, value_to_color,
random_bright_color, show_attention_flow
)
class AttentionPatterns(Scene):
"""
Demonstrates how attention allows words to influence each other.
Shows adjectives modifying nouns through attention connections.
"""
def construct(self):
# Add sentence
phrase = "a fluffy blue creature roamed the verdant forest"
phrase_mob = Text(phrase, font_size=36)
phrase_mob.move_to(2 * UP)
words = phrase.split()
word_mobs = VGroup()
# Create individual word mobjects
current_x = phrase_mob.get_left()[0]
for word in words:
# Find the word in the phrase
word_mob = Text(word, font_size=36)
word_mobs.add(word_mob)
word_mobs.arrange(RIGHT, buff=0.3)
word_mobs.move_to(2 * UP)
self.play(
LaggedStart(*[FadeIn(w, shift=0.5 * UP) for w in word_mobs], lag_ratio=0.15)
)
self.wait()
# Create word rectangles
word_rects = VGroup()
for word_mob in word_mobs:
rect = SurroundingRectangle(word_mob, buff=0.1)
rect.set_stroke(GREY, 2)
rect.set_fill(GREY, 0.2)
word_rects.add(rect)
# Identify adjectives and nouns
adj_indices = [1, 2, 6] # fluffy, blue, verdant
noun_indices = [3, 7] # creature, forest
adj_rects = VGroup(*[word_rects[i] for i in adj_indices])
noun_rects = VGroup(*[word_rects[i] for i in noun_indices])
adj_mobs = VGroup(*[word_mobs[i] for i in adj_indices])
noun_mobs = VGroup(*[word_mobs[i] for i in noun_indices])
# Color the rectangles
adj_rects[0].set_fill(BLUE_C, 0.3)
adj_rects[1].set_fill(BLUE_D, 0.3)
adj_rects[2].set_fill(GREEN, 0.3)
noun_rects.set_fill(GREY_BROWN, 0.3)
self.play(
LaggedStart(*[DrawBorderThenFill(r) for r in adj_rects], lag_ratio=0.2),
)
self.wait()
# Show arrows from adjectives to nouns
adj_arrows = VGroup(
CurvedArrow(adj_mobs[0].get_top(), noun_mobs[0].get_top(), angle=-0.5),
CurvedArrow(adj_mobs[1].get_top(), noun_mobs[0].get_top(), angle=-0.5),
CurvedArrow(adj_mobs[2].get_top(), noun_mobs[1].get_top(), angle=-0.5),
)
adj_arrows.set_color(GREY_B)
self.play(
LaggedStart(*[DrawBorderThenFill(r) for r in noun_rects], lag_ratio=0.2),
LaggedStart(*[Create(a) for a in adj_arrows], lag_ratio=0.2),
)
self.wait()
# Animate context flow
self.play(
ContextAnimation(noun_mobs[0], adj_mobs[:2], strengths=[1, 1]),
ContextAnimation(noun_mobs[1], adj_mobs[2:], strengths=[1]),
)
self.wait()
# Show embeddings
all_rects = word_rects.copy()
embeddings = VGroup()
for rect in all_rects:
emb = NumericEmbedding(length=8)
emb.set_width(0.4)
emb.next_to(rect, DOWN, buff=1.2)
embeddings.add(emb)
emb_arrows = VGroup()
for rect, emb in zip(all_rects, embeddings):
arrow = Arrow(rect.get_bottom(), emb.get_top(), buff=0.1)
emb_arrows.add(arrow)
self.play(
FadeIn(word_rects),
LaggedStart(*[GrowArrow(a) for a in emb_arrows], lag_ratio=0.1),
LaggedStart(*[FadeIn(e, shift=0.5 * DOWN) for e in embeddings], lag_ratio=0.1),
FadeOut(adj_arrows)
)
self.wait()
# Show embedding dimension
brace = Brace(embeddings[0], LEFT, buff=SMALL_BUFF)
dim_label = Text("12,288", font_size=24, color=YELLOW)
dim_label.next_to(brace, LEFT)
self.play(
GrowFromCenter(brace),
FadeIn(dim_label)
)
self.wait(2)
class QueryKeyValueExplanation(Scene):
"""
Explains the Query, Key, Value mechanism in attention.
"""
def construct(self):
# Title
title = Text("Query, Key, Value", font_size=48)
title.to_edge(UP)
self.play(Write(title))
self.wait()
# Create three matrices
q_matrix = WeightMatrix(shape=(4, 4), value_range=(-5, 5))
k_matrix = WeightMatrix(shape=(4, 4), value_range=(-5, 5))
v_matrix = WeightMatrix(shape=(4, 4), value_range=(-5, 5))
matrices = VGroup(q_matrix, k_matrix, v_matrix)
matrices.arrange(RIGHT, buff=1)
matrices.set_height(2)
matrices.next_to(title, DOWN, buff=0.8)
# Labels
q_label = Text("Query (Q)", font_size=30, color=BLUE)
k_label = Text("Key (K)", font_size=30, color=GREEN)
v_label = Text("Value (V)", font_size=30, color=RED)
q_label.next_to(q_matrix, UP)
k_label.next_to(k_matrix, UP)
v_label.next_to(v_matrix, UP)
self.play(
FadeIn(q_matrix),
Write(q_label),
)
self.wait()
self.play(
FadeIn(k_matrix),
Write(k_label),
)
self.wait()
self.play(
FadeIn(v_matrix),
Write(v_label),
)
self.wait()
# Explain the formula
formula = MathTex(
r"\text{Attention}(Q, K, V) = \text{softmax}\left(\frac{QK^T}{\sqrt{d_k}}\right)V",
font_size=36
)
formula.next_to(matrices, DOWN, buff=1)
self.play(Write(formula))
self.wait(2)
# Highlight different parts
# Q*K^T computes similarity
explanation1 = Text("Q·K^T → measures similarity between queries and keys", font_size=24)
explanation1.next_to(formula, DOWN, buff=0.5)
self.play(Write(explanation1))
self.wait()
# Softmax normalizes
explanation2 = Text("Softmax → converts to attention weights (probabilities)", font_size=24)
explanation2.next_to(explanation1, DOWN, buff=0.3)
self.play(Write(explanation2))
self.wait()
# Multiply by V
explanation3 = Text("× V → weighted sum of values", font_size=24)
explanation3.next_to(explanation2, DOWN, buff=0.3)
self.play(Write(explanation3))
self.wait(2)
class AttentionMatrixVisualization(Scene):
"""
Shows how attention scores form a matrix pattern.
"""
def construct(self):
# Create tokens
tokens = ["The", "cat", "sat", "on", "the", "mat"]
n = len(tokens)
# Token labels on left (queries)
query_labels = VGroup(*[Text(t, font_size=24) for t in tokens])
query_labels.arrange(DOWN, buff=0.4)
query_labels.shift(LEFT * 4)
# Token labels on top (keys)
key_labels = VGroup(*[Text(t, font_size=24) for t in tokens])
key_labels.arrange(RIGHT, buff=0.4)
key_labels.next_to(query_labels, RIGHT, buff=1)
key_labels.shift(UP * 2)
# Create attention grid
attention_scores = softmax(np.random.randn(n, n) * 2, temperature=0.3)
grid = VGroup()
for i in range(n):
row = VGroup()
for j in range(n):
score = attention_scores[i, j]
cell = Square(side_length=0.5)
cell.set_fill(
color=interpolate_color(BLACK, YELLOW, score),
opacity=0.8
)
cell.set_stroke(WHITE, 0.5)
row.add(cell)
row.arrange(RIGHT, buff=0)
grid.add(row)
grid.arrange(DOWN, buff=0)
grid.next_to(query_labels, RIGHT, buff=0.5)
grid.align_to(query_labels, UP)
# Adjust key labels position
key_labels.move_to(grid.get_top() + UP * 0.5)
key_labels.align_to(grid, LEFT)
# Title
title = Text("Attention Matrix", font_size=36)
title.to_edge(UP)
self.play(Write(title))
self.play(
LaggedStart(*[FadeIn(l) for l in query_labels], lag_ratio=0.1),
LaggedStart(*[FadeIn(l) for l in key_labels], lag_ratio=0.1),
)
self.wait()
# Animate grid appearing
all_cells = VGroup(*[cell for row in grid for cell in row])
self.play(
LaggedStart(*[FadeIn(c, scale=0.5) for c in all_cells], lag_ratio=0.02)
)
self.wait()
# Highlight a row (how "cat" attends to all words)
highlight_row = 1 # "cat"
row_highlight = SurroundingRectangle(grid[highlight_row], color=BLUE, buff=0.05)
explanation = Text(
'"cat" attends mostly to itself and "sat"',
font_size=24
)
explanation.next_to(grid, DOWN, buff=0.5)
self.play(Create(row_highlight), Write(explanation))
self.wait(2)
class MultiHeadedAttention(ThreeDScene):
"""
Explains multi-head attention mechanism with 3D visualization.
Shows multiple attention heads arranged in depth with camera rotation.
Inspired by 3b1b's transformer visualization.
"""
def construct(self):
# Title animation: Single head -> Multi-head
single_title = Text("Single head of attention", font_size=42)
multiple_title = Text("Multi-headed attention", font_size=42)
for title in [single_title, multiple_title]:
title.to_edge(UP)
self.play(Write(single_title))
self.wait(0.5)
# Flash around "head"
head_text = single_title[7:11] # "head"
self.play(
Indicate(head_text, color=YELLOW, scale_factor=1.2),
head_text.animate.set_color(YELLOW),
)
self.wait(0.5)
# Transform title
self.play(
TransformMatchingShapes(single_title, multiple_title),
run_time=1.5
)
self.wait()
# Create attention pattern visualization (grid with dots)
def create_attention_pattern(n_rows=8, seed=None):
"""Create a grid visualization of attention weights."""
if seed is not None:
np.random.seed(seed)
# Create the base grid
grid = VGroup()
cell_size = 0.4
for i in range(n_rows):
for j in range(n_rows):
cell = Square(side_length=cell_size)
cell.set_stroke(WHITE, 0.5, opacity=0.3)
cell.move_to(np.array([j * cell_size, -i * cell_size, 0]))
grid.add(cell)
grid.center()
# Generate causal attention pattern (lower triangular)
pattern = np.random.normal(0, 1, (n_rows, n_rows))
for n in range(n_rows):
pattern[:, n][n + 1:] = -np.inf # Mask future tokens
exp_vals = np.exp(pattern[:, n] - np.max(pattern[:, n][pattern[:, n] > -np.inf]))
pattern[:, n] = exp_vals / np.sum(exp_vals[exp_vals < np.inf])
pattern = np.nan_to_num(pattern, nan=0.0, posinf=0.0, neginf=0.0)
# Add dots based on attention weights
dots = VGroup()
for i in range(n_rows):
for j in range(n_rows):
value = pattern[i, j]
if value > 0.05: # Threshold for visibility
dot = Dot(
radius=cell_size * 0.4 * value,
color=GREY_B,
fill_opacity=0.8
)
dot.move_to(grid[i * n_rows + j].get_center())
dots.add(dot)
# Create border rectangle
border = SurroundingRectangle(grid, buff=0.05)
border.set_stroke(WHITE, 2)
border.set_fill(BLACK, 0.9)
pattern_mob = VGroup(border, grid, dots)
return pattern_mob
# Create multiple attention heads
n_heads = 12
heads = VGroup()
for i in range(n_heads):
head = create_attention_pattern(n_rows=6, seed=i * 42)
head.set_height(2.5)
heads.add(head)
# Arrange in 3D depth (along z-axis)
for i, head in enumerate(heads):
head.shift(OUT * i * 0.5) # Stack in z direction
heads.center()
heads.shift(DOWN * 0.5)
# Show first head (screen rectangle style)
first_head = heads[-1].copy()
first_head.move_to(ORIGIN + DOWN * 0.5)
first_head.shift(IN * (n_heads - 1) * 0.25) # Reset z position
self.play(FadeIn(first_head))
self.wait()
# Add fixed-in-frame elements
self.add_fixed_in_frame_mobjects(multiple_title)
# Rotate camera to reveal depth
self.move_camera(
phi=70 * DEGREES,
theta=-60 * DEGREES,
run_time=2
)
# Fan out the heads from the first one
self.play(
LaggedStart(
*[FadeIn(head, shift=OUT * 0.3) for head in heads[:-1]],
lag_ratio=0.15
),
FadeOut(first_head),
run_time=3
)
self.add(heads)
self.wait()
# Add matrix labels for each head (W_Q, W_K)
wq_labels = VGroup()
wk_labels = VGroup()
colors = [YELLOW, TEAL]
n_shown = min(5, n_heads)
for i, head in enumerate(list(heads)[-n_shown:]):
head_num = n_heads - n_shown + i + 1
wq = MathTex(f"W_Q^{{({head_num})}}", font_size=28, color=YELLOW)
wk = MathTex(f"W_K^{{({head_num})}}", font_size=28, color=TEAL)
# Position above each head
wq.next_to(head, UP, buff=0.2)
wq.shift(LEFT * 0.3)
wk.next_to(head, UP, buff=0.2)
wk.shift(RIGHT * 0.3)
# Rotate to face camera
for label in [wq, wk]:
label.rotate(70 * DEGREES, axis=RIGHT)
label.rotate(-60 * DEGREES, axis=OUT)
wq_labels.add(wq)
wk_labels.add(wk)
# Add dots to indicate more heads
dots_label = MathTex(r"\cdots", font_size=48, color=WHITE)
dots_label.next_to(heads[0], OUT, buff=0.5)
dots_label.rotate(70 * DEGREES, axis=RIGHT)
dots_label.rotate(-60 * DEGREES, axis=OUT)
self.play(
LaggedStart(*[FadeIn(wq, shift=UP * 0.2) for wq in wq_labels], lag_ratio=0.2),
run_time=1.5
)
self.play(
LaggedStart(*[FadeIn(wk, shift=UP * 0.2) for wk in wk_labels], lag_ratio=0.2),
FadeIn(dots_label),
run_time=1.5
)
self.wait()
# Add brace showing "96 heads" (scaled down for demonstration)
brace_text = Text("96 heads", font_size=36, color=WHITE)
brace_text.rotate(70 * DEGREES, axis=RIGHT)
brace_text.rotate(-60 * DEGREES, axis=OUT)
brace_text.next_to(heads, UP, buff=0.8)
brace_text.shift(LEFT * 2)
self.play(FadeIn(brace_text, shift=UP * 0.3))
self.wait()
# Rotate camera to show different angle
self.move_camera(
phi=60 * DEGREES,
theta=-80 * DEGREES,
run_time=2
)
self.wait()
# Explanation text (fixed in frame)
explanation = VGroup(
Text("Each head learns different patterns:", font_size=24),
Text("• Syntactic relationships", font_size=20, color=BLUE),
Text("• Semantic connections", font_size=20, color=GREEN),
Text("• Positional patterns", font_size=20, color=YELLOW),
)
explanation.arrange(DOWN, aligned_edge=LEFT, buff=0.15)
explanation.to_corner(DL, buff=0.5)
self.add_fixed_in_frame_mobjects(explanation)
self.play(
LaggedStart(*[Write(e) for e in explanation], lag_ratio=0.3)
)
self.wait()
# Return to front view
self.move_camera(
phi=0,
theta=-90 * DEGREES,
run_time=2
)
self.wait()
# Show concatenation concept
concat_text = Text("Concatenate outputs from all heads", font_size=28)
concat_text.to_edge(DOWN, buff=0.5)
self.add_fixed_in_frame_mobjects(concat_text)
self.play(Write(concat_text))
self.wait()
# Final hold
self.wait()
self.wait(2)
class SelfAttentionDemo(Scene):
"""
Interactive demonstration of self-attention on a simple sentence.
"""
def construct(self):
# Title
title = Text("Self-Attention in Action", font_size=42)
title.to_edge(UP)
self.play(Write(title))
# Create sentence
sentence = "The quick brown fox"
words = sentence.split()
word_boxes = VGroup()
for word in words:
box = VGroup(
RoundedRectangle(
width=1.5, height=0.8,
corner_radius=0.1,
fill_opacity=0.3,
fill_color=BLUE,
stroke_color=WHITE
),
Text(word, font_size=28)
)
box[1].move_to(box[0])
word_boxes.add(box)
word_boxes.arrange(RIGHT, buff=0.5)
word_boxes.next_to(title, DOWN, buff=1)
self.play(
LaggedStart(*[FadeIn(b, scale=0.8) for b in word_boxes], lag_ratio=0.2)
)
self.wait()
# Show attention from "fox" to other words
target_idx = 3 # "fox"
attention_weights = [0.1, 0.3, 0.4, 0.2] # Attention weights
# Highlight target
target_box = word_boxes[target_idx]
target_highlight = SurroundingRectangle(target_box, color=YELLOW, buff=0.1)
self.play(Create(target_highlight))
# Create attention arrows
attention_arrows = VGroup()
weight_labels = VGroup()
for i, (box, weight) in enumerate(zip(word_boxes, attention_weights)):
if i != target_idx:
arrow = CurvedArrow(
box.get_bottom() + DOWN * 0.1,
target_box.get_bottom() + DOWN * 0.1,
angle=0.5 if i < target_idx else -0.5
)
arrow.set_stroke(
color=interpolate_color(GREY, YELLOW, weight),
width=weight * 8
)
attention_arrows.add(arrow)
label = DecimalNumber(weight, num_decimal_places=1, font_size=20)
label.next_to(arrow.point_from_proportion(0.5), DOWN, buff=0.1)
weight_labels.add(label)
self.play(
LaggedStart(*[Create(a) for a in attention_arrows], lag_ratio=0.2),
LaggedStart(*[FadeIn(l) for l in weight_labels], lag_ratio=0.2),
)
self.wait()
# Show weighted combination
result_text = Text(
'"fox" = 0.1×"The" + 0.3×"quick" + 0.4×"brown" + 0.2×"fox"',
font_size=24
)
result_text.next_to(word_boxes, DOWN, buff=1.5)
self.play(Write(result_text))
self.wait(2)
class ScaledDotProductAttention(Scene):
"""
Step-by-step visualization of scaled dot-product attention.
"""
def construct(self):
# Title
title = Text("Scaled Dot-Product Attention", font_size=40)
title.to_edge(UP)
self.play(Write(title))
# Step 1: Show Q, K, V
step1 = Text("Step 1: Compute Q, K, V from input", font_size=28)
step1.next_to(title, DOWN, buff=0.5)
q_vec = NumericEmbedding(length=4).set_height(1.5)
k_vec = NumericEmbedding(length=4).set_height(1.5)
v_vec = NumericEmbedding(length=4).set_height(1.5)
vectors = VGroup(q_vec, k_vec, v_vec)
vectors.arrange(RIGHT, buff=1)
vectors.next_to(step1, DOWN, buff=0.5)
q_label = Text("Q", color=BLUE, font_size=24).next_to(q_vec, UP)
k_label = Text("K", color=GREEN, font_size=24).next_to(k_vec, UP)
v_label = Text("V", color=RED, font_size=24).next_to(v_vec, UP)
self.play(Write(step1))
self.play(
FadeIn(q_vec), FadeIn(k_vec), FadeIn(v_vec),
Write(q_label), Write(k_label), Write(v_label)
)
self.wait()
# Step 2: Compute Q·K^T
self.play(
FadeOut(step1),
VGroup(vectors, q_label, k_label, v_label).animate.shift(UP)
)
step2 = Text("Step 2: Q · K^T (dot product)", font_size=28)
step2.next_to(title, DOWN, buff=0.5)
dot_product = MathTex(r"Q \cdot K^T = ", font_size=36)
score = DecimalNumber(2.5, font_size=36, color=YELLOW)
dot_result = VGroup(dot_product, score).arrange(RIGHT)
dot_result.next_to(vectors, DOWN, buff=0.5)
self.play(Write(step2))
self.play(Write(dot_product), FadeIn(score))
self.wait()
# Step 3: Scale
self.play(FadeOut(step2))
step3 = Text("Step 3: Scale by √d_k", font_size=28)
step3.next_to(title, DOWN, buff=0.5)
scale_formula = MathTex(r"\frac{Q \cdot K^T}{\sqrt{d_k}} = \frac{2.5}{\sqrt{4}} = 1.25", font_size=32)
scale_formula.next_to(dot_result, DOWN, buff=0.3)
self.play(Write(step3))
self.play(Write(scale_formula))
self.wait()
# Step 4: Softmax
self.play(FadeOut(step3))
step4 = Text("Step 4: Softmax → attention weights", font_size=28)
step4.next_to(title, DOWN, buff=0.5)
softmax_text = MathTex(r"\text{softmax}(1.25) \rightarrow \text{weights}", font_size=32)
softmax_text.next_to(scale_formula, DOWN, buff=0.3)
self.play(Write(step4))
self.play(Write(softmax_text))
self.wait()
# Step 5: Multiply by V
self.play(FadeOut(step4))
step5 = Text("Step 5: Weighted sum of V", font_size=28)
step5.next_to(title, DOWN, buff=0.5)
final = MathTex(r"\text{Output} = \text{weights} \times V", font_size=32)
final.next_to(softmax_text, DOWN, buff=0.3)
self.play(Write(step5))
self.play(Write(final))
self.wait(2)
class PositionalEncoding(Scene):
"""
Explains positional encoding in transformers.
"""
def construct(self):
title = Text("Positional Encoding", font_size=42)
title.to_edge(UP)
self.play(Write(title))
# Problem statement
problem = Text(
"Problem: Attention has no sense of word order!",
font_size=28, color=RED
)
problem.next_to(title, DOWN, buff=0.5)
self.play(Write(problem))
self.wait()
# Show two sentences
sent1 = Text('"The cat ate the fish"', font_size=24)
sent2 = Text('"The fish ate the cat"', font_size=24)
sents = VGroup(sent1, sent2).arrange(DOWN, buff=0.3)
sents.next_to(problem, DOWN, buff=0.5)
self.play(Write(sent1), Write(sent2))
self.wait()
# Show they have same words
same = Text("Same words, different meanings!", font_size=24, color=YELLOW)
same.next_to(sents, DOWN, buff=0.3)
self.play(Write(same))
self.wait()
# Solution
self.play(FadeOut(problem), FadeOut(sents), FadeOut(same))
solution = Text(
"Solution: Add position information to embeddings",
font_size=28, color=GREEN
)
solution.next_to(title, DOWN, buff=0.5)
self.play(Write(solution))
# Show formula
formula = MathTex(
r"PE_{(pos, 2i)} &= \sin\left(\frac{pos}{10000^{2i/d}}\right) \\",
r"PE_{(pos, 2i+1)} &= \cos\left(\frac{pos}{10000^{2i/d}}\right)",
font_size=32
)
formula.next_to(solution, DOWN, buff=0.5)
self.play(Write(formula))
self.wait()
# Visual representation
positions = VGroup()
for i in range(5):
pos_vec = VGroup()
for j in range(8):
val = np.sin(i / (10000 ** (j / 8))) if j % 2 == 0 else np.cos(i / (10000 ** (j / 8)))
cell = Square(side_length=0.3)
cell.set_fill(interpolate_color(BLUE, RED, (val + 1) / 2), opacity=0.8)
cell.set_stroke(WHITE, 0.5)
pos_vec.add(cell)
pos_vec.arrange(DOWN, buff=0)
positions.add(pos_vec)
positions.arrange(RIGHT, buff=0.2)
positions.set_height(2)
positions.next_to(formula, DOWN, buff=0.5)
pos_labels = VGroup(*[
Text(f"pos={i}", font_size=16).next_to(p, DOWN, buff=0.1)
for i, p in enumerate(positions)
])
self.play(
LaggedStart(*[FadeIn(p) for p in positions], lag_ratio=0.1),
LaggedStart(*[FadeIn(l) for l in pos_labels], lag_ratio=0.1),
)
self.wait(2)
# Additional simplified scenes for the key concepts
class WhatIsAttention(Scene):
"""Simple introduction to attention."""
def construct(self):
title = Text("What is Attention?", font_size=48)
title.to_edge(UP)
self.play(Write(title))
# Key idea
idea = Text(
"Attention lets each word look at other words\nto understand context",
font_size=32, line_spacing=1.5
)
idea.next_to(title, DOWN, buff=1)
self.play(Write(idea))
self.wait()
# Example
example_sentence = Text("The bank was steep", font_size=36)
example_sentence.next_to(idea, DOWN, buff=1)
self.play(Write(example_sentence))
self.wait()
# Highlight "bank" and "steep"
bank_box = SurroundingRectangle(
example_sentence[4:8], # "bank"
color=YELLOW, buff=0.05
)
steep_box = SurroundingRectangle(
example_sentence[13:18], # "steep"
color=GREEN, buff=0.05
)
self.play(Create(bank_box))
self.wait()
arrow = CurvedArrow(
steep_box.get_top(),
bank_box.get_top(),
angle=-0.5,
color=YELLOW
)
self.play(Create(steep_box), Create(arrow))
meaning = Text(
'"steep" helps us know "bank" means riverbank, not financial bank',
font_size=24, color=GREY_B
)
meaning.next_to(example_sentence, DOWN, buff=0.8)
self.play(Write(meaning))
self.wait(2)
"""
Basic Animation Patterns for Manim Community
This file demonstrates fundamental animation techniques adapted from 3b1b patterns.
Run with: manim -pql basic_animations.py SceneName
"""
from manim import *
class ShapeCreation(Scene):
"""Demonstrates various ways to create and animate shapes."""
def construct(self):
# Create shapes
circle = Circle(radius=1, color=BLUE, fill_opacity=0.5)
square = Square(side_length=2, color=RED)
triangle = Triangle(color=GREEN, fill_opacity=0.8)
# Arrange shapes
shapes = VGroup(circle, square, triangle).arrange(RIGHT, buff=1)
# Different creation animations
self.play(Create(circle)) # Draw outline progressively
self.play(DrawBorderThenFill(square)) # Border first, then fill
self.play(GrowFromCenter(triangle)) # Grow from center point
self.wait()
# Transform between shapes
self.play(Transform(circle, square.copy().shift(UP * 2)))
self.wait()
class TextAnimations(Scene):
"""Demonstrates text and LaTeX animations."""
def construct(self):
# Plain text
title = Text("Manim Community", font_size=72, color=BLUE)
self.play(Write(title))
self.wait()
# Move title up
self.play(title.animate.to_edge(UP))
# LaTeX math
equation = MathTex(r"e^{i\pi} + 1 = 0", font_size=64)
self.play(Write(equation))
self.wait()
# Transform equation
expanded = MathTex(r"e^{i\pi} = -1", font_size=64)
self.play(TransformMatchingTex(equation, expanded))
self.wait()
class LaggedAnimations(Scene):
"""Demonstrates staggered animations using LaggedStart patterns."""
def construct(self):
# Create a grid of dots
dots = VGroup(*[
Dot(radius=0.15, color=interpolate_color(BLUE, RED, i / 24))
for i in range(25)
]).arrange_in_grid(rows=5, cols=5, buff=0.5)
# Staggered fade in
self.play(
LaggedStart(*[FadeIn(dot, scale=0.5) for dot in dots], lag_ratio=0.1)
)
self.wait()
# Staggered transformation using LaggedStart with animate
self.play(
LaggedStart(
*[dot.animate.scale(1.5).set_color(YELLOW) for dot in dots],
lag_ratio=0.05
)
)
self.wait()
# Wave effect using AnimationGroup with rate_func
self.play(
LaggedStart(
*[dot.animate(rate_func=there_and_back).shift(UP * 0.5) for dot in dots],
lag_ratio=0.02,
run_time=2
)
)
class AnimationComposition(Scene):
"""Demonstrates combining multiple animations."""
def construct(self):
# Create objects
circle = Circle(color=BLUE, fill_opacity=0.5)
label = Text("Circle", font_size=36).next_to(circle, DOWN)
# Group them
group = VGroup(circle, label)
# Animate together
self.play(
Create(circle),
Write(label),
run_time=2
)
self.wait()
# Sequential animations with Succession
square = Square(color=RED, fill_opacity=0.5).shift(RIGHT * 3)
square_label = Text("Square", font_size=36).next_to(square, DOWN)
self.play(
Succession(
group.animate.shift(LEFT * 2),
Create(square),
Write(square_label),
lag_ratio=0.5
)
)
self.wait()
class PathAnimations(Scene):
"""Demonstrates movement along paths."""
def construct(self):
# Create a path
path = VMobject()
path.set_points_smoothly([
LEFT * 3,
LEFT * 2 + UP * 2,
ORIGIN + UP,
RIGHT * 2 + UP * 2,
RIGHT * 3,
])
path.set_color(GREY)
# Create moving object
dot = Dot(color=RED, radius=0.2)
dot.move_to(path.get_start())
self.add(path)
self.play(Create(path))
# Move along path
self.play(MoveAlongPath(dot, path), run_time=3, rate_func=smooth)
self.wait()
class ColorTransitions(Scene):
"""Demonstrates color manipulation and gradients."""
def construct(self):
# Color gradient on shapes
squares = VGroup(*[
Square(side_length=0.8, fill_opacity=0.8)
for _ in range(7)
]).arrange(RIGHT, buff=0.2)
# Apply gradient colors
colors = [RED, ORANGE, YELLOW, GREEN, BLUE, PURPLE, PINK]
for square, color in zip(squares, colors):
square.set_fill(color)
square.set_stroke(WHITE, width=2)
self.play(LaggedStartMap(GrowFromCenter, squares, lag_ratio=0.1))
self.wait()
# Animate color change
self.play(
*[square.animate.set_fill(interpolate_color(BLUE, RED, i / 6))
for i, square in enumerate(squares)],
run_time=2
)
self.wait()
class GroupOperations(Scene):
"""Demonstrates VGroup operations and arrangements."""
def construct(self):
# Create VGroup
shapes = VGroup(
Circle(color=RED),
Square(color=GREEN),
Triangle(color=BLUE),
)
# Arrange horizontally
shapes.arrange(RIGHT, buff=1)
self.play(Create(shapes))
self.wait()
# Scale entire group
self.play(shapes.animate.scale(0.5))
self.wait()
# Arrange vertically
self.play(shapes.animate.arrange(DOWN, buff=0.5))
self.wait()
# Apply operation to all
self.play(shapes.animate.set_fill(YELLOW, opacity=0.5))
self.wait()
"""
Graph and Function Plotting Patterns for Manim Community
Demonstrates Axes, NumberPlane, function plotting, and coordinate systems.
Adapted from 3b1b patterns for ManimCE.
Run with: manim -pql graph_plotting.py SceneName
"""
from manim import *
import numpy as np
class BasicAxes(Scene):
"""Basic axes setup and labeling."""
def construct(self):
# Create axes
axes = Axes(
x_range=[-3, 3, 1],
y_range=[-2, 2, 1],
x_length=8,
y_length=5,
axis_config={
"include_tip": True,
"include_numbers": True,
},
)
# Labels
x_label = axes.get_x_axis_label("x")
y_label = axes.get_y_axis_label("y")
self.play(Create(axes), Write(x_label), Write(y_label))
self.wait()
class FunctionPlotting(Scene):
"""Plotting functions on axes."""
def construct(self):
axes = Axes(
x_range=[-3, 3, 1],
y_range=[-1, 9, 2],
x_length=8,
y_length=5,
axis_config={"include_numbers": True},
)
# Plot y = x^2
parabola = axes.plot(
lambda x: x ** 2,
color=BLUE,
x_range=[-3, 3]
)
# Label
label = MathTex(r"y = x^2", color=BLUE)
label.next_to(parabola, UR)
self.play(Create(axes))
self.play(Create(parabola), Write(label))
self.wait()
class MultipleFunctions(Scene):
"""Multiple functions on same axes."""
def construct(self):
axes = Axes(
x_range=[-2 * PI, 2 * PI, PI / 2],
y_range=[-1.5, 1.5, 0.5],
x_length=10,
y_length=4,
)
# Plot sine and cosine
sine = axes.plot(np.sin, color=BLUE, x_range=[-2 * PI, 2 * PI])
cosine = axes.plot(np.cos, color=RED, x_range=[-2 * PI, 2 * PI])
# Labels
sin_label = MathTex(r"\sin(x)", color=BLUE).to_corner(UR)
cos_label = MathTex(r"\cos(x)", color=RED).next_to(sin_label, DOWN)
self.play(Create(axes))
self.play(Create(sine), Write(sin_label))
self.play(Create(cosine), Write(cos_label))
self.wait()
class AreaUnderCurve(Scene):
"""Visualizing area under a curve (integration)."""
def construct(self):
axes = Axes(
x_range=[0, 5, 1],
y_range=[0, 10, 2],
x_length=8,
y_length=5,
)
# Function
func = axes.plot(lambda x: 0.5 * x ** 2, color=BLUE, x_range=[0, 4])
# Area under curve from x=1 to x=3
area = axes.get_area(
func,
x_range=[1, 3],
color=BLUE,
opacity=0.3
)
# Integral notation
integral = MathTex(
r"\int_1^3 \frac{x^2}{2} \, dx",
font_size=48
).to_corner(UR)
self.play(Create(axes))
self.play(Create(func))
self.play(FadeIn(area))
self.play(Write(integral))
self.wait()
class NumberPlaneExample(Scene):
"""Using NumberPlane for coordinate grid."""
def construct(self):
# Create number plane
plane = NumberPlane(
x_range=[-7, 7, 1],
y_range=[-4, 4, 1],
background_line_style={
"stroke_color": BLUE_D,
"stroke_width": 1,
"stroke_opacity": 0.5,
}
)
# Plot a point
point = Dot(plane.c2p(2, 3), color=RED, radius=0.15)
point_label = MathTex("(2, 3)", color=RED).next_to(point, UR, buff=0.1)
# Vector from origin to point
vector = Arrow(
plane.c2p(0, 0),
plane.c2p(2, 3),
buff=0,
color=YELLOW
)
self.play(Create(plane))
self.play(GrowArrow(vector))
self.play(FadeIn(point), Write(point_label))
self.wait()
class ParametricCurve(Scene):
"""Plotting parametric curves."""
def construct(self):
axes = Axes(
x_range=[-4, 4, 1],
y_range=[-4, 4, 1],
x_length=7,
y_length=7,
)
# Parametric curve (circle)
circle = axes.plot_parametric_curve(
lambda t: np.array([2 * np.cos(t), 2 * np.sin(t), 0]),
t_range=[0, 2 * PI],
color=BLUE
)
# Lissajous curve
lissajous = axes.plot_parametric_curve(
lambda t: np.array([2 * np.sin(3 * t), 2 * np.sin(2 * t), 0]),
t_range=[0, 2 * PI],
color=RED
)
self.play(Create(axes))
self.play(Create(circle))
self.wait()
self.play(Transform(circle, lissajous))
self.wait()
class TangentLine(Scene):
"""Showing tangent line to a curve."""
def construct(self):
axes = Axes(
x_range=[-1, 4, 1],
y_range=[-1, 10, 2],
x_length=8,
y_length=5,
)
# Function y = x^2
func = axes.plot(lambda x: x ** 2, color=BLUE, x_range=[0, 3])
# Point of tangency at x = 2
x_val = 2
point = Dot(axes.c2p(x_val, x_val ** 2), color=RED)
# Tangent line: derivative of x^2 is 2x, at x=2 slope is 4
tangent = axes.plot(
lambda x: 4 * (x - 2) + 4, # Point-slope form
color=YELLOW,
x_range=[0.5, 3.5]
)
# Label
slope_label = MathTex(r"m = 2x = 4", color=YELLOW).to_corner(UR)
self.play(Create(axes))
self.play(Create(func))
self.play(FadeIn(point))
self.play(Create(tangent), Write(slope_label))
self.wait()
class AnimatedGraph(Scene):
"""Animating a function parameter change."""
def construct(self):
axes = Axes(
x_range=[-3, 3, 1],
y_range=[-2, 2, 1],
x_length=8,
y_length=5,
)
# Amplitude tracker
amplitude = ValueTracker(1)
# Graph that updates with amplitude
graph = always_redraw(
lambda: axes.plot(
lambda x: amplitude.get_value() * np.sin(x),
color=BLUE,
x_range=[-3, 3]
)
)
# Amplitude display
amp_text = always_redraw(
lambda: MathTex(
f"A = {amplitude.get_value():.1f}"
).to_corner(UR)
)
self.add(axes, graph, amp_text)
# Animate amplitude change
self.play(amplitude.animate.set_value(2), run_time=2)
self.play(amplitude.animate.set_value(0.5), run_time=2)
self.play(amplitude.animate.set_value(1.5), run_time=1)
self.wait()
class RiemannSum(Scene):
"""Visualizing Riemann sums for integration."""
def construct(self):
axes = Axes(
x_range=[0, 5, 1],
y_range=[0, 5, 1],
x_length=8,
y_length=5,
)
# Function
func = axes.plot(lambda x: 0.2 * x ** 2, color=BLUE, x_range=[0, 4])
self.play(Create(axes), Create(func))
self.wait()
# Riemann rectangles
dx_values = [1, 0.5, 0.25]
for dx in dx_values:
rects = axes.get_riemann_rectangles(
func,
x_range=[1, 3],
dx=dx,
color=BLUE,
fill_opacity=0.5,
stroke_width=1,
)
if dx == 1:
self.play(Create(rects))
else:
self.play(Transform(rects, rects))
self.wait()
class ImplicitFunction(Scene):
"""Plotting implicit functions (level curves)."""
def construct(self):
axes = Axes(
x_range=[-4, 4, 1],
y_range=[-4, 4, 1],
x_length=7,
y_length=7,
)
# Circle x^2 + y^2 = 4 as parametric
circle = axes.plot_parametric_curve(
lambda t: np.array([2 * np.cos(t), 2 * np.sin(t), 0]),
t_range=[0, 2 * PI],
color=BLUE
)
# Equation label
equation = MathTex(r"x^2 + y^2 = 4", color=BLUE).to_corner(UR)
self.play(Create(axes))
self.play(Create(circle), Write(equation))
self.wait()
class CoordinateLabeling(Scene):
"""Advanced coordinate labeling techniques."""
def construct(self):
axes = Axes(
x_range=[-1, 5, 1],
y_range=[-1, 5, 1],
x_length=7,
y_length=7,
axis_config={"include_numbers": True},
)
# Function
func = axes.plot(lambda x: np.sqrt(x), color=BLUE, x_range=[0, 4])
# Highlight a specific point
x_val = 2
y_val = np.sqrt(2)
point = Dot(axes.c2p(x_val, y_val), color=RED)
# Dashed lines to axes
h_line = DashedLine(
axes.c2p(0, y_val),
axes.c2p(x_val, y_val),
color=GREY
)
v_line = DashedLine(
axes.c2p(x_val, 0),
axes.c2p(x_val, y_val),
color=GREY
)
# Labels
x_label = MathTex("2").next_to(axes.c2p(x_val, 0), DOWN)
y_label = MathTex(r"\sqrt{2}").next_to(axes.c2p(0, y_val), LEFT)
self.play(Create(axes))
self.play(Create(func))
self.play(Create(v_line), Create(h_line))
self.play(FadeIn(point), Write(x_label), Write(y_label))
self.wait()
class PolarPlot(Scene):
"""Plotting in polar coordinates."""
def construct(self):
# Polar axes
polar_plane = PolarPlane(
radius_max=3,
size=6,
)
# Polar curve: r = 1 + sin(theta) (cardioid)
cardioid = polar_plane.plot_polar_graph(
lambda theta: 1 + np.sin(theta),
theta_range=[0, 2 * PI],
color=BLUE
)
# Rose curve: r = 2*cos(3*theta)
rose = polar_plane.plot_polar_graph(
lambda theta: 2 * np.cos(3 * theta),
theta_range=[0, PI],
color=RED
)
self.play(Create(polar_plane))
self.play(Create(cardioid))
self.wait()
self.play(Transform(cardioid, rose))
self.wait()
"""
Lorenz Attractor - Converted from 3b1b ManimGL to ManimCE
Original: videos/_2024/manim_demo/lorenz.py
This demonstrates a chaotic system visualization with 3D curves and tracing dots.
Run with: manim -pql lorenz_attractor.py LorenzAttractor
"""
from manim import *
from scipy.integrate import solve_ivp
import numpy as np
def lorenz_system(t, state, sigma=10, rho=28, beta=8 / 3):
"""The Lorenz system of differential equations."""
x, y, z = state
dxdt = sigma * (y - x)
dydt = x * (rho - z) - y
dzdt = x * y - beta * z
return [dxdt, dydt, dzdt]
def ode_solution_points(function, state0, time, dt=0.01):
"""Solve ODE and return solution points."""
solution = solve_ivp(
function,
t_span=(0, time),
y0=state0,
t_eval=np.arange(0, time, dt)
)
return solution.y.T
class LorenzAttractor(ThreeDScene):
"""
Visualization of the Lorenz attractor - a classic chaotic system.
Shows multiple trajectories starting from nearly identical initial conditions
that diverge chaotically over time.
"""
def construct(self):
# Set up 3D axes
axes = ThreeDAxes(
x_range=(-50, 50, 10),
y_range=(-50, 50, 10),
z_range=(0, 50, 10),
x_length=12,
y_length=12,
z_length=6,
)
axes.center()
# Set camera orientation
self.set_camera_orientation(phi=76 * DEGREES, theta=43 * DEGREES)
self.add(axes)
# Add the equations (fixed to screen)
equations = MathTex(
r"\frac{dx}{dt} &= \sigma(y-x) \\",
r"\frac{dy}{dt} &= x(\rho-z)-y \\",
r"\frac{dz}{dt} &= xy-\beta z",
font_size=30
)
equations.to_corner(UL)
self.add_fixed_in_frame_mobjects(equations)
self.play(Write(equations))
# Compute a set of solutions with slightly different initial conditions
epsilon = 1e-5
evolution_time = 20 # Reduced for faster rendering
n_points = 5 # Reduced for performance
states = [
[10, 10, 10 + n * epsilon]
for n in range(n_points)
]
colors = color_gradient([BLUE_E, BLUE_A], len(states))
# Create curves from ODE solutions
curves = VGroup()
for state, color in zip(states, colors):
points = ode_solution_points(lorenz_system, state, evolution_time)
# Scale points to fit axes
scaled_points = [axes.c2p(p[0], p[1], p[2]) for p in points]
curve = VMobject()
curve.set_points_smoothly(scaled_points)
curve.set_stroke(color, width=2, opacity=0.8)
curves.add(curve)
# Create dots that will trace the curves
dots = VGroup(*[
Dot3D(color=color, radius=0.15)
for color in colors
])
# Position dots at start of curves
for dot, curve in zip(dots, curves):
dot.move_to(curve.get_start())
self.add(dots)
# Start ambient camera rotation
self.begin_ambient_camera_rotation(rate=0.1)
# Animate curves being drawn with dots following
self.play(
*[Create(curve, rate_func=linear) for curve in curves],
*[MoveAlongPath(dot, curve, rate_func=linear) for dot, curve in zip(dots, curves)],
run_time=evolution_time,
)
self.wait(2)
class LorenzAttractorSimple(ThreeDScene):
"""
Simplified version with just one trajectory and traced path.
Better for understanding the basic pattern.
"""
def construct(self):
# Set up axes
axes = ThreeDAxes(
x_range=(-50, 50, 10),
y_range=(-50, 50, 10),
z_range=(0, 50, 10),
x_length=10,
y_length=10,
z_length=5,
)
self.set_camera_orientation(phi=70 * DEGREES, theta=45 * DEGREES)
self.add(axes)
# Compute single trajectory
evolution_time = 15
points = ode_solution_points(lorenz_system, [10, 10, 10], evolution_time)
scaled_points = [axes.c2p(p[0], p[1], p[2]) for p in points]
# Create curve
curve = VMobject()
curve.set_points_smoothly(scaled_points)
curve.set_stroke(BLUE, width=2)
# Create moving dot with traced path
dot = Dot3D(color=RED, radius=0.2)
dot.move_to(curve.get_start())
# Traced path follows the dot
traced_path = TracedPath(
dot.get_center,
stroke_color=YELLOW,
stroke_width=3,
)
self.add(traced_path, dot)
# Title
title = Text("Lorenz Attractor", font_size=36)
title.to_corner(UL)
self.add_fixed_in_frame_mobjects(title)
# Animate
self.begin_ambient_camera_rotation(rate=0.15)
self.play(
MoveAlongPath(dot, curve, rate_func=linear),
run_time=evolution_time,
)
self.wait(2)
"""
Mathematical Visualization Patterns for Manim Community
Demonstrates LaTeX rendering, equation animations, and color-coded math.
Adapted from 3b1b patterns for ManimCE compatibility.
Run with: manim -pql math_visualization.py SceneName
"""
from manim import *
class ColorCodedEquation(Scene):
"""Demonstrates color-coding for syntax highlighting in equations."""
def construct(self):
# Method 1: Use set_color_by_tex after creation (safer approach)
equation = MathTex(
r"\vec{v}_1", r"=", r"\begin{bmatrix} 1 \\ \lambda_1 \end{bmatrix}"
)
equation.scale(1.5)
# Color specific parts
equation[0].set_color(TEAL) # \vec{v}_1
self.play(Write(equation))
self.wait()
# Second equation with multiple colored parts
equation2 = MathTex(r"A", r"\vec{v}_1", r"=", r"\lambda_1", r"\vec{v}_1")
equation2.scale(1.5)
equation2[0].set_color(RED) # A
equation2[1].set_color(TEAL) # first \vec{v}_1
equation2[3].set_color(YELLOW) # \lambda_1
equation2[4].set_color(TEAL) # second \vec{v}_1
self.play(TransformMatchingTex(equation, equation2))
self.wait()
class EquationDerivation(Scene):
"""Shows step-by-step equation derivation with highlighting."""
def construct(self):
# Starting equation
eq1 = MathTex(r"x^2 + 5x + 6 = 0")
eq1.to_edge(UP)
self.play(Write(eq1))
self.wait()
# Factor step
eq2 = MathTex(r"(x + 2)(x + 3) = 0")
eq2.next_to(eq1, DOWN, buff=0.8)
self.play(
TransformFromCopy(eq1, eq2),
run_time=1.5
)
self.wait()
# Solutions
eq3 = MathTex(r"x = -2", color=BLUE)
eq4 = MathTex(r"x = -3", color=GREEN)
solutions = VGroup(eq3, eq4).arrange(RIGHT, buff=1)
solutions.next_to(eq2, DOWN, buff=0.8)
self.play(
LaggedStart(
Write(eq3),
Write(eq4),
lag_ratio=0.3
)
)
# Highlight solutions
boxes = VGroup(
SurroundingRectangle(eq3, color=BLUE),
SurroundingRectangle(eq4, color=GREEN),
)
self.play(Create(boxes))
self.wait()
class MatrixTransformation(Scene):
"""Demonstrates matrix notation and transformations."""
def construct(self):
# Matrix definition
matrix = MathTex(
r"A = \begin{bmatrix} 2 & 1 \\ 1 & 3 \end{bmatrix}"
).scale(1.2)
self.play(Write(matrix))
self.wait()
# Move to side
self.play(matrix.animate.to_edge(LEFT))
# Show transformation
vector = MathTex(
r"\vec{x} = \begin{bmatrix} 1 \\ 1 \end{bmatrix}",
color=YELLOW
)
vector.next_to(matrix, RIGHT, buff=1)
self.play(Write(vector))
self.wait()
# Result
result = MathTex(
r"A\vec{x} = \begin{bmatrix} 3 \\ 4 \end{bmatrix}",
tex_to_color_map={r"\vec{x}": YELLOW}
)
result.next_to(vector, RIGHT, buff=1)
arrow = Arrow(vector.get_right(), result.get_left(), buff=0.2)
self.play(GrowArrow(arrow), Write(result))
self.wait()
class IntegralVisualization(Scene):
"""Shows integral notation with visual meaning."""
def construct(self):
# Integral expression
integral = MathTex(
r"\int_0^1 x^2 \, dx = \frac{1}{3}",
font_size=64
)
integral.to_edge(UP)
self.play(Write(integral))
self.wait()
# Create axes
axes = Axes(
x_range=[0, 1.2, 0.5],
y_range=[0, 1.2, 0.5],
x_length=5,
y_length=3,
axis_config={"include_tip": True},
)
axes.shift(DOWN)
# Create graph
graph = axes.plot(lambda x: x**2, x_range=[0, 1], color=BLUE)
# Create area under curve
area = axes.get_area(graph, x_range=[0, 1], color=BLUE, opacity=0.3)
self.play(Create(axes))
self.play(Create(graph))
self.play(FadeIn(area))
self.wait()
class SummationNotation(Scene):
"""Demonstrates summation and series notation."""
def construct(self):
# Summation formula
formula = MathTex(
r"\sum_{n=1}^{\infty} \frac{1}{n^2} = \frac{\pi^2}{6}",
font_size=64
)
self.play(Write(formula))
self.wait()
# Show first few terms
terms = MathTex(
r"= 1 + \frac{1}{4} + \frac{1}{9} + \frac{1}{16} + \cdots",
font_size=48
)
terms.next_to(formula, DOWN, buff=0.8)
self.play(Write(terms))
self.wait()
# Create surrounding box around result
box = SurroundingRectangle(formula, color=YELLOW, buff=0.2)
self.play(Create(box))
self.wait()
class FunctionNotation(Scene):
"""Shows function definition and evaluation."""
def construct(self):
# Function definition
f_def = MathTex(r"f(x) = x^2 + 2x + 1", font_size=56)
f_def.to_edge(UP)
self.play(Write(f_def))
self.wait()
# Evaluation at x=3
eval_step1 = MathTex(r"f(3) = 3^2 + 2(3) + 1", font_size=48)
eval_step2 = MathTex(r"f(3) = 9 + 6 + 1", font_size=48)
eval_step3 = MathTex(r"f(3) = 16", font_size=48, color=GREEN)
steps = VGroup(eval_step1, eval_step2, eval_step3)
steps.arrange(DOWN, buff=0.5)
steps.next_to(f_def, DOWN, buff=1)
for step in steps:
self.play(Write(step))
self.wait(0.5)
# Box the answer
box = SurroundingRectangle(eval_step3, color=GREEN)
self.play(Create(box))
self.wait()
class LimitNotation(Scene):
"""Demonstrates limit notation and evaluation."""
def construct(self):
# Limit expression
limit = MathTex(
r"\lim_{x \to 0} \frac{\sin x}{x} = 1",
font_size=64
)
self.play(Write(limit))
self.wait()
# Show approaching behavior
approaching = MathTex(
r"x \to 0: \quad",
r"\frac{\sin(0.1)}{0.1} \approx 0.998",
font_size=40
)
approaching.next_to(limit, DOWN, buff=1)
self.play(Write(approaching))
self.wait()
class DerivativeChainRule(Scene):
"""Shows the chain rule for derivatives."""
def construct(self):
title = Text("Chain Rule", font_size=48, color=BLUE)
title.to_edge(UP)
# Chain rule formula
rule = MathTex(
r"\frac{d}{dx}[f(g(x))] = f'(g(x)) \cdot g'(x)",
font_size=48
)
# Example
example_title = Text("Example:", font_size=36)
example = MathTex(
r"\frac{d}{dx}[\sin(x^2)] = \cos(x^2) \cdot 2x",
tex_to_color_map={
r"\sin": BLUE,
r"\cos": BLUE,
r"x^2": YELLOW,
r"2x": YELLOW,
},
font_size=44
)
content = VGroup(rule, example_title, example)
content.arrange(DOWN, buff=0.8)
self.play(Write(title))
self.play(Write(rule))
self.wait()
self.play(Write(example_title))
self.play(Write(example))
self.wait()
class TexHighlighting(Scene):
"""Advanced tex highlighting techniques."""
def construct(self):
# Create equation with substrings to highlight
equation = MathTex(
r"E", r"=", r"m", r"c^2",
font_size=96
)
self.play(Write(equation))
self.wait()
# Highlight individual parts
self.play(equation[0].animate.set_color(YELLOW)) # E
self.wait(0.3)
self.play(equation[2].animate.set_color(BLUE)) # m
self.wait(0.3)
self.play(equation[3].animate.set_color(RED)) # c^2
self.wait()
# Add labels
e_label = Text("Energy", font_size=24, color=YELLOW)
m_label = Text("Mass", font_size=24, color=BLUE)
c_label = Text("Speed of Light", font_size=24, color=RED)
e_label.next_to(equation[0], UP)
m_label.next_to(equation[2], DOWN)
c_label.next_to(equation[3], UP)
self.play(
FadeIn(e_label, shift=DOWN * 0.3),
FadeIn(m_label, shift=UP * 0.3),
FadeIn(c_label, shift=DOWN * 0.3),
)
self.wait()
"""
Updater and ValueTracker Patterns for Manim Community
Demonstrates dynamic animations using updaters and ValueTracker.
Adapted from 3b1b's animation patterns for ManimCE.
Run with: manim -pql updater_patterns.py SceneName
"""
from manim import *
import numpy as np
class BasicUpdater(Scene):
"""Simple updater that makes an object follow another."""
def construct(self):
# Leader dot
leader = Dot(color=RED, radius=0.2)
leader_label = Text("Leader", font_size=24).next_to(leader, UP)
# Follower that always stays next to leader
follower = Dot(color=BLUE, radius=0.15)
follower.add_updater(lambda m: m.next_to(leader, RIGHT, buff=0.5))
follower_label = Text("Follower", font_size=24, color=BLUE)
follower_label.add_updater(lambda m: m.next_to(follower, DOWN))
self.add(leader, leader_label, follower, follower_label)
# Move the leader - follower automatically follows
self.play(leader.animate.shift(RIGHT * 3), run_time=2)
self.play(leader.animate.shift(UP * 2), run_time=2)
self.play(leader.animate.shift(LEFT * 4 + DOWN), run_time=2)
self.wait()
class ValueTrackerBasics(Scene):
"""Demonstrates ValueTracker for animating numeric values."""
def construct(self):
# Create a ValueTracker
tracker = ValueTracker(0)
# DecimalNumber that displays the tracker value
number = DecimalNumber(
0,
num_decimal_places=2,
font_size=72,
include_sign=True
)
number.add_updater(lambda m: m.set_value(tracker.get_value()))
# Label
label = Text("Value: ", font_size=48)
label.next_to(number, LEFT)
self.add(label, number)
# Animate the tracker
self.play(tracker.animate.set_value(10), run_time=2)
self.wait(0.5)
self.play(tracker.animate.set_value(-5), run_time=2)
self.wait(0.5)
self.play(tracker.animate.set_value(0), run_time=1)
self.wait()
class CircleRadiusTracker(Scene):
"""Circle that grows/shrinks with a ValueTracker."""
def construct(self):
tracker = ValueTracker(1)
# Circle with radius controlled by tracker
circle = always_redraw(
lambda: Circle(
radius=tracker.get_value(),
color=BLUE,
fill_opacity=0.3
)
)
# Radius label
radius_text = always_redraw(
lambda: MathTex(
f"r = {tracker.get_value():.2f}"
).to_edge(UP)
)
self.add(circle, radius_text)
# Animate radius changes
self.play(tracker.animate.set_value(2.5), run_time=2)
self.play(tracker.animate.set_value(0.5), run_time=2)
self.play(tracker.animate.set_value(1.5), run_time=1)
self.wait()
class RotatingUpdater(Scene):
"""Object that rotates continuously using dt (delta time)."""
def construct(self):
# Create rotating group
square = Square(side_length=2, color=BLUE, fill_opacity=0.5)
dot = Dot(color=RED).move_to(square.get_corner(UR))
group = VGroup(square, dot)
# Add rotation updater with dt for smooth rotation
group.add_updater(lambda m, dt: m.rotate(dt * PI / 2))
self.add(group)
self.wait(4) # Watch it rotate
# Remove updater
group.clear_updaters()
self.wait()
class TracedPathExample(Scene):
"""Demonstrates TracedPath for drawing motion trails."""
def construct(self):
# Moving dot
dot = Dot(color=RED, radius=0.15)
dot.move_to(LEFT * 3)
# Traced path follows the dot
traced_path = TracedPath(
dot.get_center,
stroke_color=YELLOW,
stroke_width=3
)
self.add(traced_path, dot)
# Move dot in a pattern
self.play(
dot.animate.shift(RIGHT * 3 + UP * 2),
run_time=1.5
)
self.play(
dot.animate.shift(RIGHT * 2 + DOWN * 3),
run_time=1.5
)
self.play(
dot.animate.shift(LEFT * 2 + UP * 1),
run_time=1.5
)
self.wait()
class SineWaveTracker(Scene):
"""Animated sine wave using ValueTracker."""
def construct(self):
# Phase tracker
phase = ValueTracker(0)
# Axes
axes = Axes(
x_range=[0, 2 * PI, PI / 2],
y_range=[-1.5, 1.5, 0.5],
x_length=10,
y_length=4,
)
# Sine wave that updates with phase
sine_wave = always_redraw(
lambda: axes.plot(
lambda x: np.sin(x + phase.get_value()),
color=BLUE,
x_range=[0, 2 * PI]
)
)
# Dot that follows the wave
dot = always_redraw(
lambda: Dot(color=RED).move_to(
axes.c2p(PI, np.sin(PI + phase.get_value()))
)
)
self.add(axes, sine_wave, dot)
# Animate phase change (wave shifts)
self.play(
phase.animate.set_value(2 * PI),
run_time=4,
rate_func=linear
)
class ArrowUpdater(Scene):
"""Arrow that always points from one object to another."""
def construct(self):
# Two dots
dot1 = Dot(color=BLUE, radius=0.2).shift(LEFT * 2)
dot2 = Dot(color=RED, radius=0.2).shift(RIGHT * 2)
# Arrow that always connects them
arrow = always_redraw(
lambda: Arrow(
dot1.get_center(),
dot2.get_center(),
buff=0.3,
color=YELLOW
)
)
# Distance label
distance = always_redraw(
lambda: DecimalNumber(
np.linalg.norm(dot2.get_center() - dot1.get_center()),
num_decimal_places=2,
font_size=36
).next_to(arrow, UP)
)
self.add(dot1, dot2, arrow, distance)
# Move dots around
self.play(dot1.animate.shift(UP * 2), run_time=1.5)
self.play(dot2.animate.shift(DOWN + LEFT * 2), run_time=1.5)
self.play(
dot1.animate.shift(RIGHT * 3),
dot2.animate.shift(UP * 2),
run_time=2
)
self.wait()
class ParametricCurveTracer(Scene):
"""Traces a parametric curve using ValueTracker."""
def construct(self):
# Parameter t
t_tracker = ValueTracker(0)
# Parametric curve (Lissajous)
def parametric_func(t):
return np.array([
2 * np.sin(2 * t),
2 * np.sin(3 * t),
0
])
# Dot at current position
dot = always_redraw(
lambda: Dot(color=RED, radius=0.15).move_to(
parametric_func(t_tracker.get_value())
)
)
# Traced path
path = TracedPath(
dot.get_center,
stroke_color=BLUE,
stroke_width=2
)
self.add(path, dot)
# Trace the curve
self.play(
t_tracker.animate.set_value(2 * PI),
run_time=6,
rate_func=linear
)
self.wait()
class MultipleTrackers(Scene):
"""Using multiple ValueTrackers together."""
def construct(self):
# Separate trackers for x and y
x_tracker = ValueTracker(0)
y_tracker = ValueTracker(0)
# Dot controlled by both trackers
dot = always_redraw(
lambda: Dot(color=RED, radius=0.2).move_to(
RIGHT * x_tracker.get_value() + UP * y_tracker.get_value()
)
)
# Coordinate display
coords = always_redraw(
lambda: MathTex(
f"({x_tracker.get_value():.1f}, {y_tracker.get_value():.1f})"
).to_corner(UL)
)
self.add(dot, coords)
# Animate both trackers
self.play(x_tracker.animate.set_value(3), run_time=1.5)
self.play(y_tracker.animate.set_value(2), run_time=1.5)
self.play(
x_tracker.animate.set_value(-2),
y_tracker.animate.set_value(-1),
run_time=2
)
self.wait()
class SpringMassSimulation(Scene):
"""Simple physics simulation with updaters."""
def construct(self):
# Physics parameters
k = 10 # Spring constant
mass = 1
damping = 0.5
# State trackers
position = ValueTracker(2) # Initial displacement
velocity = ValueTracker(0)
# Ground line
ground = Line(LEFT * 4, RIGHT * 4, color=WHITE).shift(DOWN * 2)
# Mass (square)
mass_obj = always_redraw(
lambda: Square(
side_length=0.8,
color=BLUE,
fill_opacity=0.8
).move_to(UP * position.get_value())
)
# Spring (simplified as line)
spring = always_redraw(
lambda: Line(
ground.get_center() + UP * 0.1,
mass_obj.get_bottom(),
color=GREY
)
)
self.add(ground, spring, mass_obj)
# Physics update function
def physics_update(mob, dt):
x = position.get_value()
v = velocity.get_value()
# F = -kx - damping*v
acceleration = (-k * x - damping * v) / mass
new_v = v + acceleration * dt
new_x = x + new_v * dt
velocity.set_value(new_v)
position.set_value(new_x)
# Add physics updater to a dummy mobject
physics_driver = Mobject()
physics_driver.add_updater(physics_update)
self.add(physics_driver)
# Let it run
self.wait(5)
# Clean up
physics_driver.clear_updaters()
self.wait()
MIT License
Copyright (c) 2026 Adithya S Kolavi (adithyaskolavi@gmail.com)
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
3D Graphics in Manim
Create 3D visualizations with ThreeDScene.
ThreeDScene Basics
from manim import *
class Basic3D(ThreeDScene):
def construct(self):
# Set camera angle
self.set_camera_orientation(phi=75 * DEGREES, theta=-45 * DEGREES)
# Add 3D axes
axes = ThreeDAxes()
self.add(axes)Camera Orientation
class CameraOrientation(ThreeDScene):
def construct(self):
axes = ThreeDAxes()
# phi: angle from z-axis (0 = top view, 90 = side view)
# theta: rotation around z-axis
# gamma: roll angle
self.set_camera_orientation(
phi=75 * DEGREES,
theta=-45 * DEGREES,
gamma=0
)
self.add(axes)Animated Camera Movement
class AnimatedCamera(ThreeDScene):
def construct(self):
axes = ThreeDAxes()
self.add(axes)
self.set_camera_orientation(phi=75*DEGREES, theta=0)
# Animate camera movement
self.move_camera(phi=45*DEGREES, theta=90*DEGREES, run_time=3)Continuous Camera Rotation
class RotatingCamera(ThreeDScene):
def construct(self):
axes = ThreeDAxes()
self.add(axes)
self.set_camera_orientation(phi=75*DEGREES, theta=0)
# Start ambient rotation
self.begin_ambient_camera_rotation(rate=0.2)
self.wait(5)
self.stop_ambient_camera_rotation()3D Primitives
Sphere
class SphereExample(ThreeDScene):
def construct(self):
sphere = Sphere(radius=1, resolution=(20, 20))
sphere.set_color(BLUE)
self.set_camera_orientation(phi=75*DEGREES, theta=-45*DEGREES)
self.add(sphere)Cube / Prism
class CubeExample(ThreeDScene):
def construct(self):
cube = Cube(side_length=2, fill_opacity=0.8)
cube.set_color(RED)
# Rectangular prism
prism = Prism(dimensions=[3, 1, 2])
self.set_camera_orientation(phi=75*DEGREES, theta=-45*DEGREES)
self.add(cube)Cylinder / Cone
class CylinderCone(ThreeDScene):
def construct(self):
cylinder = Cylinder(radius=1, height=2, fill_opacity=0.8)
cone = Cone(base_radius=1, height=2, fill_opacity=0.8)
cylinder.shift(LEFT * 2)
cone.shift(RIGHT * 2)
self.set_camera_orientation(phi=75*DEGREES, theta=-45*DEGREES)
self.add(cylinder, cone)Torus
class TorusExample(ThreeDScene):
def construct(self):
torus = Torus(major_radius=2, minor_radius=0.5)
self.set_camera_orientation(phi=75*DEGREES, theta=-45*DEGREES)
self.add(torus)3D Axes
class ThreeDAxesExample(ThreeDScene):
def construct(self):
axes = ThreeDAxes(
x_range=[-4, 4, 1],
y_range=[-4, 4, 1],
z_range=[-4, 4, 1],
x_length=8,
y_length=8,
z_length=6,
)
# Add axis labels
x_label = axes.get_x_axis_label("x")
y_label = axes.get_y_axis_label("y")
z_label = axes.get_z_axis_label("z")
self.set_camera_orientation(phi=75*DEGREES, theta=-45*DEGREES)
self.add(axes, x_label, y_label, z_label)Surface Plots
class SurfacePlot(ThreeDScene):
def construct(self):
axes = ThreeDAxes(x_range=[-3, 3], y_range=[-3, 3], z_range=[-2, 2])
# Function z = f(x, y)
surface = axes.plot_surface(
lambda u, v: np.sin(u) * np.cos(v),
u_range=[-3, 3],
v_range=[-3, 3],
resolution=(30, 30),
colorscale=[BLUE, GREEN, YELLOW, RED],
)
self.set_camera_orientation(phi=75*DEGREES, theta=-45*DEGREES)
self.add(axes, surface)Surface Class (standalone)
class SurfaceExample(ThreeDScene):
def construct(self):
def param_func(u, v):
x = u
y = v
z = np.sin(np.sqrt(u**2 + v**2))
return np.array([x, y, z])
surface = Surface(
param_func,
u_range=[-3, 3],
v_range=[-3, 3],
resolution=(30, 30),
fill_opacity=0.8,
)
surface.set_color_by_gradient(BLUE, GREEN)
self.set_camera_orientation(phi=75*DEGREES, theta=-45*DEGREES)
self.add(surface)3D Parametric Curves
class ParametricCurve3D(ThreeDScene):
def construct(self):
# Helix
curve = ParametricFunction(
lambda t: np.array([
np.cos(t),
np.sin(t),
t * 0.2
]),
t_range=[-4*PI, 4*PI],
color=YELLOW
)
curve.set_shade_in_3d(True)
self.set_camera_orientation(phi=75*DEGREES, theta=-45*DEGREES)
self.add(ThreeDAxes(), curve)Shading in 3D
class Shading3D(ThreeDScene):
def construct(self):
sphere = Sphere()
# Enable shading for realistic lighting
sphere.set_shade_in_3d(True)
self.set_camera_orientation(phi=75*DEGREES, theta=-45*DEGREES)
self.add(sphere)Arrow3D and Line3D
class Vectors3D(ThreeDScene):
def construct(self):
axes = ThreeDAxes()
arrow = Arrow3D(ORIGIN, [2, 1, 2], color=RED)
line = Line3D(ORIGIN, [-2, 1, 1], color=BLUE)
self.set_camera_orientation(phi=75*DEGREES, theta=-45*DEGREES)
self.add(axes, arrow, line)Best Practices
1. Always set camera orientation - Default view may not show 3D well 2. Use set_shade_in_3d for realism - Adds depth perception 3. Use ambient camera rotation sparingly - Can be disorienting 4. Match resolution to detail needed - Higher res = slower render 5. Use colorscale for surfaces - Shows elevation/value changes
Animation Groups
Control how multiple animations play together.
AnimationGroup
Play multiple animations with controlled timing.
from manim import *
class AnimationGroupExample(Scene):
def construct(self):
circles = VGroup(*[Circle() for _ in range(5)]).arrange(RIGHT)
# All animations play simultaneously (lag_ratio=0)
self.play(AnimationGroup(
*[Create(c) for c in circles],
lag_ratio=0
))lag_ratio Parameter
Controls the delay between animation starts:
lag_ratio=0: All start simultaneouslylag_ratio=0.5: Each starts when previous is 50% completelag_ratio=1: Each starts when previous finishes (sequential)
class LagRatioDemo(Scene):
def construct(self):
squares = VGroup(*[Square() for _ in range(4)]).arrange(RIGHT)
# Staggered start - each begins when previous is 25% done
self.play(AnimationGroup(
*[FadeIn(s) for s in squares],
lag_ratio=0.25,
run_time=2
))LaggedStart
Convenience class with default lag_ratio=0.05 (5% overlap).
class LaggedStartExample(Scene):
def construct(self):
dots = VGroup(*[Dot() for _ in range(10)]).arrange(RIGHT)
# Rapid staggered animation
self.play(LaggedStart(
*[GrowFromCenter(d) for d in dots],
lag_ratio=0.1
))Common LaggedStart Patterns
# Staggered fade in
self.play(LaggedStart(*[FadeIn(m) for m in mobjects], lag_ratio=0.2))
# Wave effect
self.play(LaggedStart(
*[m.animate.shift(UP * 0.5) for m in mobjects],
lag_ratio=0.1
))
# Staggered color change
self.play(LaggedStart(
*[m.animate.set_color(RED) for m in mobjects],
lag_ratio=0.15
))Succession
Play animations one after another (equivalent to lag_ratio=1).
class SuccessionExample(Scene):
def construct(self):
circle = Circle().shift(LEFT * 2)
square = Square()
triangle = Triangle().shift(RIGHT * 2)
# Animations play in sequence
self.play(Succession(
Create(circle),
Create(square),
Create(triangle)
))Succession vs Multiple play() Calls
# These are equivalent:
# Using Succession
self.play(Succession(
Create(circle),
Create(square)
))
# Using separate play calls
self.play(Create(circle))
self.play(Create(square))Succession is useful when you want to treat sequential animations as a single unit.
Combining Group Types
class CombinedExample(Scene):
def construct(self):
group1 = VGroup(*[Circle() for _ in range(3)]).arrange(RIGHT).shift(UP)
group2 = VGroup(*[Square() for _ in range(3)]).arrange(RIGHT).shift(DOWN)
# First group appears with stagger, then second group
self.play(Succession(
LaggedStart(*[Create(c) for c in group1], lag_ratio=0.2),
LaggedStart(*[Create(s) for s in group2], lag_ratio=0.2)
))LaggedStartMap
Apply an animation to all submobjects of a mobject with staggered timing.
class LaggedStartMapExample(Scene):
def construct(self):
dots = VGroup(*[Dot(radius=0.16) for _ in range(35)]).arrange_in_grid(rows=5, cols=7)
# Apply FadeIn to all dots with stagger
self.play(LaggedStartMap(FadeIn, dots, lag_ratio=0.1))
self.wait(0.5)
# Change color with stagger using LaggedStart
self.play(LaggedStart(
*[dot.animate.set_color(YELLOW) for dot in dots],
lag_ratio=0.05
))LaggedStartMap is cleaner for applying the same animation to each submobject. For property changes, use LaggedStart with .animate.
AnimationGroup with run_time
The total run_time is distributed among animations based on lag_ratio.
self.play(AnimationGroup(
*[Create(c) for c in circles],
lag_ratio=0.5,
run_time=4 # Total duration is 4 seconds
))Practical Examples
Text Appearing Word by Word
class WordByWord(Scene):
def construct(self):
words = VGroup(
Text("Hello"),
Text("World"),
Text("!")
).arrange(RIGHT)
self.play(LaggedStart(
*[Write(w) for w in words],
lag_ratio=0.5
))Grid Animation
class GridAnimation(Scene):
def construct(self):
grid = VGroup(*[
Square().scale(0.3)
for _ in range(25)
]).arrange_in_grid(5, 5)
# Diagonal wave effect
self.play(LaggedStart(
*[GrowFromCenter(s) for s in grid],
lag_ratio=0.05
))Best Practices
1. Use LaggedStart for visual polish - Staggered animations look more dynamic 2. Keep lag_ratio small (0.05-0.2) - Too high feels slow 3. Use Succession for distinct steps - When animations are conceptually separate 4. Adjust run_time with lag_ratio - More items may need longer total time
Animations in Manim
Animations interpolate mobjects between states over time. They are played using self.play().
The .animate Syntax
The most common way to animate is using the .animate property:
# Move a square to the right
self.play(square.animate.shift(RIGHT))
# Scale up
self.play(circle.animate.scale(2))
# Change color
self.play(text.animate.set_color(RED))
# Chain multiple changes
self.play(square.animate.shift(RIGHT).rotate(PI/4).set_color(BLUE))Animation Parameters
run_time
Controls animation duration in seconds (default: 1).
self.play(Create(circle), run_time=2) # 2 second animation
self.play(Create(circle), run_time=0.5) # Half secondrate_func
Controls the animation's timing curve (easing).
from manim import smooth, linear, there_and_back
self.play(square.animate.shift(RIGHT), rate_func=smooth)
self.play(square.animate.shift(RIGHT), rate_func=linear)
self.play(square.animate.shift(RIGHT), rate_func=there_and_back)Playing Multiple Animations
Simultaneously
# All play at the same time
self.play(
Create(circle),
FadeIn(square),
Write(text)
)Sequentially
# One after another
self.play(Create(circle))
self.play(FadeIn(square))
self.play(Write(text))
# Or use Succession
self.play(Succession(
Create(circle),
FadeIn(square),
Write(text)
))Common Animation Classes
Creation Animations
Create(mobject) # Draw the mobject progressively
Write(text) # Write text/equations
FadeIn(mobject) # Fade in from transparent
DrawBorderThenFill(mob) # Draw outline, then fill
GrowFromCenter(mobject) # Grow from center pointRemoval Animations
FadeOut(mobject) # Fade to transparent
Uncreate(mobject) # Reverse of Create
ShrinkToCenter(mobject) # Shrink to center and disappearTransform Animations
Transform(mob1, mob2) # Morph mob1 into mob2
ReplacementTransform(mob1, mob2) # Replace mob1 with mob2
TransformFromCopy(mob1, mob2) # Keep mob1, create mob2Movement Animations
MoveToTarget(mobject) # Move to preset target
Rotate(mobject, angle) # Rotate by angle
Circumscribe(mobject) # Draw attention with circleAnimation vs Instant Changes
# Animated change (visible transition)
self.play(circle.animate.set_color(RED))
# Instant change (no animation)
circle.set_color(RED)
self.add(circle)Best Practices
1. Use .animate for simple transformations - Cleaner than explicit Animation classes 2. Keep run_time reasonable - 0.5-2 seconds for most animations 3. Use rate_func for polish - smooth is usually better than linear 4. Group related animations - Play simultaneously when conceptually related
Coordinate Systems
Create axes, grids, and number lines for mathematical visualizations.
Axes
Basic 2D coordinate axes.
from manim import *
class AxesExample(Scene):
def construct(self):
# Default axes
axes = Axes()
self.add(axes)Customizing Axes
class CustomAxes(Scene):
def construct(self):
axes = Axes(
x_range=[-5, 5, 1], # [min, max, step]
y_range=[-3, 3, 1],
x_length=10, # Physical length on screen
y_length=6,
axis_config={
"color": BLUE,
"include_tip": True,
"include_numbers": True,
},
x_axis_config={
"numbers_to_include": [-4, -2, 0, 2, 4],
},
y_axis_config={
"numbers_to_include": [-2, 0, 2],
},
)
self.add(axes)Adding Labels
class AxesLabels(Scene):
def construct(self):
axes = Axes(x_range=[-5, 5], y_range=[-3, 3])
# Add axis labels
x_label = axes.get_x_axis_label("x")
y_label = axes.get_y_axis_label("y")
# Custom labels
x_label = axes.get_x_axis_label(MathTex(r"\theta"))
y_label = axes.get_y_axis_label(MathTex(r"f(\theta)"))
self.add(axes, x_label, y_label)NumberPlane
Grid with axes - shows coordinate lines.
class NumberPlaneExample(Scene):
def construct(self):
# Default plane
plane = NumberPlane()
self.add(plane)Customizing NumberPlane
class CustomPlane(Scene):
def construct(self):
plane = NumberPlane(
x_range=[-4, 4, 1],
y_range=[-3, 3, 1],
x_length=8,
y_length=6,
background_line_style={
"stroke_color": BLUE_D,
"stroke_width": 1,
"stroke_opacity": 0.5,
},
axis_config={
"color": WHITE,
},
)
self.add(plane)ComplexPlane
For visualizing complex numbers.
class ComplexPlaneExample(Scene):
def construct(self):
plane = ComplexPlane()
# Plot complex number
z = complex(2, 1) # 2 + i
dot = Dot(plane.n2p(z), color=YELLOW)
label = MathTex("2+i").next_to(dot, UR)
self.add(plane, dot, label)NumberLine
Single axis line.
class NumberLineExample(Scene):
def construct(self):
line = NumberLine(
x_range=[-5, 5, 1],
length=10,
include_numbers=True,
include_tip=True,
)
self.add(line)Coordinate Conversions
class CoordinateConversion(Scene):
def construct(self):
axes = Axes(x_range=[-5, 5], y_range=[-3, 3])
# Convert coordinates to screen position
point = axes.c2p(2, 1) # coords_to_point: (2, 1) -> screen position
# Convert screen position to coordinates
coords = axes.p2c(point) # point_to_coords: screen -> (x, y)
dot = Dot(point, color=RED)
self.add(axes, dot)Shorthand Methods
axes = Axes()
# c2p = coords_to_point
axes.c2p(x, y)
# p2c = point_to_coords
axes.p2c(point)
# i2gp = input_to_graph_point (for graphs)
axes.i2gp(x, graph)
# For NumberPlane/ComplexPlane
plane.n2p(complex_number) # number_to_point
plane.p2n(point) # point_to_numberThreeDAxes
For 3D visualizations.
class ThreeDAxesExample(ThreeDScene):
def construct(self):
axes = ThreeDAxes(
x_range=[-4, 4, 1],
y_range=[-4, 4, 1],
z_range=[-4, 4, 1],
x_length=8,
y_length=8,
z_length=6,
)
self.set_camera_orientation(phi=75 * DEGREES, theta=-45 * DEGREES)
self.add(axes)Plotting Points
class PlotPoints(Scene):
def construct(self):
axes = Axes(x_range=[-5, 5], y_range=[-3, 3])
points = [(1, 2), (-2, 1), (3, -1), (0, 2)]
dots = VGroup(*[
Dot(axes.c2p(x, y), color=YELLOW)
for x, y in points
])
self.add(axes, dots)Best Practices
1. Set appropriate ranges - Don't include unnecessary empty space 2. Match x_length/y_length to range ratio - Prevents distortion 3. Use NumberPlane for transformations - Grid shows distortion clearly 4. Use c2p for all coordinate work - Don't manually convert 5. Include numbers sparingly - Too many numbers clutter the display
Camera Control
Control what the viewer sees with camera manipulation.
MovingCameraScene
For 2D scenes with camera movement (zoom, pan).
from manim import *
class CameraExample(MovingCameraScene):
def construct(self):
circle = Circle()
square = Square().shift(RIGHT * 3)
self.add(circle, square)
# Access camera frame
# self.camera.frame is the viewable areaZooming
Zoom In/Out by Scaling Frame
class ZoomExample(MovingCameraScene):
def construct(self):
dots = VGroup(*[Dot() for _ in range(100)])
dots.arrange_in_grid(10, 10, buff=0.3)
self.add(dots)
# Zoom in (make frame smaller)
self.play(self.camera.frame.animate.scale(0.5))
self.wait()
# Zoom out (make frame larger)
self.play(self.camera.frame.animate.scale(4))Zoom to Specific Width
class ZoomToWidth(MovingCameraScene):
def construct(self):
text = Text("Focus on me!")
self.add(text)
# Zoom to fit text with padding
self.play(
self.camera.frame.animate.set(width=text.width * 1.5)
)Panning
Move Camera to Location
class PanExample(MovingCameraScene):
def construct(self):
c1 = Circle().shift(LEFT * 3)
c2 = Circle().shift(RIGHT * 3)
self.add(c1, c2)
# Pan to first circle
self.play(self.camera.frame.animate.move_to(c1))
self.wait()
# Pan to second circle
self.play(self.camera.frame.animate.move_to(c2))Combined Zoom and Pan
class ZoomAndPan(MovingCameraScene):
def construct(self):
square = Square().shift(LEFT * 2)
triangle = Triangle().shift(RIGHT * 2)
self.add(square, triangle)
# Zoom in and pan simultaneously
self.play(
self.camera.frame.animate.scale(0.5).move_to(square)
)
self.wait()
# Move to triangle (still zoomed)
self.play(self.camera.frame.animate.move_to(triangle))Save and Restore Camera State
class SaveRestoreCamera(MovingCameraScene):
def construct(self):
circle = Circle()
self.add(circle)
# Save current state
self.camera.frame.save_state()
# Make changes
self.play(self.camera.frame.animate.scale(0.3).move_to(circle))
self.wait()
# Restore to saved state
self.play(Restore(self.camera.frame))auto_zoom
Automatically zoom to fit mobjects.
class AutoZoomExample(MovingCameraScene):
def construct(self):
squares = VGroup(*[
Square().shift(RIGHT * i + UP * j)
for i in range(-2, 3) for j in range(-2, 3)
])
self.add(squares)
# Zoom to fit specific mobject
self.play(self.camera.auto_zoom(squares[0]))
self.wait()
# Zoom to fit all with margin
self.play(self.camera.auto_zoom(squares, margin=1))3D Camera (ThreeDScene)
class ThreeDCameraExample(ThreeDScene):
def construct(self):
axes = ThreeDAxes()
sphere = Sphere()
self.add(axes, sphere)
# Set initial camera orientation
self.set_camera_orientation(
phi=75 * DEGREES, # Angle from z-axis
theta=-45 * DEGREES # Angle around z-axis
)Animated Camera Rotation
class RotatingCamera(ThreeDScene):
def construct(self):
axes = ThreeDAxes()
self.add(axes)
self.set_camera_orientation(phi=75 * DEGREES, theta=0)
# Continuous rotation
self.begin_ambient_camera_rotation(rate=0.2)
self.wait(5)
self.stop_ambient_camera_rotation()Move 3D Camera
class Move3DCamera(ThreeDScene):
def construct(self):
axes = ThreeDAxes()
self.add(axes)
self.set_camera_orientation(phi=75 * DEGREES, theta=-45 * DEGREES)
# Animate camera movement
self.move_camera(
phi=45 * DEGREES,
theta=45 * DEGREES,
run_time=3
)Camera Background
class CameraBackground(Scene):
def construct(self):
# Set background color
self.camera.background_color = BLUE_E
circle = Circle()
self.add(circle)Best Practices
1. Use MovingCameraScene for zoom/pan - Regular Scene camera is static 2. Save state before complex movements - Easy to restore 3. Use auto_zoom for dynamic content - Automatically fits content 4. Keep camera movements smooth - Don't make viewers dizzy 5. Use 3D camera rotation sparingly - Can be disorienting
Manim CLI
The manim command-line interface for rendering scenes.
Basic Usage
# Render a scene
manim file.py SceneName
# With preview (opens video after rendering)
manim -p file.py SceneName
# Preview with low quality (fast)
manim -pql file.py SceneNameQuality Flags
Quality presets for different use cases:
# Low Quality: 854x480, 15fps (fast for testing)
manim -ql file.py SceneName
# Medium Quality: 1280x720, 30fps
manim -qm file.py SceneName
# High Quality: 1920x1080, 60fps
manim -qh file.py SceneName
# 2K Quality: 2560x1440, 60fps
manim -qp file.py SceneName
# 4K Quality: 3840x2160, 60fps
manim -qk file.py SceneNameCommon Combinations
# Preview + Low Quality (development workflow)
manim -pql file.py SceneName
# Preview + High Quality (final check)
manim -pqh file.py SceneNamePreview Flag
# -p: Open video after rendering
manim -p file.py SceneName
# Without -p: Render only (no auto-open)
manim file.py SceneNameRendering Multiple Scenes
# Render all scenes in file
manim -a file.py
# Render specific scenes
manim file.py Scene1 Scene2 Scene3Output Options
Save Last Frame Only
# -s: Save only the last frame as PNG
manim -s file.py SceneName
# With quality
manim -sql file.py SceneNameOutput Format
# GIF output
manim --format gif file.py SceneName
# PNG sequence
manim --format png file.py SceneName
# WebM (default is MP4)
manim --format webm file.py SceneNameCustom Output Directory
manim -o custom_name file.py SceneName
manim --media_dir /path/to/output file.py SceneNameFrame Control
# Start from specific animation number
manim -n 5 file.py SceneName
# Render frames from animation 3 to 7
manim -n 3,7 file.py SceneNameResolution and FPS
# Custom resolution
manim -r 1920,1080 file.py SceneName
# Custom frame rate
manim --fps 24 file.py SceneName
# Both
manim -r 1280,720 --fps 30 file.py SceneNameTransparency
# Render with transparent background
manim -t file.py SceneNameRenderer Selection
# Cairo renderer (default, 2D)
manim --renderer cairo file.py SceneName
# OpenGL renderer (3D, faster preview)
manim --renderer opengl file.py SceneNameOther Useful Flags
# Verbose output
manim -v DEBUG file.py SceneName
# Quiet mode
manim -v WARNING file.py SceneName
# Show progress bar
manim --progress_bar display file.py SceneName
# Disable caching
manim --disable_caching file.py SceneName
# Write to movie even if no animations
manim --write_to_movie file.py SceneNameHelp
# Show all options
manim --help
# Show render command options
manim render --helpOther Commands
# Check installation and dependencies
manim checkhealth
# Initialize new project
manim init
# Show config values
manim cfg show
# Write current config to file
manim cfg write
# List installed plugins
manim plugins -lJupyter Notebook Support
Use the %%manim cell magic in Jupyter notebooks:
%%manim -qm -v WARNING MyScene
class MyScene(Scene):
def construct(self):
circle = Circle()
self.play(Create(circle))Flags work the same as CLI (-qm, -ql, etc.).
Typical Development Workflow
# 1. Develop with fast preview
manim -pql scene.py MyScene
# 2. Check at medium quality
manim -pqm scene.py MyScene
# 3. Final render at high quality
manim -qh scene.py MyScene
# 4. Create GIF for sharing
manim --format gif -qm scene.py MySceneBest Practices
1. Use -pql for development - Fast iteration cycle 2. Use -qh for final output - Good quality, reasonable render time 3. Use -s for thumbnails - Quick last-frame capture 4. Use -a sparingly - Renders everything, can be slow 5. Use --format gif for demos - Easy to share and embed
Creation Animations
Animations that introduce mobjects to the scene.
Create
Draws a VMobject progressively along its path.
from manim import *
class CreateExample(Scene):
def construct(self):
circle = Circle()
self.play(Create(circle))Best for: Geometric shapes, lines, arrows.
Write
Simulates handwriting. Best for text and equations.
class WriteExample(Scene):
def construct(self):
text = Text("Hello World")
equation = MathTex(r"E = mc^2")
self.play(Write(text))
self.wait()
self.play(Write(equation))Write automatically sets appropriate timing based on text length.
DrawBorderThenFill
Draws the outline first, then fills in the shape.
class DrawBorderExample(Scene):
def construct(self):
square = Square(fill_opacity=0.8, color=BLUE)
self.play(DrawBorderThenFill(square))Best for: Shapes with fills where you want to emphasize the outline first.
FadeIn / FadeOut
Simple opacity transitions.
class FadeExample(Scene):
def construct(self):
circle = Circle()
# Fade in
self.play(FadeIn(circle))
self.wait()
# Fade out
self.play(FadeOut(circle))Directional Fades
# Fade in from a direction
self.play(FadeIn(square, shift=UP)) # Fade in while moving up
self.play(FadeIn(square, shift=LEFT)) # Fade in from right
# Fade out to a direction
self.play(FadeOut(square, shift=DOWN)) # Fade out while moving downScale Fades
self.play(FadeIn(circle, scale=0.5)) # Fade in while growing
self.play(FadeOut(circle, scale=2)) # Fade out while shrinkingGrowFromCenter / ShrinkToCenter
class GrowExample(Scene):
def construct(self):
circle = Circle()
self.play(GrowFromCenter(circle))
self.wait()
self.play(ShrinkToCenter(circle))GrowFromPoint
Grow from a specific point.
self.play(GrowFromPoint(circle, ORIGIN))
self.play(GrowFromPoint(circle, LEFT * 3))GrowFromEdge
Grow from a specific edge.
self.play(GrowFromEdge(square, LEFT)) # Grow from left edge
self.play(GrowFromEdge(square, DOWN)) # Grow from bottom edgeSpinInFromNothing
Object spins in while growing.
self.play(SpinInFromNothing(circle))Uncreate
Reverse of Create - erases the mobject.
self.play(Create(circle))
self.wait()
self.play(Uncreate(circle)) # Erases in reverseAddTextLetterByLetter
Types text one character at a time.
class TypingExample(Scene):
def construct(self):
text = Text("Hello World")
self.play(AddTextLetterByLetter(text, time_per_char=0.1))Note: Only works with Text, not MathTex.
Best Practices
1. Use Write for text - Looks more natural than Create 2. Use Create for shapes - Clean progressive drawing 3. Use FadeIn for quick introductions - When drawing isn't important 4. Match removal to creation - If you Create, use Uncreate; if FadeIn, use FadeOut