🛠️ Setup & first run
Install Manim, test your environment, and render your first scene.
Write Manim code, render it, preview it, and download the result — all in one place.
Public preview is open to everyone. Guests can use up to 5 AI prompts and 5 renders per day, then sign in for full access.
Install Manim, test your environment, and render your first scene.
Learn text, LaTeX, layout, simple animations, and clean scene structure.
Move from small demos to graphs, parameter shifts, and event-ready clips.
A distinct example that uses both an SVG image and an audio file, so students can immediately see that this lab supports more than plain text scenes.
SVGMobject("invmath.svg")self.add_sound("intro.mp3")This is intentionally separate from the main modules so it feels like a ready-made showcase, not just another lesson.
ImageMobject("photo.png") or SVGMobject("logo.svg").self.add_sound("intro.mp3").
A simple scene needs a Scene class, a construct method, and one animation.
from manim import *
class HelloWorld(Scene):
def construct(self):
text = Tex("Hello, InvariantMath!")
self.play(Write(text))
self.wait(1)
Use text for titles and MathTex for mathematics.
title = Text("ODE–Integration Bee 2.0")
equation = MathTex(r"\int_0^1 x^n\,dx = \frac{1}{n+1}")
title.to_edge(UP)
equation.next_to(title, DOWN, buff=0.5)
This is a gentle first taste of mathematical motion: a curve starts concentrated, then spreads and softens as time moves forward.
from manim import *
import numpy as np
class HeatEquationDiffusion(Scene):
def construct(self):
self.camera.background_color = "#050816"
axes = Axes(
x_range=[-4, 4, 1],
y_range=[0, 3, 1],
x_length=10,
y_length=4,
axis_config={"include_ticks": False},
tips=False,
)
t = ValueTracker(0.05)
def heat(x, time):
sigma = 0.1 + time
return 2*np.exp(-(x**2)/(2*sigma)) / np.sqrt(1+4*time) + 0.3
graph = always_redraw(
lambda: axes.plot(
lambda x: heat(x, t.get_value()),
color=GOLD,
stroke_width=5
)
)
area = always_redraw(
lambda: axes.get_area(
graph,
x_range=[-4, 4],
color=ORANGE,
opacity=0.2
)
)
self.play(Create(axes))
self.play(FadeIn(area), Create(graph))
self.play(t.animate.set_value(2), run_time=6, rate_func=linear)
self.wait()
Some directions fall into the centre, others move away from it. This scene makes that contrast visible at a glance.
from manim import *
import numpy as np
class SaddlePoint(Scene):
def construct(self):
self.camera.background_color = "#050816"
plane = NumberPlane(
x_range=[-5, 5, 1],
y_range=[-3, 3, 1],
background_line_style={
"stroke_color": BLUE_E,
"stroke_opacity": 0.2,
}
)
def vf(p):
x, y = p[0], p[1]
return np.array([x, -y, 0]) * 0.5
stream = StreamLines(
vf,
stroke_width=1.5,
max_anchors_per_line=30
)
stable = Line(UP * 3, DOWN * 3, color=GREEN)
unstable = Line(LEFT * 4, RIGHT * 4, color=RED)
origin = Dot(ORIGIN, color=WHITE)
self.play(Create(plane))
self.play(Create(stable), Create(unstable), FadeIn(origin))
self.add(stream)
self.play(stream.create(), run_time=6)
self.wait()
This is one of the most magical Manim ideas: circles spin on circles, and together they draw a curve in real time.
from manim import *
import numpy as np
class FourierEpicycles(Scene):
def construct(self):
self.camera.background_color = "#050816"
time = ValueTracker(0)
def circle(radius, speed, phase):
return lambda t: radius*np.exp(1j*(speed*t+phase))
freqs = [
(1, 1, 0),
(0.5, -3, 0),
(0.3, 5, 0),
(0.2, -7, 0),
(0.15, 9, 0)
]
path = VMobject(color=YELLOW)
path.set_points_as_corners([ORIGIN, ORIGIN])
def update_path(m):
t = time.get_value() * TAU
z = sum(circle(r, s, p)(t) for r, s, p in freqs)
m.add_points_as_corners([np.array([z.real, z.imag, 0])])
path.add_updater(update_path)
def epicycles():
t = time.get_value() * TAU
z = 0
group = VGroup()
for r, s, p in freqs:
prev = z
z += circle(r, s, p)(t)
center = np.array([prev.real, prev.imag, 0])
tip = np.array([z.real, z.imag, 0])
group.add(Circle(radius=r).move_to(center).set_stroke(BLUE, 1))
group.add(Line(center, tip, color=WHITE))
return group
epi = always_redraw(epicycles)
self.add(epi, path)
self.play(time.animate.set_value(1), run_time=8, rate_func=linear)
self.wait()
from manim import *
import numpy as np
class FourierInfinity(Scene):
def compute_fourier_coeffs(self, points, n_terms=61):
"""
Compute discrete Fourier coefficients for a sampled closed curve.
Returns list of (frequency, coefficient), sorted by coefficient size.
"""
N = len(points)
ts = np.arange(N) / N
freqs = list(range(-n_terms // 2, n_terms // 2 + 1))
coeffs = []
for k in freqs:
c = np.sum(points * np.exp(-2j * np.pi * k * ts)) / N
coeffs.append((k, c))
coeffs.sort(key=lambda item: abs(item[1]), reverse=True)
return coeffs
def infinity_curve(self, n_samples=2000):
"""
A smooth infinity / lemniscate-like closed curve in the complex plane.
"""
t = np.linspace(0, 2 * np.pi, n_samples, endpoint=False)
# A clean, smooth figure-eight / infinity symbol
x = np.sin(t)
y = 0.55 * np.sin(2 * t)
return x + 1j * y
def construct(self):
self.camera.background_color = "#050816"
title = Text("Fourier Epicycles", font_size=34, color=GREY_A).to_edge(UP)
samples = self.infinity_curve()
coeffs = self.compute_fourier_coeffs(samples, n_terms=81)
time = ValueTracker(0)
# scale factor for scene size
SCALE = 3.2
N_USED = 35
def endpoint_and_groups(t):
z = 0j
circles = VGroup()
radii = VGroup()
tips = VGroup()
for freq, coeff in coeffs[:N_USED]:
prev = z
z += coeff * np.exp(2j * np.pi * freq * t)
center = np.array([SCALE * prev.real, SCALE * prev.imag, 0])
tip = np.array([SCALE * z.real, SCALE * z.imag, 0])
radius = SCALE * abs(coeff)
if radius > 0.015:
circles.add(
Circle(radius=radius)
.move_to(center)
.set_stroke(BLUE_B, width=1.4, opacity=0.45)
)
radii.add(
Line(center, tip, color=WHITE, stroke_width=2.0, stroke_opacity=0.9)
)
tips.add(Dot(tip, radius=0.015, color=YELLOW))
return np.array([SCALE * z.real, SCALE * z.imag, 0]), circles, radii, tips
trace = VMobject(color=YELLOW)
start_point = endpoint_and_groups(0)[0]
trace.set_points_as_corners([start_point, start_point])
def update_trace(mob):
p = endpoint_and_groups(time.get_value())[0]
old = mob.copy()
old.add_points_as_corners([p])
mob.become(old)
trace.add_updater(update_trace)
circles_group = always_redraw(lambda: endpoint_and_groups(time.get_value())[1])
radii_group = always_redraw(lambda: endpoint_and_groups(time.get_value())[2])
tracing_dot = always_redraw(
lambda: Dot(endpoint_and_groups(time.get_value())[0], radius=0.04, color=YELLOW)
)
faint_target = ParametricFunction(
lambda tt: np.array([
SCALE * np.sin(tt),
SCALE * 0.55 * np.sin(2 * tt),
0
]),
t_range=[0, TAU],
color=GREY_B,
stroke_opacity=0.15,
stroke_width=2,
)
self.play(FadeIn(title, shift=DOWN * 0.2))
self.add(faint_target)
self.add(circles_group, radii_group, trace, tracing_dot)
self.play(time.animate.set_value(1), run_time=10, rate_func=linear)
trace.remove_updater(update_trace)
self.wait(0.5)
Rotations and reflections become much easier to remember when the polygon itself performs the symmetry moves on screen.
from manim import *
class PolygonSymmetry(Scene):
def construct(self):
self.camera.background_color = "#050816"
n = 6
polygon = RegularPolygon(n=n, radius=2, color=BLUE)
dots = VGroup(*[Dot(v) for v in polygon.get_vertices()])
self.play(Create(polygon), FadeIn(dots))
# rotation
self.play(
Rotate(
VGroup(polygon,dots),
angle=TAU/n
),
run_time=2
)
# reflection
self.play(
VGroup(polygon,dots).animate.stretch(-1,0),
run_time=2
)
# another rotation
self.play(
Rotate(
VGroup(polygon,dots),
angle=TAU/n
),
run_time=2
)
self.wait()
Paste code, render fast, preview immediately, then refine.
Click Run render to generate the current scene and preview it here.
.py file and run it outside the browser.
# Your Manim code will appear here with syntax colours.