Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

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);