
Visual Storyteller
- 7 installs
- 292 repo stars
- Updated January 29, 2026
- rohitg00/manim-video-generator
Helps with ai & agent building tasks during AI-assisted development.
About
visual-storyteller is a Claude Code skill for ai & agent building. It helps solo builders move faster with AI-assisted coding.
- visual-storyteller
- AI & Agent Building
- AI-coding skill
Visual Storyteller by the numbers
- 7 all-time installs (skills.sh)
- Ranked #12,520 of 16,546 AI & Agent Building skills by installs in the Skillselion catalog
- Data as of Aug 2, 2026 (Skillselion catalog sync)
npx skills add https://github.com/rohitg00/manim-video-generator --skill visual-storytellerAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 7 |
|---|---|
| repo stars | ★ 292 |
| Last updated | January 29, 2026 |
| Repository | rohitg00/manim-video-generator ↗ |
What it does
Helps with ai & agent building tasks during AI-assisted development.
Files
Visual Storyteller Skill
The Visual Storyteller transforms explanations and processes into engaging visual narratives that build understanding progressively.
Storytelling Framework
The CLEAR Method
- Context: Establish what we're exploring
- Layers: Build complexity gradually
- Examples: Show concrete instances
- Analogies: Connect to familiar concepts
- Reinforce: Summarize key insights
Narrative Arc for Explanations
1. Hook (Why should I care?)
↓
2. Setup (What do I need to know?)
↓
3. Rising Action (Build understanding step by step)
↓
4. Climax (The "aha!" moment)
↓
5. Resolution (How does this connect to the bigger picture?)Rules
rules/progressive-revelation.md
Show information piece by piece, never overwhelming the viewer.
rules/visual-metaphors.md
Use relatable visual metaphors to explain abstract concepts.
rules/pacing-for-understanding.md
Allow time for concepts to sink in before moving forward.
rules/emphasis-techniques.md
Highlight key elements using color, size, and animation.
Templates
Concept Explanation
from manim import *
class ConceptExplanation(Scene):
def construct(self):
# Hook: Pose an intriguing question
question = Text("Why does this happen?", color=YELLOW)
self.play(Write(question))
self.wait(2)
self.play(FadeOut(question))
# Setup: Introduce the elements
elements = self.introduce_elements()
# Rising Action: Build step by step
for i, step in enumerate(self.get_steps()):
step_label = Text(f"Step {i+1}", font_size=24).to_corner(UL)
self.play(FadeIn(step_label))
self.demonstrate_step(step, elements)
self.play(FadeOut(step_label))
# Climax: The revelation
self.play(Indicate(elements, scale_factor=1.2, color=GREEN))
insight = Text("And that's why!", color=GREEN)
self.play(Write(insight))
# Resolution: Connect to bigger picture
self.play(FadeOut(insight), FadeOut(elements))
summary = self.create_summary()
self.play(FadeIn(summary))Process Walkthrough
from manim import *
class ProcessWalkthrough(Scene):
def construct(self):
# Title
title = Text("How X Works").scale(1.2)
self.play(Write(title))
self.play(title.animate.to_edge(UP).scale(0.6))
# Create process diagram
steps = VGroup(*[
self.create_step_box(f"Step {i+1}", desc)
for i, desc in enumerate(self.step_descriptions)
]).arrange(RIGHT, buff=1)
# Progressive revelation
for i, step in enumerate(steps):
self.play(FadeIn(step, shift=UP))
self.wait(0.5)
# Highlight current step
self.play(step.animate.set_color(YELLOW))
self.demonstrate_step_detail(i)
self.play(step.animate.set_color(WHITE))
# Draw arrow to next step
if i < len(steps) - 1:
arrow = Arrow(step.get_right(), steps[i+1].get_left())
self.play(Create(arrow))Comparison/Contrast
from manim import *
class ComparisonScene(Scene):
def construct(self):
# Split screen
line = Line(UP * 3, DOWN * 3)
self.play(Create(line))
# Left side: Concept A
left_title = Text("Without X").to_edge(UP).shift(LEFT * 3)
left_demo = self.create_without_x().shift(LEFT * 3)
# Right side: Concept B
right_title = Text("With X").to_edge(UP).shift(RIGHT * 3)
right_demo = self.create_with_x().shift(RIGHT * 3)
# Show side by side
self.play(Write(left_title), Write(right_title))
self.play(Create(left_demo), Create(right_demo))
# Animate differences
self.highlight_differences(left_demo, right_demo)
# Conclusion
self.play(FadeOut(line), FadeOut(left_demo), FadeOut(left_title))
self.play(right_demo.animate.move_to(ORIGIN))
conclusion = Text("X makes the difference!", color=GREEN).next_to(right_demo, DOWN)
self.play(Write(conclusion))Emphasis Techniques
Color Highlighting
# Fade everything except the focus
self.play(
other_elements.animate.set_opacity(0.3),
focus_element.animate.set_color(YELLOW)
)Scale Emphasis
# Grow important element
self.play(important.animate.scale(1.5))Indicator Animation
# Pulse attention
self.play(Indicate(element, color=RED))Surrounding Highlight
# Circle the important part
circle = Circle(color=YELLOW).surround(element)
self.play(Create(circle))Pacing Guidelines
| Content Type | Wait Time | Animation Speed |
|---|---|---|
| New concept | 2-3 sec | Slow (run_time=2) |
| Step in process | 1-2 sec | Medium (run_time=1) |
| Transition | 0.5 sec | Fast (run_time=0.5) |
| Final reveal | 3-4 sec | Slow with emphasis |
Best Practices
1. One idea at a time - Don't introduce multiple concepts simultaneously 2. Build on prior knowledge - Connect new ideas to what's already shown 3. Use consistent visual language - Same colors/shapes for same concepts 4. Allow breathing room - Silence and stillness aid comprehension 5. End with synthesis - Bring everything together at the conclusion
"""
Example: The Story of Gravity
Demonstrates narrative-driven educational animation
"""
from manim import *
class GravityStory(Scene):
def construct(self):
# Act 1: The Question
self.act_question()
# Transition
self.play(*[FadeOut(m) for m in self.mobjects])
# Act 2: Newton's Discovery
self.act_newton()
# Transition
self.play(*[FadeOut(m) for m in self.mobjects])
# Act 3: The Law
self.act_law()
def act_question(self):
"""Hook: Why do things fall?"""
question = Text("Why do things fall?", font_size=40)
self.play(Write(question))
self.wait()
# Show falling objects
apple = Circle(radius=0.3, color=RED, fill_opacity=1)
apple.move_to(UP * 3)
ground = Line(LEFT * 5, RIGHT * 5, color=GREEN).to_edge(DOWN)
self.play(FadeIn(apple), Create(ground))
self.play(question.animate.to_edge(UP))
# Apple falls
self.play(apple.animate.next_to(ground, UP, buff=0), run_time=1.5)
# More objects
objects = VGroup(
Square(side_length=0.4, color=BLUE, fill_opacity=1),
Triangle(color=YELLOW, fill_opacity=1).scale(0.3),
)
for i, obj in enumerate(objects):
obj.move_to(UP * 3 + RIGHT * (i + 1) * 1.5)
self.play(FadeIn(obj), run_time=0.3)
self.play(obj.animate.next_to(ground, UP, buff=0), run_time=1)
self.wait()
def act_newton(self):
"""The discovery moment"""
# Newton under tree
story = Text("1666 - Isaac Newton", font_size=30).to_edge(UP)
self.play(Write(story))
tree = self.create_tree()
tree.to_edge(LEFT).shift(DOWN)
self.play(FadeIn(tree))
# Apple falls - the famous moment
apple = Circle(radius=0.2, color=RED, fill_opacity=1)
apple.next_to(tree, UP + RIGHT, buff=0)
self.play(FadeIn(apple))
self.wait(0.5)
# Dramatic fall
self.play(
apple.animate.shift(DOWN * 3),
run_time=1,
rate_func=rate_functions.ease_in_quad
)
# Lightbulb moment
idea = Text("💡", font_size=60).move_to(RIGHT * 2)
insight = Text(
"Everything attracts everything!",
font_size=28
).next_to(idea, DOWN)
self.play(FadeIn(idea, scale=2))
self.play(Write(insight))
self.wait(2)
def act_law(self):
"""The universal law"""
title = Text("Newton's Law of Gravitation", font_size=36)
title.to_edge(UP)
self.play(Write(title))
# The famous equation
equation = MathTex(
"F", "=", "G", "\\frac{m_1 m_2}{r^2}"
).scale(1.5)
self.play(Write(equation))
self.wait()
# Explain each part
explanations = [
("F", "Gravitational Force", RED),
("G", "Universal Constant", BLUE),
("m_1 m_2", "Two Masses", GREEN),
("r^2", "Distance Squared", YELLOW),
]
for i, (symbol, meaning, color) in enumerate(explanations):
equation[0 if symbol == "F" else (2 if symbol == "G" else 3)].set_color(color)
label = Text(f"{symbol}: {meaning}", font_size=20, color=color)
label.to_edge(DOWN).shift(UP * (i * 0.5))
self.play(FadeIn(label), run_time=0.5)
self.wait(0.5)
self.wait(2)
def create_tree(self):
trunk = Rectangle(width=0.3, height=1.5, color=DARK_BROWN, fill_opacity=1)
leaves = Circle(radius=1, color=GREEN, fill_opacity=1)
leaves.next_to(trunk, UP, buff=-0.2)
return VGroup(trunk, leaves)
"""
Example: Bubble Sort Visual Explanation
Demonstrates step-by-step algorithm visualization
"""
from manim import *
class BubbleSortExplanation(Scene):
def construct(self):
# Title
title = Text("Bubble Sort", font_size=48)
subtitle = Text("The simplest sorting algorithm", font_size=24, color=GRAY)
header = VGroup(title, subtitle).arrange(DOWN)
self.play(Write(title))
self.play(FadeIn(subtitle))
self.wait(1)
self.play(FadeOut(header))
# Create bars representing numbers
values = [5, 2, 8, 1, 9, 3]
bars = self.create_bars(values)
bars.move_to(ORIGIN)
self.play(LaggedStart(*[GrowFromEdge(bar, DOWN) for bar in bars], lag_ratio=0.1))
self.wait()
# Explain the concept
explanation = Text(
"Compare adjacent pairs and swap if needed",
font_size=24
).to_edge(UP)
self.play(Write(explanation))
# Perform bubble sort with visualization
self.bubble_sort_animate(bars, values)
# Conclusion
self.play(FadeOut(explanation))
success = Text("Sorted!", font_size=36, color=GREEN)
self.play(Write(success))
self.play(
*[bar.animate.set_color(GREEN) for bar in bars],
run_time=0.5
)
self.wait(2)
def create_bars(self, values):
bars = VGroup()
max_val = max(values)
for i, val in enumerate(values):
height = val / max_val * 3
bar = Rectangle(
width=0.8,
height=height,
fill_opacity=0.8,
fill_color=BLUE,
stroke_color=WHITE
)
label = Text(str(val), font_size=20).next_to(bar, UP, buff=0.1)
bar.add(label)
bars.add(bar)
bars.arrange(RIGHT, buff=0.2, aligned_edge=DOWN)
return bars
def bubble_sort_animate(self, bars, values):
n = len(values)
for i in range(n):
for j in range(n - i - 1):
# Highlight comparison
self.play(
bars[j].animate.set_color(YELLOW),
bars[j + 1].animate.set_color(YELLOW),
run_time=0.3
)
if values[j] > values[j + 1]:
# Swap needed
values[j], values[j + 1] = values[j + 1], values[j]
# Animate swap
self.play(
bars[j].animate.shift(RIGHT * 1),
bars[j + 1].animate.shift(LEFT * 1),
run_time=0.5
)
bars[j], bars[j + 1] = bars[j + 1], bars[j]
# Reset colors
self.play(
bars[j].animate.set_color(BLUE),
bars[j + 1].animate.set_color(BLUE),
run_time=0.2
)
Community Edition Implementation Patterns
Best practices for implementing animations with the Manim Community Edition renderer.
Import Patterns
# Standard import (recommended)
from manim import *
# Selective imports for clarity
from manim import Scene, Circle, Square, Write, FadeIn, MathTexScene Types
Standard Scene
class MyAnimation(Scene):
def construct(self):
circle = Circle()
self.play(Create(circle))
self.wait()3D Scene
class My3DAnimation(ThreeDScene):
def construct(self):
self.set_camera_orientation(phi=75 * DEGREES, theta=30 * DEGREES)
sphere = Sphere()
self.play(Create(sphere))Moving Camera Scene
class CameraAnimation(MovingCameraScene):
def construct(self):
circle = Circle()
self.play(Create(circle))
self.play(self.camera.frame.animate.scale(0.5).move_to(circle))Core Animations
Creation Animations
| Animation | Use Case |
|---|---|
Create(mobject) | Draw shapes stroke-first |
Write(text) | Write text/equations |
FadeIn(mobject) | Gentle appearance |
GrowFromCenter(mobject) | Expand from center |
DrawBorderThenFill(mobject) | Outline then fill |
Transformation Animations
| Animation | Use Case |
|---|---|
Transform(a, b) | Morph a into b |
ReplacementTransform(a, b) | Replace a with b |
TransformMatchingTex(a, b) | Match LaTeX parts |
TransformMatchingShapes(a, b) | Match shape components |
Exit Animations
| Animation | Use Case |
|---|---|
FadeOut(mobject) | Gentle disappearance |
Uncreate(mobject) | Reverse of Create |
ShrinkToCenter(mobject) | Collapse to center |
LaTeX Rendering
Basic Math
# Simple equation
eq = MathTex("E = mc^2")
# With color isolation
eq = MathTex("E", "=", "m", "c^2")
eq[0].set_color(YELLOW) # E
eq[3].set_color(BLUE) # c^2Using substrings_to_isolate
eq = MathTex(
"x^2 + 2x + 1 = 0",
substrings_to_isolate=["x", "1", "0"]
)
eq.set_color_by_tex("x", BLUE)
eq.set_color_by_tex("1", GREEN)Text with Math
text = Tex("The area is ", "$A = \\pi r^2$")CLI Usage
# Preview with low quality (fast)
manim -pql script.py SceneName
# Preview with medium quality
manim -pqm script.py SceneName
# High quality render
manim -pqh script.py SceneName
# 4K render
manim -pqk script.py SceneName
# Save last frame as image
manim -pql -s script.py SceneName
# Transparent background
manim -pql -t script.py SceneNameQuality Presets
| Flag | Resolution | FPS | Use Case |
|---|---|---|---|
-ql | 480p | 15 | Quick preview |
-qm | 720p | 30 | Development |
-qh | 1080p | 60 | Production |
-qk | 4K | 60 | High-end production |
Debugging Techniques
Visual Debugging
# Add without animation (instant)
self.add(circle)
# Show bounding box
circle.add(SurroundingRectangle(circle))
# Add coordinate labels
self.add(NumberPlane())Print Debugging
def construct(self):
circle = Circle()
print(f"Circle center: {circle.get_center()}")
print(f"Circle radius: {circle.radius}")
print(f"Circle color: {circle.get_color()}")Interactive Mode
# Pause and inspect
self.wait()
self.interactive_embed() # Opens interactive shellAnimation Timing
Run Time Control
self.play(Create(circle), run_time=2) # 2 seconds
self.play(FadeIn(square), run_time=0.5) # FastSimultaneous Animations
# All at once
self.play(Create(circle), Write(text), FadeIn(square))
# With AnimationGroup
self.play(AnimationGroup(
Create(circle),
Write(text),
lag_ratio=0.5 # Stagger start times
))Sequential with Succession
self.play(Succession(
Create(circle),
circle.animate.shift(RIGHT),
FadeOut(circle)
))Common Patterns
Animate Method
# Fluent animation syntax
self.play(circle.animate.shift(RIGHT).scale(2).set_color(RED))Value Tracking
tracker = ValueTracker(0)
number = always_redraw(lambda: DecimalNumber(tracker.get_value()))
self.play(tracker.animate.set_value(100), run_time=3)Updaters
dot = Dot()
label = always_redraw(lambda: Text(f"({dot.get_x():.1f}, {dot.get_y():.1f})").next_to(dot, UP))
self.add(dot, label)
self.play(dot.animate.shift(RIGHT * 3))Anti-Patterns
Avoid
# Don't modify mobjects during play
self.play(Create(circle))
circle.shift(RIGHT) # Wrong - not animated
# Don't forget to add mobjects
text = Text("Hello")
self.play(text.animate.shift(UP)) # Error - text not addedPrefer
# Animate changes
self.play(Create(circle))
self.play(circle.animate.shift(RIGHT)) # Correct
# Add before animating
text = Text("Hello")
self.add(text)
self.play(text.animate.shift(UP)) # Or use FadeInEmphasis Techniques
The Emphasis Hierarchy
From subtle to strong: 1. Position - Center stage = important 2. Size - Larger = more important 3. Color - Bright/contrast = attention 4. Animation - Movement catches eye 5. Isolation - Remove distractions
Position-Based Emphasis
Center Stage
# Most important element at center
key_element.move_to(ORIGIN)
supporting.to_edge(LEFT)
context.to_edge(RIGHT)Golden Ratio Positioning
# Professional composition
# Place key elements at 1/3 and 2/3 points
left_focus = LEFT * 2.5 # ~1/3 from left
right_focus = RIGHT * 2.5 # ~2/3 from leftForeground/Background
# Foreground = emphasized
important.set_z_index(10)
background.set_z_index(0)Size-Based Emphasis
Scale Animation
# Draw attention by scaling
self.play(important.animate.scale(1.5))
# Pulse effect (scale up then back)
self.play(important.animate.scale(1.3))
self.play(important.animate.scale(1/1.3))Relative Size
# Important elements larger from the start
main_point = Text("Key Concept", font_size=48)
sub_point = Text("Supporting detail", font_size=24)Color-Based Emphasis
Highlight Colors
# Standard highlight palette
EMPHASIS_PRIMARY = YELLOW
EMPHASIS_SECONDARY = GOLD
EMPHASIS_ALERT = RED
EMPHASIS_SUCCESS = GREENColor Change Animation
# Draw attention with color
self.play(element.animate.set_color(YELLOW))
# Gradient emphasis
element.set_color_by_gradient(BLUE, YELLOW)Dimming Others
# Emphasize by reducing others
all_elements = VGroup(elem1, elem2, elem3, focus_elem)
others = VGroup(elem1, elem2, elem3)
self.play(
others.animate.set_opacity(0.3),
focus_elem.animate.set_color(YELLOW)
)Animation-Based Emphasis
Indicate Animation
# Built-in emphasis animation
self.play(Indicate(element, color=YELLOW, scale_factor=1.2))Circumscribe (Circle Attention)
# Draw circle around important element
self.play(Circumscribe(element, color=RED, time_width=2))Flash
# Quick bright flash
self.play(Flash(element.get_center(), color=WHITE))Wiggle
# Attention-grabbing wiggle
self.play(Wiggle(element))Focus Frame
# Box around important content
frame = SurroundingRectangle(element, color=YELLOW, buff=0.2)
self.play(Create(frame))Underline
# Underline key text
underline = Underline(text_element, color=RED)
self.play(Create(underline))Isolation Emphasis
Clear Everything Else
# Remove distractions
other_elements = [e for e in self.mobjects if e != focus_element]
self.play(*[FadeOut(e) for e in other_elements])
self.wait(2) # Focus time
self.play(*[FadeIn(e) for e in other_elements]) # RestoreSpotlight Effect
# Dim surroundings, spotlight on focus
def spotlight(scene, focus, others):
scene.play(
focus.animate.set_opacity(1),
*[o.animate.set_opacity(0.2) for o in others]
)Zoom Isolation
# Zoom camera to focus element
self.play(
self.camera.frame.animate.scale(0.5).move_to(focus_element)
)Combining Techniques
Weak Emphasis (Subtle)
# Just position or small color change
self.play(element.animate.move_to(UP * 0.5))Medium Emphasis (Notice)
# Position + color
self.play(
element.animate.move_to(UP * 0.5).set_color(YELLOW)
)Strong Emphasis (Critical)
# Position + color + size + frame
frame = SurroundingRectangle(element, color=YELLOW)
self.play(
element.animate.move_to(ORIGIN).scale(1.5).set_color(YELLOW),
Create(frame)
)Maximum Emphasis (Climax)
# Everything: isolation + position + color + size + animation
self.play(*[FadeOut(m) for m in self.mobjects if m != element])
self.play(
element.animate.move_to(ORIGIN).scale(2).set_color(GOLD)
)
self.play(Flash(element.get_center(), color=WHITE, num_lines=12))
self.play(Indicate(element))When to Use Each Level
| Situation | Emphasis Level |
|---|---|
| Minor detail | None or weak |
| Step in process | Weak |
| Important point | Medium |
| Key concept | Strong |
| Main takeaway | Strong |
| Climax/aha moment | Maximum |
Anti-Patterns
DON'T: Emphasize everything
# BAD - Nothing stands out
for elem in all_elements:
self.play(Indicate(elem)) # Everything is "important"DON'T: Inconsistent emphasis
# BAD - Confusing signals
self.play(important.animate.scale(0.5)) # Shrinking = less important??
self.play(minor.animate.scale(2)) # Growing = important??DO: Reserve strong emphasis for key moments
# GOOD - Clear hierarchy
for elem in supporting_elements:
self.play(FadeIn(elem)) # Normal
self.play(Indicate(key_element, scale_factor=1.5, color=GOLD)) # EmphasisCLEAR Explanation Framework
A structured approach to creating educational animations that ensure understanding and retention.
The CLEAR Method
Context → Layers → Examples → Analogies → Reinforce
Each component builds upon the previous, creating a complete learning experience.
┌─────────────────────────────────────────────────────────┐
│ CLEAR Framework │
├──────────┬──────────┬──────────┬──────────┬─────────────┤
│ Context │ Layers │ Examples │ Analogies │ Reinforce │
│ 10% │ 40% │ 25% │ 15% │ 10% │
├──────────┼──────────┼──────────┼──────────┼─────────────┤
│ Setup │ Build │ Apply │ Connect │ Solidify │
│ ground │ step by │ to real │ to known │ the │
│ │ step │ cases │ concepts │ learning │
└──────────┴──────────┴──────────┴──────────┴─────────────┘---
C - Context (10% of content)
Purpose
Establish why this matters and what the viewer already knows.
Elements
| Element | Description | Duration |
|---|---|---|
| Hook | Grab attention with a question/problem | 5-10s |
| Relevance | Why should they care? | 10-15s |
| Prerequisites | What they need to know | 5-10s |
| Overview | What we'll cover | 5-10s |
Implementation Pattern
class ContextScene(Scene):
def construct(self):
# Hook - Start with a compelling question
hook = Text("Why do things fall?", font_size=48)
self.play(Write(hook))
self.wait(2)
# Relevance - Connect to their world
relevance = Text(
"Understanding gravity explains everything\n"
"from raindrops to rocket launches",
font_size=32
)
self.play(
FadeOut(hook),
FadeIn(relevance)
)
self.wait(3)
# Overview - Set expectations
overview = BulletedList(
"What gravity is",
"How Newton discovered it",
"The universal law",
font_size=28
)
self.play(
FadeOut(relevance),
Write(overview)
)
self.wait(2)Context Anti-Patterns
| Avoid | Why | Instead |
|---|---|---|
| Jumping straight in | No mental preparation | Start with hook |
| Long introductions | Lose attention | Keep under 30s |
| Assumed knowledge | Confusion later | State prerequisites |
| Vague promises | Low engagement | Be specific |
---
L - Layers (40% of content)
Purpose
Build understanding progressively, from simple to complex.
Layering Strategies
1. Additive Layering
Simple concept
+ First detail
+ Second detail
+ Full complexity2. Zoom-In Layering
Big picture overview
→ Focus on component A
→ Detail of component A
→ Return to big picture
→ Focus on component B3. Scaffold Layering
Foundation concept
↑ builds on
Supporting concept
↑ builds on
Target conceptImplementation Pattern
class LayeredExplanation(Scene):
def construct(self):
# Layer 1: Simplest form
simple = MathTex("F = ma")
self.play(Write(simple))
self.wait(2)
# Layer 2: Add meaning
labels = VGroup(
Text("Force", font_size=24).next_to(simple[0][0], DOWN),
Text("Mass", font_size=24).next_to(simple[0][2], DOWN),
Text("Acceleration", font_size=24).next_to(simple[0][4], DOWN),
)
self.play(FadeIn(labels))
self.wait(2)
# Layer 3: Add context
context = Text(
"The more massive an object,\n"
"the more force needed to accelerate it",
font_size=28
).to_edge(DOWN)
self.play(FadeIn(context))
self.wait(3)
# Layer 4: Show implications
self.play(FadeOut(labels), FadeOut(context))
rearranged = MathTex("a = \\frac{F}{m}")
self.play(TransformMatchingTex(simple, rearranged))
self.wait(2)Pacing Guidelines
| Layer | Purpose | Pacing |
|---|---|---|
| First | Foundation | Slow, deliberate |
| Middle | Building | Moderate |
| Last | Full picture | Can be faster |
---
E - Examples (25% of content)
Purpose
Make abstract concepts concrete through specific instances.
Example Types
| Type | Use When | Effect |
|---|---|---|
| Worked Example | Teaching procedure | Shows steps |
| Counter-Example | Clarifying boundaries | Shows what it's NOT |
| Edge Case | Deepening understanding | Tests limits |
| Real-World | Building relevance | Connects to life |
Implementation Pattern
class ExampleScene(Scene):
def construct(self):
# Set up the example context
title = Text("Example: Dropping a ball", font_size=36)
title.to_edge(UP)
self.play(Write(title))
# Visual setup
ball = Circle(radius=0.3, fill_opacity=1, color=RED)
ball.move_to(UP * 2)
ground = Line(LEFT * 5, RIGHT * 5, color=WHITE)
ground.move_to(DOWN * 2)
self.play(Create(ball), Create(ground))
# Show the physics
formula = MathTex("h = \\frac{1}{2}gt^2")
formula.to_edge(RIGHT)
self.play(Write(formula))
# Animate the example
self.play(
ball.animate.move_to(DOWN * 2 + UP * 0.3),
run_time=2,
rate_func=rate_functions.ease_in_quad
)
# Highlight the connection
result = Text("t ≈ 0.64 seconds", font_size=28)
result.next_to(formula, DOWN)
self.play(Write(result))Example Selection Criteria
Good examples are:
- Familiar: Use everyday objects/situations
- Simple: Minimal distracting details
- Representative: Capture the core concept
- Memorable: Easy to recall later
---
A - Analogies (15% of content)
Purpose
Connect new concepts to existing mental models.
Analogy Types
| Type | Structure | Example |
|---|---|---|
| Direct | "X is like Y" | "An atom is like a solar system" |
| Functional | "X works like Y" | "RAM works like a desk, storage like a filing cabinet" |
| Structural | "X is structured like Y" | "DNA is structured like a twisted ladder" |
| Process | "X happens like Y" | "Electricity flows like water" |
Implementation Pattern
class AnalogyScene(Scene):
def construct(self):
# Show the unfamiliar concept
concept = Text("Electric Current", font_size=36)
concept.to_edge(UP)
self.play(Write(concept))
# Show the familiar analogy
analogy = Text("is like", font_size=28)
familiar = Text("Water Flow", font_size=36)
VGroup(concept, analogy, familiar).arrange(DOWN)
self.play(Write(analogy), Write(familiar))
self.wait(1)
# Visual comparison
self.play(FadeOut(VGroup(concept, analogy, familiar)))
# Left: Water
pipe = Rectangle(width=4, height=1, color=BLUE)
pipe.shift(LEFT * 3)
water = Arrow(LEFT * 1.5, RIGHT * 1.5, color=BLUE)
water.move_to(pipe)
water_label = Text("Water", font_size=24).next_to(pipe, DOWN)
# Right: Electricity
wire = Rectangle(width=4, height=0.3, color=YELLOW)
wire.shift(RIGHT * 3)
current = Arrow(LEFT * 1.5, RIGHT * 1.5, color=YELLOW)
current.move_to(wire)
current_label = Text("Current", font_size=24).next_to(wire, DOWN)
self.play(
Create(pipe), Create(wire),
Create(water), Create(current),
Write(water_label), Write(current_label)
)
self.wait(2)
# Show the mapping
mapping = VGroup(
Text("Pressure → Voltage", font_size=24),
Text("Flow Rate → Current", font_size=24),
Text("Pipe Width → Resistance", font_size=24),
).arrange(DOWN, aligned_edge=LEFT)
mapping.to_edge(DOWN)
for item in mapping:
self.play(FadeIn(item, shift=RIGHT * 0.3))
self.wait(0.5)Analogy Guidelines
| Do | Don't |
|---|---|
| Explain the mapping explicitly | Assume they see the connection |
| Acknowledge limitations | Overextend the analogy |
| Use familiar domains | Use equally unfamiliar analogies |
| Keep it simple | Make it more complex than the concept |
---
R - Reinforce (10% of content)
Purpose
Solidify learning through summary, repetition, and forward connection.
Reinforcement Techniques
| Technique | Description | Implementation |
|---|---|---|
| Summary | Recap key points | Bullet list |
| Callback | Reference earlier content | "Remember when..." |
| Application | Suggest uses | "Now you can..." |
| Preview | Connect to next topic | "Next, we'll see..." |
Implementation Pattern
class ReinforcementScene(Scene):
def construct(self):
# Summary title
title = Text("Key Takeaways", font_size=48)
title.to_edge(UP)
self.play(Write(title))
# Key points
points = VGroup(
Text("✓ Gravity pulls objects toward Earth", font_size=28),
Text("✓ F = mg gives the gravitational force", font_size=28),
Text("✓ All objects fall at the same rate (in vacuum)", font_size=28),
).arrange(DOWN, aligned_edge=LEFT, buff=0.4)
points.move_to(ORIGIN)
for point in points:
self.play(FadeIn(point, shift=RIGHT * 0.3))
self.wait(0.5)
self.wait(2)
# Application teaser
application = Text(
"Now you can calculate the fall time\n"
"of any object from any height!",
font_size=32,
color=YELLOW
).to_edge(DOWN)
self.play(FadeIn(application))
self.wait(2)
# Next topic preview
self.play(FadeOut(VGroup(title, points, application)))
preview = Text(
"Next: How does this apply to orbits?",
font_size=36
)
self.play(Write(preview))
self.wait(2)Memory Techniques
# The Rule of Three
key_points = [
"Point one",
"Point two",
"Point three"
] # Three is optimal for memory
# Repetition with Variation
self.play(Write(concept)) # First exposure
# ... explanation ...
self.play(Indicate(concept)) # Second exposure (different form)
# ... examples ...
self.play(Transform(concept, summary_version)) # Third exposure (summarized)---
Complete CLEAR Scene Template
class CLEARExplanation(Scene):
"""Complete template following CLEAR framework."""
def construct(self):
self.context_phase()
self.layers_phase()
self.examples_phase()
self.analogies_phase()
self.reinforce_phase()
def context_phase(self):
"""10% - Set up the learning."""
pass # Implement hook, relevance, prerequisites
def layers_phase(self):
"""40% - Build understanding."""
pass # Implement progressive complexity
def examples_phase(self):
"""25% - Concrete instances."""
pass # Implement worked examples
def analogies_phase(self):
"""15% - Connect to known."""
pass # Implement visual analogies
def reinforce_phase(self):
"""10% - Solidify learning."""
pass # Implement summary and callbacks---
Measuring Effectiveness
Self-Check Questions
After creating a CLEAR explanation:
1. Context: Could someone skip the intro and still follow? 2. Layers: Is there a logical progression? 3. Examples: Are examples relatable? 4. Analogies: Is the analogy simpler than the concept? 5. Reinforce: Will they remember the key points?
Explanation Patterns for Visual Stories
The "What, Why, How" Pattern
Structure
1. What: Define the concept (5-10 seconds) 2. Why: Explain importance/relevance (10-15 seconds) 3. How: Demonstrate mechanics (20-40 seconds)
Example Implementation
def construct(self):
# WHAT
title = Text("Recursion")
definition = Text("A function that calls itself", font_size=24)
self.play(Write(title))
self.play(FadeIn(definition))
self.wait(2)
# WHY
self.clear()
use_cases = BulletedList(
"Solve complex problems simply",
"Tree traversal",
"Mathematical sequences"
)
self.play(Write(use_cases))
self.wait(3)
# HOW
self.clear()
# ... demonstration animationThe "Problem → Solution" Pattern
Structure
1. Present the problem visually 2. Show failed/naive approaches (optional) 3. Introduce the solution 4. Demonstrate why it works
Visual Cues
- Problem: Red highlights, question marks
- Struggle: Shaking, confusion animations
- Solution: Green highlights, checkmarks
- Success: Celebration effects
The "Build-Up" Pattern
Layer-by-Layer Construction
# Start simple
base = Square()
self.play(Create(base))
# Add complexity
addition1 = Circle().next_to(base, UP)
self.play(Create(addition1))
# Show relationship
connection = Arrow(base, addition1)
self.play(Create(connection))
# Reveal full picture
label = Text("Complete System")
self.play(Write(label))The "Comparison" Pattern
Side-by-Side Analysis
- Show two approaches simultaneously
- Highlight differences with color
- Animate parallel processes
- Conclude with clear winner/summary
The "Zoom" Pattern
Macro to Micro
# Start with big picture
overview = ImageMobject("full_system.png")
self.play(FadeIn(overview))
# Zoom to detail
self.play(
overview.animate.scale(3).shift(LEFT * 2 + UP),
run_time=2
)
# Highlight specific part
highlight = Circle(color=YELLOW).move_to(...)
self.play(Create(highlight))Micro to Macro
- Start with detail
- Pull back to show context
- Connect to larger system
Transition Phrases (Visual)
| Verbal | Visual Equivalent |
|---|---|
| "First..." | Number "1" appears |
| "However..." | Color shift, direction change |
| "Therefore..." | Arrow pointing to conclusion |
| "In summary..." | Elements gather together |
| "For example..." | Box/highlight appears |
Visual Storytelling Principles
Narrative Structure
Three-Act Structure
1. Setup - Introduce the problem or question 2. Confrontation - Explore the concept, show challenges 3. Resolution - Reveal the answer, demonstrate understanding
Story Arc Elements
- Hook: Grab attention in first 3 seconds
- Build: Gradually increase complexity
- Climax: The "aha moment"
- Denouement: Reinforce the lesson
Visual Metaphors
Abstract → Concrete
Transform abstract concepts into tangible visuals:
- Numbers → Objects (5 apples, not "5")
- Growth → Expanding shapes
- Connection → Lines/arrows between elements
- Time → Left-to-right progression
Color as Emotion
| Color | Emotion/Meaning |
|---|---|
| Blue | Trust, stability, calm |
| Red | Urgency, importance, warning |
| Green | Growth, success, nature |
| Yellow | Attention, caution, energy |
| Purple | Creativity, mystery, luxury |
Pacing for Storytelling
Emotional Beats
# Tension building
self.play(obj.animate.scale(1.2), run_time=2)
self.wait(0.5) # Pause for effect
# Release/resolution
self.play(
obj.animate.set_color(GREEN),
Flash(obj),
run_time=0.5
)Rhythm Patterns
- Quick-quick-slow for emphasis
- Consistent rhythm for procedural content
- Irregular rhythm for dramatic effect
Character/Object Consistency
Visual Identity
- Keep consistent colors for recurring elements
- Use similar animation styles for related objects
- Establish visual "characters" early
Transformation Rules
- Show intermediate states during morphs
- Maintain recognizable features when transforming
- Use anticipation before major changes
Techniques
Reveal Strategies
# Progressive reveal
for part in parts:
self.play(FadeIn(part))
self.wait(0.3)
# Dramatic reveal
self.play(
FadeOut(cover),
GrowFromCenter(hidden_element)
)
# Focus reveal (blur surroundings)
self.play(
element.animate.set_opacity(1),
*[other.animate.set_opacity(0.2) for other in others]
)Emphasis Techniques
- Scale up important elements
- Add glow or highlight
- Isolate from other elements
- Use contrasting colors
Avoid
- Information overload
- Inconsistent visual language
- Abrupt topic changes
- Missing context or setup
OpenGL Renderer Implementation Patterns
Best practices for implementing animations with the OpenGL-based renderer (alternative to Cairo).
Key Differences from Community Edition
| Feature | Community Edition | OpenGL Renderer |
|---|---|---|
| Creation animation | Create() | ShowCreation() |
| Color in LaTeX | set_color_by_tex() | t2c parameter |
| Camera control | self.camera.frame | self.frame |
| 3D Scene class | ThreeDScene | Standard Scene |
| Interactive mode | Limited | Full with -se flag |
Import Patterns
# Standard import for OpenGL renderer
from manimlib import *
# Note: Different package nameInteractive Development
Starting Interactive Mode
# Run with interactive embed
manimgl script.py SceneName -se
# The -se flag enables:
# - Pausing at self.embed() calls
# - Live code execution
# - Real-time mobject inspectionUsing embed()
class InteractiveScene(Scene):
def construct(self):
circle = Circle()
self.play(ShowCreation(circle))
# Pause here for interactive exploration
self.embed()
# Continue after exiting embed
self.play(circle.animate.shift(RIGHT))Checkpoint Workflow
def construct(self):
# Create initial state
circle = Circle()
self.play(ShowCreation(circle))
# Save checkpoint
self.checkpoint_paste() # Copy current state
# Experiment from here
self.play(circle.animate.scale(2))LaTeX with Color Coding
Using t2c Parameter
# t2c = "tex to color" dictionary
equation = Tex(
"E = mc^2",
t2c={
"E": YELLOW,
"m": GREEN,
"c": BLUE
}
)Complex Color Mapping
# Multiple characters with same color
formula = Tex(
"\\frac{d}{dx}[x^n] = nx^{n-1}",
t2c={
"x": BLUE,
"n": RED,
"d": GREEN,
"dx": GREEN
}
)Isolating Substrings
# For transformation matching
eq1 = Tex("x^2", " + ", "2x", " + ", "1")
eq2 = Tex("(x + 1)^2")
# Color individual parts
eq1[0].set_color(BLUE) # x^2
eq1[2].set_color(GREEN) # 2xCamera Control
Frame-Based Control
class CameraScene(Scene):
def construct(self):
circle = Circle()
self.play(ShowCreation(circle))
# Access frame directly (not self.camera.frame)
self.play(self.frame.animate.scale(0.5))
self.play(self.frame.animate.move_to(circle))3D Camera Orientation
class My3DScene(Scene):
def construct(self):
# No special 3D scene class needed
surface = Surface(lambda u, v: [u, v, u*v])
self.play(ShowCreation(surface))
# Reorient camera
self.play(self.frame.animate.reorient(45, 60))
# Camera parameters: theta (rotation), phi (elevation)Camera Movements
# Pan
self.play(self.frame.animate.shift(RIGHT * 2))
# Zoom
self.play(self.frame.animate.scale(0.5)) # Zoom in
self.play(self.frame.animate.scale(2)) # Zoom out
# Combined
self.play(
self.frame.animate
.move_to(target)
.scale(0.3)
.reorient(30, 45)
)Animation Syntax
ShowCreation (not Create)
# Correct for OpenGL renderer
self.play(ShowCreation(circle))
# NOT Create(circle) - that's Community EditionOther Animation Differences
# These work the same
self.play(Write(text))
self.play(FadeIn(mobject))
self.play(Transform(a, b))
# Animate method works the same
self.play(circle.animate.shift(RIGHT).scale(2))3D Without Special Classes
class Simple3D(Scene):
def construct(self):
# 3D objects work in regular Scene
axes = ThreeDAxes()
sphere = Sphere(radius=1)
self.play(ShowCreation(axes))
self.play(ShowCreation(sphere))
# Camera reorientation
self.play(self.frame.animate.reorient(60, 70))
# Continuous rotation
self.frame.add_updater(lambda m, dt: m.increment_theta(0.1 * dt))
self.wait(5)Debugging in Interactive Mode
Inspecting Mobjects
def construct(self):
circle = Circle()
self.add(circle)
self.embed()
# In interactive mode, you can:
# >>> circle.get_center()
# >>> circle.set_color(RED)
# >>> self.play(circle.animate.scale(2))Real-Time Modifications
# While in embed(), you can:
# - Create new mobjects
# - Run animations
# - Test transformations
# - Adjust colors and positionsPerformance Optimizations
GPU Acceleration
# OpenGL renderer leverages GPU for:
# - Shader-based rendering
# - Real-time preview
# - Smooth 3D rotationsEfficient Updates
# Use always_redraw for dynamic content
graph = always_redraw(lambda: FunctionGraph(
lambda x: np.sin(x + tracker.get_value()),
x_range=[-PI, PI]
))Common Patterns
Interactive Workflow
# 1. Start with interactive flag
manimgl script.py MyScene -se
# 2. Scene runs until embed()
# 3. Test changes in Python shell
# 4. Type 'exit()' to continue
# 5. Iterate on codeRapid Prototyping
class Prototype(Scene):
def construct(self):
# Quick setup
self.play(ShowCreation(Circle()))
# Pause to experiment
self.embed()
# Continue with refined versionAnti-Patterns
Avoid
# Don't use Create() - use ShowCreation()
self.play(Create(circle)) # Wrong for OpenGL
# Don't use self.camera.frame
self.play(self.camera.frame.animate.scale(0.5)) # WrongPrefer
# Use ShowCreation()
self.play(ShowCreation(circle)) # Correct
# Use self.frame directly
self.play(self.frame.animate.scale(0.5)) # CorrectPacing for Understanding
The Learning Curve Principle
Viewers need time to: 1. See the visual (0.5s minimum) 2. Read any text (varies) 3. Process the meaning (1-3s) 4. Connect to prior knowledge (0.5-1s)
Pacing by Content Type
New Concepts (Slow)
# Introducing something unfamiliar
new_concept = Text("Recursion: A function calling itself")
self.play(Write(new_concept), run_time=2)
self.wait(3) # Long pause for processing
# Support with example
example = Code("def f(): return f()")
self.play(FadeIn(example))
self.wait(2)Familiar Concepts (Medium)
# Building on known knowledge
familiar = Text("Like the factorial function...")
self.play(Write(familiar), run_time=1)
self.wait(1.5)Reinforcement (Fast)
# Repeating what viewer already saw
reminder = Text("Remember: f calls f")
self.play(FadeIn(reminder), run_time=0.5)
self.wait(1)Wait Time Guidelines
Standard Waits
| After... | Wait Time | Why |
|---|---|---|
| Title | 2s | Orient viewer |
| Question | 2-3s | Let them think |
| Simple statement | 1s | Read and absorb |
| Complex statement | 2-3s | Process meaning |
| Key insight | 3-4s | Deep processing |
| Transition | 0.5s | Scene change |
Dynamic Wait Calculation
def calculate_wait(text_content):
"""Estimate appropriate wait time"""
word_count = len(text_content.split())
base_wait = 1.0
# Reading time: ~3 words per second
reading_time = word_count / 3
# Complexity bonus (simple heuristic)
complexity_bonus = 0
if any(word in text_content.lower() for word in ['therefore', 'because', 'however']):
complexity_bonus = 1
return base_wait + reading_time + complexity_bonusRhythm Patterns
The "Teach" Rhythm
[Present] → [Pause] → [Explain] → [Pause] → [Example] → [Long Pause]
1s 1s 1s 1s 1s 2s# Implementation
self.play(Write(concept)) # Present
self.wait(1) # Pause
self.play(Write(explanation)) # Explain
self.wait(1) # Pause
self.play(FadeIn(example)) # Example
self.wait(2) # Long pauseThe "Build" Rhythm
[Element] → [Short] → [Element] → [Short] → [Element] → [Medium] → [Result] → [Long]
0.5s 0.3s 0.5s 0.3s 0.5s 1s 1s 2s# Implementation
for elem in building_blocks[:-1]:
self.play(FadeIn(elem), run_time=0.5)
self.wait(0.3)
self.play(FadeIn(building_blocks[-1])) # Last element
self.wait(1)
self.play(Create(result)) # Show result
self.wait(2)The "Compare" Rhythm
[A] → [Pause] → [B] → [Pause] → [Difference Highlight] → [Long Pause]Signals for Pacing
Speed Up When:
- Reviewing familiar material
- Showing repetitive examples
- Building momentum toward climax
- Viewer has seen similar patterns
Slow Down When:
- Introducing new terminology
- Showing crucial transformations
- Making logical leaps
- Complex visual relationships
The "Breath" Technique
Insert "breathing room" at natural break points:
# Section complete - take a breath
self.wait(1.5)
# Before major topic shift
self.play(FadeOut(*self.mobjects))
self.wait(0.5) # Mental reset
# After climax/key insight
self.play(Indicate(key_element))
self.wait(3) # Let it sink inAvoiding Common Pacing Mistakes
Too Fast
# BAD - No time to process
for concept in [c1, c2, c3, c4, c5]:
self.play(FadeIn(concept), run_time=0.2)
# Everything blurs togetherToo Slow
# BAD - Boring, loses attention
self.play(Write(simple_text), run_time=5)
self.wait(10)
# Viewer's mind wandersInconsistent
# BAD - Jarring
self.wait(3)
self.play(something, run_time=0.1) # Sudden speed change
self.wait(5)
# Creates anxietyJust Right
# GOOD - Matched to content
important_concept = Text("Key insight here")
self.play(Write(important_concept), run_time=1.5)
self.wait(2.5) # Proportional to importance
minor_detail = Text("(side note)")
self.play(FadeIn(minor_detail), run_time=0.5)
self.wait(1) # Less wait for less importanceTesting Your Pacing
1. Watch at normal speed - does it feel rushed? 2. Watch at 1.5x speed - is it still clear? 3. Watch without sound - do pauses feel natural? 4. Show to someone unfamiliar - do they follow?
Progressive Revelation Rules
Core Principle
Never show everything at once. Build understanding layer by layer, allowing viewers to absorb each piece before adding more.
The Information Hierarchy
Level 1: Essential (Show First)
- The main concept or question
- Core visual elements
- What the viewer absolutely needs
Level 2: Supporting (Show Second)
- Details that explain Level 1
- Examples or applications
- Labels and annotations
Level 3: Enriching (Show Last)
- Additional context
- Edge cases or exceptions
- Deeper connections
Revelation Patterns
1. Linear Revelation (Step by Step)
# Each element builds on the last
self.play(Create(step1))
self.wait(1)
self.play(Create(step2)) # Related to step1
self.wait(1)
self.play(Create(step3)) # Builds on step22. Central Expansion (Outward)
# Start with core, expand outward
self.play(Create(center_element)) # Most important
self.wait(0.5)
self.play(
*[Create(e) for e in surrounding_elements] # Supporting
)3. Layer Stacking (Top to Bottom)
# Build depth through layers
self.play(FadeIn(background_layer))
self.play(FadeIn(middle_layer))
self.play(FadeIn(foreground_layer))4. Question-Answer Revelation
# Pose question, then reveal answer
question = Text("Why does water boil?")
self.play(Write(question))
self.wait(2) # Let viewer think
answer_parts = [
"Heat provides energy",
"Molecules move faster",
"Escape as steam"
]
for part in answer_parts:
text = Text(part, font_size=24)
self.play(FadeIn(text))
self.wait(1)Timing for Revelation
| Content Type | Reveal Speed | Wait After |
|---|---|---|
| Single word | 0.3s | 0.5s |
| Short phrase | 0.5s | 1s |
| Sentence | 1s | 1.5s |
| Diagram part | 0.5s | 1s |
| Full diagram | 2s | 2s |
| Key insight | 1s | 3s |
Animation Choices for Revelation
For Text
# Word by word (dramatic)
words = sentence.split()
for word in words:
t = Text(word)
self.play(FadeIn(t, shift=UP * 0.3), run_time=0.2)
self.wait(0.1)
# Typewriter effect
self.play(AddTextLetterByLetter(text), run_time=2)
# Full write (standard)
self.play(Write(text))For Diagrams
# Part by part
for part in diagram_parts:
self.play(Create(part))
# Growing from center
self.play(GrowFromCenter(diagram))
# Tracing (for paths/lines)
self.play(Create(path), run_time=2)For Data
# Bars growing
self.play(LaggedStart(
*[GrowFromEdge(bar, DOWN) for bar in bars],
lag_ratio=0.2
))
# Numbers counting up
number = DecimalNumber(0)
self.play(ChangeDecimalToValue(number, 100), run_time=2)Pacing Guidelines
The 3-Second Rule
No new element should appear less than 3 seconds after a complex reveal.
The Breathing Room Rule
After revealing something important: 1. Wait for viewer to see it (0.5s) 2. Wait for viewer to read it (varies by length) 3. Wait for viewer to understand it (1-2s)
self.play(Write(important_formula))
self.wait(3) # Full breathing room for formulaThe Anticipation Rule
Signal that something is coming before revealing it:
# Create anticipation
self.play(Indicate(related_element))
self.wait(0.5)
# Then reveal
self.play(FadeIn(new_element))Common Mistakes
DON'T: Information dump
# BAD
self.add(elem1, elem2, elem3, elem4, elem5) # All at onceDO: Gradual build
# GOOD
for elem in [elem1, elem2, elem3, elem4, elem5]:
self.play(FadeIn(elem))
self.wait(0.3)DON'T: Reveal and immediately move on
# BAD
self.play(Write(important_concept))
self.play(FadeOut(important_concept)) # No time to absorbDO: Give time to absorb
# GOOD
self.play(Write(important_concept))
self.wait(2) # Absorb
self.play(Indicate(important_concept)) # Reinforce
self.wait(1)Advanced: Conditional Revelation
Hide-Reveal Pattern
# Show blurred/hidden version first
hidden = element.copy().set_opacity(0.2).add(blur_effect)
self.play(FadeIn(hidden))
self.wait(1)
# Then reveal clearly
self.play(Transform(hidden, element))Progressive Detail Pattern
# Start simple, add detail
simple_version = create_simplified()
self.play(Create(simple_version))
self.wait(1)
# Add first layer of detail
self.play(Transform(simple_version, medium_detail))
self.wait(1)
# Full detail
self.play(Transform(simple_version, full_detail))Visual Metaphors Rules
Why Use Visual Metaphors?
Abstract concepts become tangible when connected to familiar visuals:
- Numbers → Physical objects (5 apples, not "5")
- Growth → Expanding shapes, rising graphs
- Connection → Lines, arrows, bridges
- Transformation → Morphing shapes
- Time → Left-to-right movement, timeline
Common Metaphor Mappings
Size = Importance/Quantity
# Larger = more important
important = Circle(radius=1.5, color=YELLOW)
normal = Circle(radius=0.5, color=WHITE)
# Growing = increasing
self.play(circle.animate.scale(2)) # "Value doubled"Color = Category/Emotion
# Consistent color coding
POSITIVE = GREEN
NEGATIVE = RED
NEUTRAL = WHITE
# Emotional mapping
CALM = BLUE
ENERGY = YELLOW
DANGER = REDPosition = Time/Sequence/Hierarchy
# Left to right = past to future
past.to_edge(LEFT)
present.move_to(ORIGIN)
future.to_edge(RIGHT)
# Top to bottom = hierarchy
boss.to_edge(UP)
employee.to_edge(DOWN)
# Center = focus/importance
main_topic.move_to(ORIGIN)Movement = Change/Process
# Moving right = progress
self.play(progress_bar.animate.shift(RIGHT * 5))
# Moving up = improvement
self.play(value.animate.shift(UP * 2))
# Rotation = cycle/repetition
self.play(Rotate(cycle_diagram, angle=2*PI))Opacity = Relevance/Focus
# Dim = less important now
self.play(background_info.animate.set_opacity(0.3))
# Full opacity = current focus
self.play(current_topic.animate.set_opacity(1))Creating Effective Metaphors
Rule 1: Use Familiar Objects
# GOOD - Everyone knows these
apple = Circle(color=RED) # Fruit
house = Rectangle() # Building
person = SVGMobject("stick_figure.svg")
# BAD - Obscure or abstract
complex_symbol = MathTex(r"\aleph_0") # Not universally knownRule 2: Maintain Consistency
# If "x" is a blue circle in Scene 1, it must be blue circle throughout
x_visual = Circle(color=BLUE)
# NEVER change to square or different color mid-explanationRule 3: Match Metaphor to Concept
# Concept: Division
# Metaphor: Cutting a pie
pie = Circle(fill_opacity=0.8)
slice_line = Line(ORIGIN, UP)
self.play(Create(slice_line)) # "Dividing"
# Concept: Multiplication
# Metaphor: Grid/array of objects
objects = VGroup(*[Square() for _ in range(12)])
objects.arrange_in_grid(rows=3, cols=4) # "3 × 4 = 12"Domain-Specific Metaphors
Mathematics
| Concept | Metaphor |
|---|---|
| Addition | Combining groups |
| Subtraction | Removing from group |
| Functions | Machine with input/output |
| Limits | Approaching a barrier |
| Derivatives | Slope of hill |
| Integrals | Area under curve |
Computer Science
| Concept | Metaphor |
|---|---|
| Variables | Labeled boxes |
| Arrays | Row of boxes |
| Recursion | Russian nesting dolls |
| Stack | Stack of plates |
| Queue | Line of people |
| Tree | Family tree / actual tree |
Physics
| Concept | Metaphor |
|---|---|
| Force | Arrows/vectors |
| Energy | Glowing aura |
| Waves | Ocean waves |
| Gravity | Pulling down |
| Electricity | Flowing water |
Animation Techniques for Metaphors
Transformation Metaphor (A becomes B)
# Show conceptual transformation
water = SVGMobject("water_drop")
steam = SVGMobject("steam_cloud")
self.play(Transform(water, steam)) # EvaporationContainer Metaphor (X contains Y)
# Variable as box
box = Square()
value = Text("42")
self.play(Create(box))
self.play(value.animate.move_to(box)) # Value stored in variableFlow Metaphor (X leads to Y)
# Process flow
arrow = Arrow(start.get_right(), end.get_left())
self.play(Create(arrow))
# Animated particle along arrow
dot = Dot()
self.play(MoveAlongPath(dot, arrow))Balance Metaphor (X equals Y)
# Show equality as balance
scale = VGroup(
Line(LEFT * 2, RIGHT * 2), # Balance beam
Triangle().scale(0.3).next_to(ORIGIN, DOWN) # Fulcrum
)
left_weight = Square().move_to(LEFT * 1.5)
right_weight = Square().move_to(RIGHT * 1.5)
# Scale is level when equalCommon Pitfalls
DON'T: Mix metaphors
# BAD - Confusing
variable_as_box = Square() # Box metaphor
variable_as_bucket = Arc() # Now bucket?? Pick one!DON'T: Over-complicate
# BAD - Metaphor harder than concept
# Using a 3D rotating hypercube to represent a simple arrayDO: Keep it simple and consistent
# GOOD - One clear metaphor throughout
array_boxes = VGroup(*[Square() for _ in range(5)])
array_boxes.arrange(RIGHT)
# Always use boxes for arrays in this animation"""
Template: Process Visualization
Use for algorithms, workflows, and step-by-step procedures
"""
from manim import *
import numpy as np
class ProcessVisualization(Scene):
"""
Base template for visualizing processes and algorithms.
Features: State highlighting, step counters, comparison indicators.
"""
# Override in subclass
PROCESS_NAME = "Process Name"
STEPS = [
{"name": "Step 1", "description": "First step description"},
{"name": "Step 2", "description": "Second step description"},
{"name": "Step 3", "description": "Third step description"},
]
def construct(self):
# Title
self.show_title()
# Process visualization
self.setup_visualization()
self.run_process()
# Summary
self.show_summary()
def show_title(self):
"""Display process title."""
title = Text(self.PROCESS_NAME, font_size=48)
self.play(Write(title))
self.wait(1)
self.play(FadeOut(title))
def setup_visualization(self):
"""Set up the visual elements. Override in subclass."""
pass
def run_process(self):
"""Run through the process steps. Override in subclass."""
for i, step in enumerate(self.STEPS):
self.show_step_indicator(i)
self.execute_step(i, step)
def show_step_indicator(self, step_index):
"""Show current step number and name."""
step = self.STEPS[step_index]
indicator = VGroup(
Text(f"Step {step_index + 1}/{len(self.STEPS)}", font_size=24, color=BLUE),
Text(step["name"], font_size=20, color=GREY_A)
).arrange(DOWN, buff=0.1)
indicator.to_corner(UR)
if hasattr(self, 'current_indicator'):
self.play(
FadeOut(self.current_indicator),
FadeIn(indicator)
)
else:
self.play(FadeIn(indicator))
self.current_indicator = indicator
def execute_step(self, index, step):
"""Execute a single step. Override in subclass."""
# Default: show step description
desc = Text(step["description"], font_size=28)
self.play(Write(desc))
self.wait(1)
self.play(FadeOut(desc))
def show_summary(self):
"""Show process summary."""
if hasattr(self, 'current_indicator'):
self.play(FadeOut(self.current_indicator))
summary = Text("Process Complete!", font_size=36, color=GREEN)
self.play(FadeIn(summary, scale=1.2))
self.wait(2)
# =============================================================================
# SORTING ALGORITHM VISUALIZATION
# =============================================================================
class SortingVisualization(Scene):
"""Template for sorting algorithm visualization."""
ALGORITHM_NAME = "Sorting Algorithm"
INITIAL_ARRAY = [5, 2, 8, 1, 9, 3, 7, 4, 6]
BAR_WIDTH = 0.6
BAR_SPACING = 0.1
def construct(self):
self.comparisons = 0
self.swaps = 0
# Title
title = Text(self.ALGORITHM_NAME, font_size=42)
title.to_edge(UP)
self.play(Write(title))
# Create bars
self.bars = self.create_bars(self.INITIAL_ARRAY)
self.play(LaggedStart(
*[GrowFromEdge(bar, DOWN) for bar in self.bars],
lag_ratio=0.1
))
# Stats display
self.stats = self.create_stats_display()
self.play(FadeIn(self.stats))
# Run sorting
self.sort()
# Final state
self.show_sorted()
def create_bars(self, values):
"""Create bar chart from values."""
bars = VGroup()
max_val = max(values)
for i, val in enumerate(values):
height = (val / max_val) * 4 # Scale to max height of 4
bar = Rectangle(
width=self.BAR_WIDTH,
height=height,
fill_opacity=0.8,
fill_color=BLUE,
stroke_color=WHITE,
stroke_width=1
)
bar.value = val
bar.move_to(
DOWN * 2 +
LEFT * (len(values) / 2 - i) * (self.BAR_WIDTH + self.BAR_SPACING)
)
bar.align_to(DOWN * 2, DOWN)
# Value label
label = Text(str(val), font_size=20)
label.next_to(bar, DOWN, buff=0.1)
bar.label = label
bars.add(VGroup(bar, label))
return bars
def create_stats_display(self):
"""Create statistics display."""
self.comp_text = Text(f"Comparisons: {self.comparisons}", font_size=20)
self.swap_text = Text(f"Swaps: {self.swaps}", font_size=20)
stats = VGroup(self.comp_text, self.swap_text)
stats.arrange(RIGHT, buff=1)
stats.to_edge(DOWN, buff=0.5)
return stats
def update_stats(self):
"""Update statistics display."""
new_comp = Text(f"Comparisons: {self.comparisons}", font_size=20)
new_swap = Text(f"Swaps: {self.swaps}", font_size=20)
new_comp.move_to(self.comp_text)
new_swap.move_to(self.swap_text)
self.play(
Transform(self.comp_text, new_comp),
Transform(self.swap_text, new_swap),
run_time=0.2
)
def compare(self, i, j):
"""Highlight comparison between two elements."""
self.comparisons += 1
bar_i = self.bars[i][0]
bar_j = self.bars[j][0]
# Highlight compared bars
self.play(
bar_i.animate.set_fill(YELLOW),
bar_j.animate.set_fill(YELLOW),
run_time=0.3
)
self.update_stats()
result = bar_i.value > bar_j.value
# Show comparison result
if result:
indicator = Text(">", font_size=36, color=RED)
else:
indicator = Text("≤", font_size=36, color=GREEN)
indicator.move_to((bar_i.get_center() + bar_j.get_center()) / 2 + UP * 2.5)
self.play(FadeIn(indicator), run_time=0.2)
self.wait(0.3)
self.play(FadeOut(indicator), run_time=0.2)
# Reset colors
self.play(
bar_i.animate.set_fill(BLUE),
bar_j.animate.set_fill(BLUE),
run_time=0.2
)
return result
def swap(self, i, j):
"""Swap two elements with animation."""
self.swaps += 1
bar_i = self.bars[i]
bar_j = self.bars[j]
# Get positions
pos_i = bar_i.get_center()
pos_j = bar_j.get_center()
# Swap animation
self.play(
bar_i.animate.move_to(pos_j),
bar_j.animate.move_to(pos_i),
run_time=0.5
)
# Swap in list
self.bars[i], self.bars[j] = self.bars[j], self.bars[i]
self.update_stats()
def mark_sorted(self, indices):
"""Mark elements as sorted."""
for i in indices:
bar = self.bars[i][0]
self.play(bar.animate.set_fill(GREEN), run_time=0.2)
def sort(self):
"""Override with specific sorting algorithm."""
# Default: bubble sort
n = len(self.bars)
for i in range(n):
for j in range(0, n - i - 1):
if self.compare(j, j + 1):
self.swap(j, j + 1)
self.mark_sorted([n - i - 1])
def show_sorted(self):
"""Final sorted state."""
self.wait(1)
complete = Text("Sorted!", font_size=48, color=GREEN)
complete.to_edge(UP, buff=1.5)
final_stats = Text(
f"Total: {self.comparisons} comparisons, {self.swaps} swaps",
font_size=24
)
final_stats.next_to(complete, DOWN)
self.play(
Write(complete),
Write(final_stats)
)
self.wait(2)
# =============================================================================
# FLOWCHART VISUALIZATION
# =============================================================================
class FlowchartVisualization(Scene):
"""Template for flowchart/decision process visualization."""
def construct(self):
# Build flowchart
nodes, edges = self.create_flowchart()
# Animate construction
self.play(LaggedStart(
*[Create(node) for node in nodes],
lag_ratio=0.2
))
self.play(LaggedStart(
*[Create(edge) for edge in edges],
lag_ratio=0.1
))
# Walk through process
self.walk_through_process(nodes)
def create_flowchart(self):
"""Create flowchart nodes and edges. Override in subclass."""
# Example flowchart
nodes = VGroup()
edges = VGroup()
# Start node
start = self.create_node("Start", "oval", UP * 3)
nodes.add(start)
# Process node
process = self.create_node("Process", "rect", UP * 1)
nodes.add(process)
# Decision node
decision = self.create_node("Decision?", "diamond", DOWN * 1)
nodes.add(decision)
# End nodes
yes_end = self.create_node("Action A", "rect", DOWN * 3 + LEFT * 2)
no_end = self.create_node("Action B", "rect", DOWN * 3 + RIGHT * 2)
nodes.add(yes_end, no_end)
# Edges
edges.add(self.create_edge(start, process))
edges.add(self.create_edge(process, decision))
edges.add(self.create_edge(decision, yes_end, label="Yes"))
edges.add(self.create_edge(decision, no_end, label="No"))
return nodes, edges
def create_node(self, text, shape, position):
"""Create a flowchart node."""
label = Text(text, font_size=24)
if shape == "oval":
border = Ellipse(width=2.5, height=1, color=WHITE)
elif shape == "rect":
border = Rectangle(width=2.5, height=1, color=WHITE)
elif shape == "diamond":
border = Square(side_length=1.5, color=WHITE)
border.rotate(PI / 4)
border.scale([1.5, 1, 1])
else:
border = Rectangle(width=2.5, height=1, color=WHITE)
node = VGroup(border, label)
node.move_to(position)
node.label_text = text
return node
def create_edge(self, from_node, to_node, label=None):
"""Create an edge between nodes."""
start = from_node.get_bottom()
end = to_node.get_top()
arrow = Arrow(start, end, buff=0.1, color=GREY_A)
if label:
label_text = Text(label, font_size=18, color=YELLOW)
label_text.next_to(arrow.get_center(), RIGHT, buff=0.1)
return VGroup(arrow, label_text)
return arrow
def walk_through_process(self, nodes):
"""Animate walking through the flowchart."""
for node in nodes:
# Highlight current node
self.play(
node[0].animate.set_color(YELLOW),
run_time=0.3
)
self.wait(1)
self.play(
node[0].animate.set_color(WHITE),
run_time=0.3
)
# =============================================================================
# STATE MACHINE VISUALIZATION
# =============================================================================
class StateMachineVisualization(Scene):
"""Template for state machine/finite automata visualization."""
STATES = ["S0", "S1", "S2"]
TRANSITIONS = [
("S0", "S1", "a"),
("S1", "S2", "b"),
("S2", "S0", "c"),
]
INITIAL_STATE = "S0"
INPUT_SEQUENCE = ["a", "b", "c", "a"]
def construct(self):
# Create state machine
self.state_nodes = {}
self.create_state_machine()
# Run input sequence
self.process_input(self.INPUT_SEQUENCE)
def create_state_machine(self):
"""Create state machine visualization."""
# Create states in a circle
n_states = len(self.STATES)
radius = 2.5
for i, state in enumerate(self.STATES):
angle = PI / 2 - (2 * PI * i / n_states)
pos = radius * np.array([np.cos(angle), np.sin(angle), 0])
circle = Circle(radius=0.5, color=WHITE)
label = Text(state, font_size=24)
node = VGroup(circle, label)
node.move_to(pos)
node.state_name = state
self.state_nodes[state] = node
self.play(Create(node), run_time=0.5)
# Highlight initial state
initial = self.state_nodes[self.INITIAL_STATE]
self.play(initial[0].animate.set_color(GREEN))
# Create transitions
for from_state, to_state, symbol in self.TRANSITIONS:
from_node = self.state_nodes[from_state]
to_node = self.state_nodes[to_state]
arrow = Arrow(
from_node.get_center(),
to_node.get_center(),
buff=0.6,
color=GREY_A
)
label = Text(symbol, font_size=20, color=YELLOW)
label.move_to(arrow.get_center() + UP * 0.3)
self.play(Create(arrow), Write(label), run_time=0.3)
def process_input(self, inputs):
"""Process input sequence with visualization."""
# Show input sequence
input_display = Text(
f"Input: {' '.join(inputs)}",
font_size=28
)
input_display.to_edge(DOWN)
self.play(Write(input_display))
current_state = self.INITIAL_STATE
for i, symbol in enumerate(inputs):
# Highlight current input
# (simplified - in practice, highlight specific character)
# Find transition
for from_s, to_s, sym in self.TRANSITIONS:
if from_s == current_state and sym == symbol:
# Animate transition
from_node = self.state_nodes[from_s]
to_node = self.state_nodes[to_s]
# Highlight path
self.play(
from_node[0].animate.set_color(YELLOW),
run_time=0.3
)
self.play(
to_node[0].animate.set_color(GREEN),
from_node[0].animate.set_color(WHITE),
run_time=0.3
)
current_state = to_s
break
self.wait(0.5)
# Final state
final = Text(f"Final State: {current_state}", font_size=24, color=GREEN)
final.next_to(input_display, UP)
self.play(Write(final))
self.wait(2)
# =============================================================================
# COMPLEXITY ANNOTATION HELPERS
# =============================================================================
def create_complexity_badge(big_o, color=BLUE):
"""Create a complexity notation badge."""
text = MathTex(f"O({big_o})", font_size=28)
box = SurroundingRectangle(text, buff=0.15, color=color, corner_radius=0.1)
return VGroup(box, text)
def create_comparison_table(data, headers=None):
"""
Create a comparison table.
Args:
data: List of rows, each row is a list of cell values
headers: Optional header row
"""
table = VGroup()
if headers:
data = [headers] + data
for row_idx, row in enumerate(data):
row_group = VGroup()
for col_idx, cell in enumerate(row):
cell_text = Text(str(cell), font_size=20)
cell_text.move_to(RIGHT * col_idx * 2)
if row_idx == 0 and headers:
cell_text.set_color(YELLOW)
row_group.add(cell_text)
row_group.move_to(DOWN * row_idx * 0.6)
table.add(row_group)
table.center()
return table