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

unit-circle-complex

One vector goes round the unit circle on a labelled complex plane, with its two components drawn as what they ARE: segments laid head to tail that ADD UP to it — the horizontal one from the origin is cos θ, the vertical one up to the tip is sin θ, and the vector is their sum, e^(iθ). Written out as the pair [cos θ, sin θ] rather than as an angle, because that is the whole content: a point of the unit circle IS that pair. Watch the horizontal segment shrink to nothing at the top and REVERSE below the axis — cos θ going negative, not disappearing. The dot never stops, because there is no final angle to arrive at — and below it, in a 3-D VIEWPORT PANEL inside the same 2-D frame (camera3 takes a centre, width and height), that same θ is given an axis of its own: the circle becomes a helix, with cos θ and sin θ dropped onto two planes either side of it. One clock drives both, so the dot above IS the helix’s tip below. It scrolls like an oscilloscope trace, showing the last two turns.

// unit-circle-complex — the flat version of examples/euler-helix.manic, on a labelled
// complex plane. One vector goes round the unit circle and its two components are drawn
// as what they are: segments that add up to it.
//
//   e^(iθ) = cos θ + i·sin θ
//
// The vector is written out as [cos θ, sin θ] rather than as an angle, because that is the
// whole content — a point of the unit circle IS the pair (cos θ, sin θ), and the identity
// is the statement that the complex number cos θ + i·sin θ sits there. Everything else is
// read off:
//
//   the horizontal segment, origin to (cos θ, 0)      — the REAL part, cos θ
//   the vertical segment, (cos θ, 0) up to the tip    — the IMAGINARY part, sin θ
//   the vector, origin to the tip                     — their sum, e^(iθ)
//
// Laid head to tail like that they are not "projections" in the passive sense; they are the
// two numbers being added. Watch the horizontal one shrink to nothing at the top of the
// circle and reverse below the axis — that is cos θ going negative, not disappearing.
//
// The dot never stops. There is no final angle to arrive at, which is the point of a
// circle: θ keeps growing, and the same tuple comes round again every 2π. Give that
// forever-growing θ its own axis instead of wrapping it, and the circle becomes the helix
// of examples/euler-helix.manic — same object, one dimension up.
//
//   manic examples/unit-circle-complex.manic
title("A point of the unit circle IS (cos θ, sin θ)");
canvas("9:16");
template("black");
bloom(0.28, 0.6, 20);

text(brand, (540, 34), "maniclang.com");
display(brand); size(brand, 21); color(brand, fg); opacity(brand, 0.82);

// one place for the geometry: a scene-level `let` is nameable in the formula blocks
// too, so the circle and the three segments cannot drift apart when this is retuned
let cx = 540;
let cy = 590;
let rad = 230;

complexplane(cp, (cx, cy), 300, 300, rad);
circle(unit, (cx, cy), rad);
outlined(unit); outline(unit, dim); stroke(unit, 3);

// ---- cos θ: the horizontal segment, origin to (cos θ, 0) ---------------------------
cloud(re, 320, #5fd4ff, 1.0) {
  let th = t*0.55;                                 // θ never stops
  let u = i/320;
  let x = cx + rad*cos(th)*u;
  let y = cy;
  let r = 2.6;
}

// ---- sin θ: the vertical segment, (cos θ, 0) up to the tip -------------------------
cloud(im, 320, #7dffbe, 1.0) {
  let th = t*0.55;
  let u = i/320;
  let x = cx + rad*cos(th);
  let y = cy - rad*sin(th)*u;
  let r = 2.6;
}

// ---- the vector: their sum, origin to [cos θ, sin θ] -------------------------------
cloud(vec, 340, #ffd479, 1.0) {
  let th = t*0.55;
  let u = i/340;
  let x = cx + rad*cos(th)*u;                     // written out as the pair it is
  let y = cy - rad*sin(th)*u;
  let r = 2.2;
}

// ---- the dot at the tip ------------------------------------------------------------
cloud(tip, 140, #ffd479, 1.0) {
  let th = t*0.55;
  let a = i/140*6.283185;
  let rr = 11*(i/140);
  let x = cx + rad*cos(th) + rr*cos(a*11);
  let y = cy - rad*sin(th) + rr*sin(a*11);
  let r = 2.4;
}

// ---- and the two feet, so the right angle is visible --------------------------------
cloud(foot, 90, #5fd4ff, 1.0) {
  let th = t*0.55;
  let a = i/90*6.283185;
  let rr = 7*(i/90);
  let x = cx + rad*cos(th) + rr*cos(a*9);
  let y = cy + rr*sin(a*9);
  let r = 2;
}


// ---- the same θ, given an axis of its own: a 3-D inset below the circle -------------
// `camera3` takes a VIEWPORT — pass a centre, width and height after the projection and
// the 3-D renders into that sub-rect instead of the whole canvas. So the flat picture and
// the spatial one sit in one frame, driven by one θ: the dot above is the helix's tip.
//
// It scrolls. θ never stops growing, so the panel shows the last two turns of it, and the
// waves run leftwards out of frame the way a trace runs off an oscilloscope.
camera3((5.8, -7.6, 3.8), (0, 0, -0.3), 32, perspective, (540, 1320), 1020, 660);

cloud3(helix3, 2600, #ffd479, 1.0) {
  let now = t*0.55;
  let u = i/2600;
  let a = now - 12.566371 + 12.566371*u;           // the last two turns
  let x = 0 - 3.6 + 7.2*u;                         // θ laid along its own axis
  let y = 1.15*cos(a);                             // real
  let z = 1.15*sin(a);                             // imaginary
  let r = 0.035;
}

cloud3(cos3, 1600, #5fd4ff, 1.0) {                 // cos θ, dropped onto the floor
  let now = t*0.55;
  let u = i/1600;
  let a = now - 12.566371 + 12.566371*u;
  let x = 0 - 3.6 + 7.2*u;
  let y = 1.15*cos(a);
  let z = 0 - 2.3;                                 // its own plane, clear of the helix
  let r = 0.03;
}

cloud3(sin3, 1600, #7dffbe, 1.0) {                 // sin θ, dropped onto the back wall
  let now = t*0.55;
  let u = i/1600;
  let a = now - 12.566371 + 12.566371*u;
  let x = 0 - 3.6 + 7.2*u;
  let y = 2.3;                                     // and the other wall
  let z = 1.15*sin(a);
  let r = 0.03;
}

cloud3(tip3, 120, #ffd479, 1.0) {                  // the tip — the dot above, in space
  let now = t*0.55;
  let p = i*2.39996;
  let c = 1 - 2*(i + 0.5)/120;
  let s = sqrt(1 - c*c);
  let x = 3.6 + 0.11*s*cos(p);
  let y = 1.15*cos(now) + 0.11*s*sin(p);
  let z = 1.15*sin(now) + 0.11*c;
  let r = 0.03;
}

caption(head, "A point of the unit circle IS (cos θ, sin θ)", (540, 132), 27);
equation(eq, (540, 1840), `e^{i\theta} = \underbrace{\cos\theta}_{\text{horizontal}} + \;i\underbrace{\sin\theta}_{\text{vertical}}`, 30);
plate(head, 0.7);

wait(24);

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