Inspired by Nadieh Bremer's The Art in Pi, irrational-art renders digit sequences from π, e, φ, and √2 as colored, directed steps on an infinite plane. I extended the concept to support multiple constants, configurable digit counts, and a robust interaction layer with cursor-centric pan/zoom.








Digit → step. Each digit 0–9 selects a slice on the unit circle (digit * 36°). That slice becomes a small vector (dx, dy) and a color from a fixed palette. Chaining thousands of these vectors yields a path that "walks" the plane.
Rendering. The canvas iterates digits, accumulates positions, and strokes each segment with the digit's color. Inputs (constant, digit count, custom text) are normalized before draw.
HTML Canvas 2D doesn't give you a built-in inverse transform, so translating screen coordinates (mouse events) into world coordinates (after pan/zoom) is the tricky bit. I solved this by wrapping the 2D context and mirroring its transform with an SVGMatrix, then exposing a single helper method:
1// ctx.transformedPoint(x, y) → world-space point under the cursor
2const svg = document.createElementNS("http://www.w3.org/2000/svg", "svg");
3const pt = svg.createSVGPoint();
4let xform = svg.createSVGMatrix();
5
6// ...override save/restore/translate/scale/rotate/transform/setTransform
7// to keep xform in sync with the canvas context...
8
9ctx.transformedPoint = function (x, y) {
10 pt.x = x; pt.y = y;
11 return pt.matrixTransform(xform.inverse());
12};With transformedPoint, pan and zoom become straightforward and feel correct at any scale:
1let lastX = 0, lastY = 0;
2let dragStart = null;
3
4// PAN: world-space delta between drag start and current cursor
5canvas.addEventListener('mousedown', (e) => {
6 lastX = e.offsetX; lastY = e.offsetY;
7 dragStart = ctx.transformedPoint(lastX, lastY);
8});
9canvas.addEventListener('mousemove', (e) => {
10 lastX = e.offsetX; lastY = e.offsetY;
11 if (!dragStart) return;
12 const p = ctx.transformedPoint(lastX, lastY);
13 ctx.translate(p.x - dragStart.x, p.y - dragStart.y);
14 redraw();
15});
16canvas.addEventListener('mouseup', () => { dragStart = null; });
17
18// ZOOM: anchor to the world point under the cursor
19const scaleFactor = 1.01;
20function zoom(clicks) {
21 const p = ctx.transformedPoint(lastX, lastY);
22 ctx.translate(p.x, p.y);
23 const factor = Math.pow(scaleFactor, clicks);
24 ctx.scale(factor, factor);
25 ctx.translate(-p.x, -p.y);
26 redraw();
27}
28canvas.addEventListener('wheel', (e) => {
29 lastX = e.offsetX; lastY = e.offsetY;
30 zoom(-e.deltaY / 40);
31 e.preventDefault();
32}, { passive: false });zMin/zMax) to prevent runaway scales.devicePixelRatio, optional OffscreenCanvas.Concept inspired by Nadieh Bremer's The Art in Pi.