Grids, tiling, recursion, and fractal pattern
Learning objectives
- learner can generate parametric tiling patterns from nested loops and wallpaper symmetry groups
- learner can use recursion and recursive grid subdivision to build self-similar and fractal layouts
- learner can implement data-driven treemap layouts decoupled from their rendering
Capstone — one whole task that evidences the objectives
Build a parametric pattern system that renders the same data three ways — a symmetry-tiled wallpaper, a recursive fractal subdivision, and a squarified treemap with data-driven styling — from one reusable layout core.
Prerequisite modules
This module builds the pattern engine behind most live visual sets: a layout core that turns one data stream into radically different textures on demand. In a VJ or algorave rig, the audio analysis or a MIDI controller feeds the same numbers every frame — what changes mid-set is the layout interpreting them. Being able to swap a wallpaper tiling for a fractal subdivision or a treemap without rewriting the renderer is the difference between three sketches and one instrument.
The arc starts supported: reproduce a seeded nested-loop grid, leaning on “Nested loops over a grid of tiles are the foundation of parametric tiling patterns” for the loop-plus-randomSeed skeleton. Then constrain it with symmetry — the fact that exactly 17 wallpaper groups tile the plane turns “make it repeat” into a concrete, guaranteed-to-tessellate recipe. Next the grid goes vertical: “Recursive grid subdivision generates fractal-like layouts” is the JIT how-to for splitting cells into sub-grids, while the recursion and self-similarity concepts supply the base-case and exponential-growth guards that keep depth sliders from crashing the sketch live. Finally the treemap leg forces the architectural payoff: the squarified algorithm computes coordinates, a styling callback maps data to colour, and the decoupled-layout principle is what makes one core drive all three renderers.
Required atoms are exactly what the capstone cannot survive without: the tiling procedure, the symmetry constraint, the recursion mechanics with their guards, and the treemap algorithm-styling-decoupling trio. Supporting atoms enrich the road there — 10-PRINT as the minimal-rule inspiration, branching fractals as a sibling recursion form, and classes, custom functions, and 2D arrays as refreshers for structuring the core cleanly.
Walkthrough
Nested loops make a grid; a rule per cell makes a pattern; recursion makes it fractal. Paste each sketch into the p5 web editor and press Run. Each is a complete, standalone sketch.
1 — a nested-loop grid. Two for loops walk a grid of cells — the foundation of every tiling pattern ([[grid-tiling-parametric-pattern]]).
function setup() { createCanvas(400, 400); noStroke(); }
function draw() {
background(15);
let n = 8, s = width / n;
for (let i = 0; i < n; i++)
for (let j = 0; j < n; j++) {
fill((i + j) % 2 ? color(80, 180, 255) : color(20, 40, 70));
rect(i * s, j * s, s, s);
}
}
2 — a rule per cell (the 10-PRINT move). Put a random choice in each cell — here a diagonal one way or the other. One tiny rule over a grid yields an endless maze ([[wallpaper-group-tiling]]).
function setup() { createCanvas(400, 400); randomSeed(3); }
function draw() {
background(15); stroke(120, 255, 180); strokeWeight(3);
let n = 16, s = width / n;
for (let i = 0; i < n; i++)
for (let j = 0; j < n; j++) {
let x = i * s, y = j * s;
if (random() < 0.5) line(x, y, x + s, y + s);
else line(x + s, y, x, y + s);
}
}
3 — symmetry makes it tile. Draw a motif once, then mirror it across the canvas — guaranteed tessellation, the essence of a wallpaper group. Here a quarter is drawn and reflected into four.
function setup() { createCanvas(400, 400); noStroke(); }
function draw() {
background(12);
drawQuad();
push(); translate(width, 0); scale(-1, 1); drawQuad(); pop();
push(); translate(0, height); scale(1, -1); drawQuad(); pop();
push(); translate(width, height); scale(-1, -1); drawQuad(); pop();
}
function drawQuad() {
fill(255, 140, 60);
for (let i = 0; i < 4; i++) { ellipse(30 + i * 30, 30 + i * 25, 40 - i * 6); }
}
4 — recursion: a function that calls itself. A recursive square subdivides into four smaller squares until a base case stops it. That base case is the guard that keeps a live depth-slider from crashing the sketch ([[processing-recursion]], [[recursive-grid-subdivision]]).
function setup() { createCanvas(400, 400); noFill(); stroke(200, 220, 255); }
function draw() { background(15); subdivide(0, 0, width, 0); }
function subdivide(x, y, s, depth) {
rect(x, y, s, s);
if (depth >= 4 || s < 20) return; // base case — the guard
let h = s / 2;
subdivide(x, y, h, depth + 1);
subdivide(x + h, y, h, depth + 1);
subdivide(x, y + h, h, depth + 1);
subdivide(x + h, y + h, h, depth + 1);
}
5 — fractal subdivision with a rule. Only subdivide some cells (a random test), and self-similarity emerges — dense here, sparse there, the same structure at every scale ([[fractal-self-similarity]]).
function setup() { createCanvas(400, 400); noStroke(); colorMode(HSB, 360, 100, 100); randomSeed(9); }
function draw() { background(0, 0, 5); split(0, 0, width, 0); }
function split(x, y, s, depth) {
if (depth >= 5 || (depth > 1 && random() < 0.4)) {
fill((depth * 55) % 360, 70, 90);
rect(x + 2, y + 2, s - 4, s - 4);
return;
}
let h = s / 2;
split(x, y, h, depth + 1); split(x + h, y, h, depth + 1);
split(x, y + h, h, depth + 1); split(x + h, y + h, h, depth + 1);
}
6 — one core, data-driven styling (the capstone). The payoff: a recursive subdivision core whose styling is a separate callback mapping depth/position to colour — change the callback and the same layout becomes a different piece, without touching the layout code ([[treemap-styling-decoupling]] if present):
function setup() { createCanvas(400, 400); noStroke(); colorMode(HSB, 360, 100, 100); randomSeed(21); }
function draw() { background(0, 0, 8); tile(0, 0, width, 0); }
// layout core — knows nothing about colour
function tile(x, y, s, depth) {
if (depth >= 5 || (depth > 1 && random() < 0.45)) { style(x, y, s, depth); return; }
let h = s / 2;
tile(x, y, h, depth + 1); tile(x + h, y, h, depth + 1);
tile(x, y + h, h, depth + 1); tile(x + h, y + h, h, depth + 1);
}
// styling callback — swap this freely
function style(x, y, s, depth) {
fill((200 + depth * 30 + x * 0.2) % 360, 65, 30 + depth * 14);
rect(x + 1, y + 1, s - 2, s - 2);
}
What good looks like. A pattern with structure at multiple scales — the eye finds order (the grid/symmetry) and detail (the recursion) at once, not a flat uniform field. If it’s crashing or freezing, your recursion is missing a base case or the depth is too high (that’s why the guard matters live); if it’s boring, the subdivision rule is too uniform — vary the split probability by depth or position. Decoupling layout from styling is what turns one sketch into an instrument. (Skill map: live-visualist Domain A4/B — generative structure and composition.)
Now make it yours. Change the split probability in step 6 for denser or sparser results. Rewrite only the style() callback (try circles, or hue by size). Add a symmetry mirror around the whole recursive tiling. Drive the max depth from mouseX for a live “zoom into detail” control.
Runnable examples
Generated from the context/ instrument corpus by concept (redistributable idioms only). Do not edit — regenerate with gen-module-examples.mjs.
tiling-repeat
osc(10).repeat(3, 3).out()
hydra-0008 · CC0-1.0
tile [4,4] (circle 0 0.3) >> add
punctual-0020 · CC0-1.0
repetition-motif
for(let i=0;i<8;i++) circle(width/2, height/2, 200 - i*20)
p5live-0023 · CC0-1.0
Atoms in this module
Required — these gate the capstone
Supporting — enrichment, not gating
Part of curricula
- Live Visualist — zero to performing live-coded & generative visuals — Generative canvas — colour, motion, and Hydra live-coding recommended