Five geometric patterns, each drawn live from the rule that generates it. No images, no dependencies, no build step — one self-contained HTML file and about 300 lines of JavaScript.
→ Folio I: The rule is the ornament → Folio II: The ratio is the form
Or clone and open index.html / ii.html. That's the whole thing.
Ordered by what does the generating: the compass, then a rule laid over a tiling, then recursion, then vibrating matter, then growth.
Circles of radius r centred on a triangular lattice of spacing r, so every circle
passes through its neighbours' centres. The six-fold rosette isn't a design decision — it
falls out of the fact that a radius steps around its own circumference exactly six times.
Control: number of rings.
Take a tiling. Mark each edge's midpoint. From each midpoint throw two rays into the tile at a fixed angle θ to that edge, and cut each ray where it meets the ray coming the other way. Adjacent tiles share midpoints, so the segments join into continuous interlacing bands across the whole plane.
One number — the contact angle — controls the entire pattern, and it's the number the historical craftsmen were choosing.
Controls: contact angle θ, and the underlying tiling (4·8·8 or 6·6·6).
θ is capped at 44° on purpose. Rays from adjacent edge midpoints only converge while θ is below half the tile's interior angle. The 4·8·8 tiling contains squares (interior angle 90°), so at θ ≥ 45° every square's rays diverge and those tiles draw nothing.
φ = (1 + √5) / 2. Start with ten Robinson half-rhombs in a wheel, then subdivide repeatedly, cutting at 1/φ:
- thin → 1 thin + 1 thick
- thick → 2 thick + 1 thin
Tile count grows by φ² per step. Only the two outer legs of each half-rhomb are stroked, so the shared diagonal vanishes and full rhombs read as single tiles.
Any patch, however large, recurs infinitely often elsewhere — and yet no translation ever maps the tiling onto itself.
Controls: deflation depth, fill by tile type.
z(x, y) = cos(nπx)·cos(mπy) − cos(mπx)·cos(nπy)
Sand rests where z = 0. These are free-edge (cosine) modes — a bowed plate has free
edges. The sine form is a fixed-edge membrane and gives a much sparser, non-iconic set.
Nodal lines are rendered at uniform width by dividing by the analytic gradient
(|z| / |∇z|) rather than thresholding |z| directly, which would make line width vary
with local slope.
Controls: modes n and m. Setting n = m blanks the plate — the two terms cancel. That's
correct, not a bug.
θᵢ = i · α rᵢ = c · √i α_golden = 137.50776°
Detune α by even a fifth of a degree and the head cracks into visible spokes. The spiral arm counts you can see are always consecutive Fibonacci numbers.
Controls: divergence angle α, floret count, and snap buttons for golden / +0.09° / +0.49°.
Four of the five were wrong on the first pass, and none of the errors were visible from reading the code. Recorded here because they're the kind that ship silently:
-
Flower of Life — lattice spacing was
r√3instead ofr. The circles intersected but didn't pass through each other's centres, so it wasn't a Flower of Life at all, just overlapping circles. -
Star pattern (angle) — default contact angle was 48°, above the 45° limit imposed by the square tiles. Every square silently drew nothing.
-
Star pattern (scale) — tiles were sized so only ~2.5 repeats spanned the canvas. The construction was correct; the pattern just never read as a pattern.
-
Chladni — two separate bugs. It used sine (fixed-edge) modes for what the caption describes as a bowed plate. And
putImageDataignores the canvas DPR transform, so on a retina display only the top-left quarter was painted, magnified 2×. That one is invisible in a headless browser, which runs atdevicePixelRatio = 1; it only appeared when the viewport was forced to scale 2.
Penrose and phyllotaxis were correct first try.
The lesson worth keeping: "the algorithm is right" and "the picture is right" turned out to be different claims four times out of five. Numeric probes confirmed the octagon's eight ray intersections were perfectly symmetric while the rendered plate was still wrong.
Renders in light or dark. Canvas colours are read from CSS custom properties at draw time,
so a MutationObserver on data-theme plus a prefers-color-scheme listener redraw all
five plates on a theme change.
Five more plates, numeric where the first five were geometric:
- Multiplication mandala — chord k → m·k (mod N); the m = 2 envelope is the cardioid.
- Harmonograph — two damped pendulums; musical intervals as decaying Lissajous figures.
- Apollonian gasket — Descartes' 1643 curvature relation, recursed by Vieta jumping
(
k′ = 2(k₁+k₂+k₃) − k₄, no square roots), so the (−1, 2, 2, 3) seed keeps every curvature an exact integer — printed on the circles. - Hyperbolic tiling {p,q} — Poincaré disk via inversion BFS;
cosh R = cot(π/p)·cot(π/q). Escher's Circle Limit skeleton. - Kolam — mirror curves (Gerdes): a 45° billiard around a dot grid. Thread count is gcd(m, n), verified live; the over–under weave alternates because mirror curves are alternating knots. The correct weave rule turned out to be: the NE pass rides over at horizontal-edge midpoints, the NW pass at vertical ones.
The kolam plate closes what the first version of this README listed as "Wanted: plate VI." The invitation stands for further plates — girih strapwork, a three-pendulum harmonograph, hyperbolic tilings with Escher-style fills. See Kolam Simulation using Angles at Lattice Points.
- Islamic star patterns — E. H. Hankin's method, formalised in Craig S. Kaplan, Islamic Star Patterns in Absolute Geometry (ACM TOG, 2004). Kaplan's own code: github.com/isohedral
- Penrose tilings — Roger Penrose (1974); Robinson triangle deflation.
- Chladni figures — Ernst Chladni, Entdeckungen über die Theorie des Klanges (1787).
- Descartes circle theorem — letter to Princess Elisabeth of Bohemia, November 1643.
- Hyperbolic tilings — Poincaré's disk model; H. S. M. Coxeter's figures, via Escher.
- Mirror curves / kolam — Paulus Gerdes' analysis of plaited-mat and kolam designs.
MIT