The canvas is the complex plane. Each point you draw is z=x+iy. Why complex? Because rotation becomes multiplication. Multiply by eiθ and you rotate by θ with no matrix, no bookkeeping. A pure circle at constant speed is just rei(2πft+ϕ) — radius r, frequency f, starting angle ϕ.
Your drawing is a loop z(t) with t from 0 to 1. Fourier's trick: any such loop, no matter how jagged, is a sum of circles spinning at integer rates k=…,−2,−1,0,1,2,…. k=0 doesn't spin — it's the centroid, the average position. k=1 goes once around during your loop. k=−1 goes once the other way. k=2 twice as fast, and so on.
Magnitude controls size. If coefficient Xk has large ∣Xk∣, its circle is big and carries most of the shape. Phase controls orientation — where the circle starts at t=0. Add all circles tip-to-tail and the final tip traces z(t) exactly. Remove small circles and you still get the gist but lose fine jitter. That is low-pass filtering.
Three details make this playground feel right:
- Resampling to 140 points. Your hand produces uneven spacing — fast strokes leave few samples, slow strokes leave many. The code walks the polyline by arc length and emits N=140 uniformly spaced points via resamplePath. That erases drawing speed and keeps only geometry. Constant SAMPLE_COUNT=140 keeps the DFT at O(N2) cheap and the animation crisp.
- Centroid first. Mean cx,cy is subtracted before transform, computed as c=N1∑nzn. The animation origin is that c. Without this, the k=0 term would dominate with a huge radius to the page corner, and everything else would be a tiny detail orbiting far away.
- Sorted by amplitude. Raw frequency order is 0,1,2,…,N/2,−N/2+1,…,−1. Visually more useful: sort by ∣Xk∣/N, biggest radius first. Then slider 1→60 peels away importance rather than arbitrary high frequency. You see structure emerge, not noise.
Animation runs LOOP_MS=9000 ms per cycle — t sweeps 0→1 every nine seconds, slow enough to see each epicycle turn. The pink trail buffer holds TRAIL_MAX=N×3=420 points, about three full loops of history, then overwrites. That persistence lets you compare reconstruction vs ideal without infinite ink.