Vectors, fields & coordinates
Each block is the whole file — copy it into x.manic and run manic x.manic (live) or --record out (video).
vector_field
A magnitude-coloured vector field.
// Vector Field — a grid of arrows sampling a named field, coloured by
// magnitude (cyan → lime → magenta), à la Manim's ArrowVectorField.
//
// manic examples/vector_field.manic
// manic examples/vector_field.manic --record out --fps 60
//
// Named fields: radial, sink, swirl, saddle, wave, shear, uniform, spiral.
title("Vector Field");
canvas(1280, 720);
text(head, (640, 118), ""); display(head); color(head, cyan); size(head, 34); hidden(head);
text(cap, (640, 668), ""); color(cap, dim); size(cap, 22);
// two fields, revealed in turn
arrowfield(swirl, (640, 384), 520, 250, swirl, 15);
untraced(swirl);
arrowfield(rad, (640, 384), 520, 250, radial, 15);
untraced(rad); hidden(rad);
show(head, 0.4);
say(head, "swirl");
say(cap, "a rotational field: (-y, x)");
draw(swirl, 1.2);
wait(1.0);
section("Radial");
say(head, "radial");
say(cap, "an outward source: (x, y) — arrows grow with distance");
par { fade(swirl, 0.5); show(rad, 0.01); }
draw(rad, 1.2);
wait(1.2);
parameterized-field-advection
One parameter smoothly deforms a persistent vector field from steady wind into a vortex.
The generic advect verb then moves real seeded tracers through that same settled field.
// PARAMETERIZED VECTOR FIELD — one value changes the whole field.
// The same settled field then moves a persistent tracer collection. No frame
// callbacks and no manually authored particle paths are involved.
title("From Wind to Vortex");
canvas("16:9");
template("mono");
watermark(mark, (170, 55), "Made With Manic");
text(kicker, (640, 48), "MANIC · PARAMETERIZED FIELD");
text(headline, (640, 94), "One parameter reshapes every arrow");
size(kicker, 18); color(kicker, dim); bold(kicker);
size(headline, 34); bold(headline);
rect(chamber, (640, 365), 780, 410);
outlined(chamber); outline(chamber, dim); stroke(chamber, 3);
// The initial named field supplies a clear first state. `bind` then gives it
// formula components where x,y are field coordinates and p is the parameter.
vectorfield(flow, (640, 365), 375, 190, uniform, 17);
parameter(mix, (640, 610), 0, 0, 1, "vortex mix", 2);
bind(mix, flow, formula, "(1-p)*1.15-p*y", "p*x");
circle(seed, (640, 365), 128);
outlined(seed); outline(seed, dim); dashed(seed, 10, 9);
particles(tracers, seed, 32, 5, 31, "random");
color(tracers, fg); glow(tracers, 0.55);
text(caption, (640, 678), "p = 0: steady wind");
size(caption, 20); color(caption, dim);
untraced(flow); hidden(seed); hidden(tracers);
step("reveal-the-field") {
par {
draw(flow, 0.9);
show(seed, 0.4);
show(tracers, 0.55);
}
}
wait(0.5);
step("wind-becomes-vortex") {
par {
to(mix, value, 1, 2.5, smooth);
say(caption, "p = 1: the same field turns into a vortex", 0.4);
}
}
wait(0.55);
step("release-tracers") {
par {
advect(tracers, flow, 4.4, 0.78);
say(caption, "The dots follow the settled field — their paths were never drawn.", 0.4);
}
}
wait(1.2);
gradient-descent-field
Twelve imperfect guesses begin without the answer. A formula-authored negative-gradient field gives each one local corrections until their different journeys discover one shared minimum—then a creator CTA turns optimization into a complete learning story.
// GRADIENT DESCENT AS A LEARNING STORY
// Twelve guesses begin without the answer. Every arrow supplies one local
// correction, and the same authored field carries all of them to one minimum.
title("How a Wrong Guess Learns");
canvas("9:16");
template("blank");
watermark(mark,(w*0.16,h*0.045),"Made With Manic");
text(kicker,(cx,h*0.075),"GRADIENT DESCENT · LEARNING AS MOTION");
text(headline,(cx,h*0.125),"How does a wrong guess find the minimum?");
text(chapter,(cx,h*0.265),"1 · START ANYWHERE");
text(caption,(cx,h*0.78),"Twelve guesses begin with no map of the whole landscape.");
text(result,(cx,h*0.715),"12 STARTING POINTS → 1 SHARED MINIMUM");
text(cta,(cx,h*0.91),"MAKE LEARNING VISIBLE → 8gwifi.org/manic");
size(kicker,18); color(kicker,dim); bold(kicker); hidden(kicker);
size(headline,32); bold(headline); wrap(headline,w*0.84); hidden(headline);
size(chapter,19); color(chapter,cyan); bold(chapter); hidden(chapter);
size(caption,21); color(caption,dim); wrap(caption,w*0.84); hidden(caption);
size(result,19); color(result,lime); bold(result); hidden(result);
size(cta,21); color(cta,cyan); bold(cta); hidden(cta);
equation(lossRule,(cx,h*0.205),`L(x,y)=\frac{1}{2}x^2+y^2`);
size(lossRule,34); color(lossRule,fg); hidden(lossRule);
let fieldY = h*0.49;
rect(domain,(cx,fieldY),w*0.82,h*0.40);
outlined(domain); outline(domain,dim); stroke(domain,3);
hidden(domain);
// -grad L = (-x,-2y). The gentler vertical coefficient keeps the motion
// readable while preserving the elliptical convergence of the loss bowl.
vectorfield(descent,(cx,fieldY),w*0.37,h*0.175,"-x","-1.6*y",17);
untraced(descent);
circle(starts,(cx,fieldY),w*0.255);
outlined(starts); outline(starts,dim); dashed(starts,12,10);
hidden(starts);
particles(guesses,starts,12,8,73,"ring");
color(guesses,magenta); glow(guesses,0.78);
hidden(guesses);
circle(minimum,(cx,fieldY),14);
filled(minimum); color(minimum,lime); glow(minimum,1.0);
hidden(minimum);
circle(targetRing,(cx,fieldY),44);
outlined(targetRing); outline(targetRing,lime); stroke(targetRing,3);
hidden(targetRing);
text(minLabel,(cx,fieldY+74),"minimum");
size(minLabel,19); color(minLabel,lime); bold(minLabel); hidden(minLabel);
step("begin with imperfect guesses") {
par {
show(kicker,0.30);
show(headline,0.45);
show(lossRule,0.45);
show(chapter,0.35);
show(caption,0.40);
show(domain,0.45);
show(starts,0.45);
show(guesses,0.60);
}
}
wait(0.65);
step("reveal one local clue everywhere") {
par {
draw(descent,1.10);
fade(starts,0.45);
say(chapter,"2 · READ THE LOCAL SLOPE",0.35);
say(caption,"Every arrow gives one instruction: move opposite the local gradient.",0.42);
}
}
wait(0.55);
step("improve one small step at a time") {
par {
advect(guesses,descent,6.80,0.32);
seq {
say(chapter,"3 · FOLLOW THE CORRECTIONS",0.35);
say(caption,"Far away, the correction is large. Near the bottom, each adjustment becomes smaller.",0.42);
wait(3.05);
say(caption,"Different starting points take different journeys—but obey the same rule.",0.42);
}
}
}
wait(0.55);
step("discover the shared destination") {
par {
show(minimum,0.35);
show(targetRing,0.40);
show(minLabel,0.35);
show(result,0.45);
say(chapter,"4 · ARRIVE WITHOUT TELEPORTING",0.35);
say(caption,"Gradient descent is not a jump to the answer. It is repeated, measured improvement.",0.42);
seq {
pulse(targetRing,0.70);
pulse(guesses,0.70);
}
}
}
wait(0.75);
step("create with Manic") {
par {
pulse(minimum,0.75);
show(cta,0.45);
say(caption,"Describe the rule once. Manic keeps every guess, arrow, correction, and learning step connected.",0.45);
}
}
wait(1.45);
coordinates
Axes, planes, number lines, polar & complex planes.
// Coordinate Systems — a guided tour of manic's four coordinate frames:
// Axes (ticks + labels), NumberPlane, PolarPlane, and ComplexPlane. Each frame
// fades in, holds, then clears before the next — one centre, four lenses.
//
// manic examples/coordinates.manic
// manic examples/coordinates.manic --record out --fps 60
title("Coordinate Systems");
canvas(1280, 720);
text(head, (640, 120), "four ways to draw a plane");
display(head); color(head, cyan); size(head, 28); hidden(head);
text(cap, (640, 640), ""); color(cap, dim); size(cap, 24);
// --- the four systems, all centred; each starts hidden ---
axes(ax, (640, 384), 540, 210, 45); // + tick marks and integer labels
plot(wave, (640, 384), 45, 45, sin, 7); // y = sin(x) drawn on the axes
color(wave, magenta); untraced(wave); hidden(ax);
plane(pl, (640, 384), 560, 230, 56); hidden(pl);
polarplane(pp, (640, 384), 230, 5, 16); hidden(pp);
complexplane(cp, (640, 384), 560, 230, 56); hidden(cp);
// --- 1. Axes ---
show(head, 0.5);
section("Axes");
say(cap, "a numbered cross — tick marks every unit");
show(ax, 0.7);
say(cap, "plot y = sin(x) on it");
draw(wave, 1.1);
wait(0.9);
par { fade(ax, 0.4); fade(wave, 0.4); }
// --- 2. NumberPlane ---
section("Number Plane");
say(cap, "a full cartesian grid");
show(pl, 0.7);
wait(1.0);
fade(pl, 0.4);
// --- 3. PolarPlane ---
section("Polar Plane");
say(cap, "concentric rings and radial spokes — angle and radius");
show(pp, 0.7);
wait(1.0);
fade(pp, 0.4);
// --- 4. ComplexPlane ---
section("Complex Plane");
say(cap, "the same grid, read as real and imaginary parts");
show(cp, 0.7);
wait(1.4);
pie
A pie chart built from sectors.
// Equal Slices — a circle cut into equal *sectors* (real filled pieces, not
// just lines) with the math-kit `pie(id, center, r, n)` builtin. Each slice is
// addressable as p0 … p5, so we can trace them on, then pull two out.
//
// manic examples/pie.manic
// manic examples/pie.manic --record out --fps 60
title("Equal Slices");
canvas(1280, 720);
// six equal sectors centred at (560, 400), radius 230 → p0 … p5, tag `p`
pie(p, (560, 400), 230, 6);
untraced(p0); untraced(p1); untraced(p2);
untraced(p3); untraced(p4); untraced(p5);
text(head, (560, 120), "six equal slices");
display(head); color(head, cyan); size(head, 38); hidden(head);
text(cap, (560, 690), ""); color(cap, dim); size(cap, 22);
// --- cut the circle equally, one slice at a time ---
show(head, 0.5);
say(cap, "cut the circle into six equal sectors");
stagger(0.12) {
draw(p0, 0.4);
draw(p1, 0.4);
draw(p2, 0.4);
draw(p3, 0.4);
draw(p4, 0.4);
draw(p5, 0.4);
}
wait(0.6);
// --- each sector is a real piece: pull two out and recolour them ---
say(cap, "each sector is a real piece — pull two out");
par {
move(p0, (621, 435), 0.6, overshoot);
move(p3, (499, 365), 0.6, overshoot);
recolor(p0, magenta, 0.5);
recolor(p3, lime, 0.5);
}
wait(1.0);