Lorenz Attractor: How to Create Stunning Chaos Art With Code
In 1963, meteorologist Edward Lorenz was running a simple weather simulation on a Royal McBee computer when he noticed something extraordinary. He'd restarted a simulation partway through, typing in numbers from a printout — but the printout showed three decimal places while the computer used six. That tiny rounding difference of 0.000127 caused the simulation to diverge completely within a few steps. Weather prediction, he realized, was fundamentally impossible beyond a short horizon. He'd stumbled onto deterministic chaos.
The equations Lorenz used produce one of the most iconic shapes in mathematics: a butterfly-shaped curve that never repeats, never crosses itself, and never settles into a fixed orbit. It's called the Lorenz attractor — a strange attractor — and it's the visual signature of chaos theory. The trajectory is deterministic (given the same starting point, you get the same path every time) but unpredictable (tiny changes in starting conditions lead to completely different paths). Order and disorder coexisting in the same system.
Strange attractors aren't just scientifically important — they're beautiful. The curves they trace are organic, flowing, endlessly varied. They look like something alive. In this guide, we'll build 8 strange attractor visualizations from scratch using JavaScript and the HTML Canvas API. No libraries. No frameworks. Just chaos and pixels.
1. The classic Lorenz attractor — the butterfly
The Lorenz system is three coupled differential equations:
dx/dt = σ(y - x)
dy/dt = x(ρ - z) - y
dz/dt = xy - βz
Where σ (sigma) = 10, ρ (rho) = 28, and β (beta) = 8/3 are the classic parameters. We approximate the continuous equations using Euler integration: multiply the derivative by a small time step dt, and add it to the current position. Each step moves us a tiny bit along the trajectory.
const canvas = document.createElement('canvas');
canvas.width = 800; canvas.height = 600;
document.body.appendChild(canvas);
const ctx = canvas.getContext('2d');
const sigma = 10, rho = 28, beta = 8 / 3;
const dt = 0.005;
let x = 0.1, y = 0, z = 0;
const points = [];
for (let i = 0; i < 30000; i++) {
const dx = sigma * (y - x);
const dy = x * (rho - z) - y;
const dz = x * y - beta * z;
x += dx * dt;
y += dy * dt;
z += dz * dt;
points.push({ x, y, z });
}
ctx.fillStyle = '#0a0a0a';
ctx.fillRect(0, 0, 800, 600);
const cx = 400, cy = 320, scale = 8;
ctx.lineWidth = 0.5;
for (let i = 1; i < points.length; i++) {
const p = points[i];
const prev = points[i - 1];
const hue = (i / points.length) * 360;
ctx.strokeStyle = `hsla(${hue}, 80%, 60%, 0.6)`;
ctx.beginPath();
ctx.moveTo(cx + prev.x * scale, cy - prev.z * scale + prev.y * 0.5);
ctx.lineTo(cx + p.x * scale, cy - p.z * scale + p.y * 0.5);
ctx.stroke();
}
ctx.fillStyle = '#fff';
ctx.font = '14px monospace';
ctx.fillText('Lorenz Attractor: σ=10, ρ=28, β=8/3', 20, 30);
ctx.fillText(`${points.length.toLocaleString()} iterations`, 20, 50);
The result is the classic butterfly shape — two lobes that the trajectory orbits unpredictably. Sometimes it circles the left lobe five times before jumping right. Sometimes it alternates. The pattern is different every time you change the starting position by even 0.0001, but the overall shape — the attractor — remains the same. That's the deep insight: the macro structure is stable even though the micro behavior is chaotic.
2. Rössler attractor — the simpler spiral
Otto Rössler designed his attractor in 1976 specifically to be the simplest possible chaotic system. Where Lorenz's equations have two nonlinear terms (xz and xy), Rössler's has just one (xz). The result is an attractor that spends most of its time spiraling in a flat plane, with occasional dramatic excursions upward. It looks like a ribbon caught in a strange wind.
const canvas = document.createElement('canvas');
canvas.width = 800; canvas.height = 600;
document.body.appendChild(canvas);
const ctx = canvas.getContext('2d');
const a = 0.2, b = 0.2, c = 5.7;
const dt = 0.01;
let x = 1, y = 0, z = 0;
const points = [];
for (let i = 0; i < 50000; i++) {
const dx = -y - z;
const dy = x + a * y;
const dz = b + z * (x - c);
x += dx * dt;
y += dy * dt;
z += dz * dt;
points.push({ x, y, z });
}
ctx.fillStyle = '#0a0a0a';
ctx.fillRect(0, 0, 800, 600);
const cx = 400, cy = 350, scale = 18;
for (let i = 1; i < points.length; i++) {
const p = points[i];
const prev = points[i - 1];
const depth = (p.z + 2) / 12;
const hue = 200 + depth * 160;
const alpha = 0.3 + depth * 0.4;
ctx.strokeStyle = `hsla(${hue}, 70%, ${50 + depth * 20}%, ${alpha})`;
ctx.lineWidth = 0.4 + depth * 0.6;
ctx.beginPath();
ctx.moveTo(cx + prev.x * scale, cy - prev.y * scale);
ctx.lineTo(cx + p.x * scale, cy - p.y * scale);
ctx.stroke();
}
ctx.fillStyle = '#fff';
ctx.font = '14px monospace';
ctx.fillText('Rössler Attractor: a=0.2, b=0.2, c=5.7', 20, 30);
The Rössler attractor is a favorite in nonlinear dynamics because its simplicity makes the transition to chaos easy to study. As you increase parameter c, the system goes from a simple limit cycle, to period-doubling cascades, to full chaos — the same route that Feigenbaum discovered is universal across chaotic systems.
3. Clifford attractor — 2D chaos art
Clifford Pickover's attractor is a 2D iterated map, not a differential equation. Instead of continuous flow, each point jumps discretely to the next position. The formula is deceptively simple — just four sine/cosine terms — but produces incredibly intricate, organic patterns. Artists love Clifford attractors because small parameter changes produce dramatically different aesthetics.
const canvas = document.createElement('canvas');
canvas.width = 800; canvas.height = 800;
document.body.appendChild(canvas);
const ctx = canvas.getContext('2d');
const a = -1.4, b = 1.6, c = 1.0, d = 0.7;
let x = 0, y = 0;
const imgData = ctx.createImageData(800, 800);
const density = new Float32Array(800 * 800);
let maxDensity = 0;
for (let i = 0; i < 2000000; i++) {
const nx = Math.sin(a * y) + c * Math.cos(a * x);
const ny = Math.sin(b * x) + d * Math.cos(b * y);
x = nx;
y = ny;
const px = Math.floor((x + 3) * 800 / 6);
const py = Math.floor((y + 3) * 800 / 6);
if (px >= 0 && px < 800 && py >= 0 && py < 800) {
const idx = py * 800 + px;
density[idx]++;
if (density[idx] > maxDensity) maxDensity = density[idx];
}
}
for (let i = 0; i < density.length; i++) {
const val = Math.log(density[i] + 1) / Math.log(maxDensity + 1);
const hue = 220 + val * 140;
const sat = 0.7 + val * 0.3;
const light = val * 0.85;
const h = hue / 360;
const s = sat;
const l = light;
const q = l < 0.5 ? l * (1 + s) : l + s - l * s;
const p = 2 * l - q;
const hue2rgb = (p, q, t) => {
if (t < 0) t += 1;
if (t > 1) t -= 1;
if (t < 1/6) return p + (q - p) * 6 * t;
if (t < 1/2) return q;
if (t < 2/3) return p + (q - p) * (2/3 - t) * 6;
return p;
};
const r = Math.round(hue2rgb(p, q, h + 1/3) * 255);
const g = Math.round(hue2rgb(p, q, h) * 255);
const b2 = Math.round(hue2rgb(p, q, h - 1/3) * 255);
imgData.data[i * 4] = r;
imgData.data[i * 4 + 1] = g;
imgData.data[i * 4 + 2] = b2;
imgData.data[i * 4 + 3] = 255;
}
ctx.putImageData(imgData, 0, 0);
ctx.fillStyle = 'rgba(255,255,255,0.8)';
ctx.font = '14px monospace';
ctx.fillText('Clifford Attractor: a=-1.4, b=1.6, c=1.0, d=0.7', 20, 30);
ctx.fillText('2,000,000 iterations, log-density mapping', 20, 50);
The log-density mapping is key to good Clifford attractor art. Without it, you'd see a few bright hotspots and nothing else. Taking the logarithm compresses the dynamic range, revealing the delicate filamentary structure that makes these images look like cosmic nebulae. Try parameters a=-1.7, b=1.3, c=-0.1, d=-1.21 for a completely different aesthetic.
4. De Jong attractor — Peter de Jong's flowing ribbons
Peter de Jong's attractor is similar in structure to Clifford's but uses a slightly different formula that produces flowing, ribbon-like patterns. Where Clifford attractors tend toward intricate filigree, De Jong attractors often look like calligraphy or smoke trails. The four parameters (a, b, c, d) give you an enormous space of visual possibilities.
const canvas = document.createElement('canvas');
canvas.width = 800; canvas.height = 800;
document.body.appendChild(canvas);
const ctx = canvas.getContext('2d');
const a = 1.641, b = 1.902, c = 0.316, d = 1.525;
let x = 0, y = 0;
const density = new Float32Array(800 * 800);
let maxDensity = 0;
for (let i = 0; i < 3000000; i++) {
const nx = Math.sin(a * y) - Math.cos(b * x);
const ny = Math.sin(c * x) - Math.cos(d * y);
x = nx;
y = ny;
const px = Math.floor((x + 2.5) * 800 / 5);
const py = Math.floor((y + 2.5) * 800 / 5);
if (px >= 0 && px < 800 && py >= 0 && py < 800) {
const idx = py * 800 + px;
density[idx]++;
if (density[idx] > maxDensity) maxDensity = density[idx];
}
}
const imgData = ctx.createImageData(800, 800);
for (let i = 0; i < density.length; i++) {
const val = Math.sqrt(density[i] / maxDensity);
const r = Math.floor(val * 180 + (1 - val) * 10);
const g = Math.floor(val * 60 + (1 - val) * 5);
const b2 = Math.floor(val * 220 + (1 - val) * 30);
imgData.data[i * 4] = r;
imgData.data[i * 4 + 1] = g;
imgData.data[i * 4 + 2] = b2;
imgData.data[i * 4 + 3] = 255;
}
ctx.putImageData(imgData, 0, 0);
ctx.fillStyle = 'rgba(255,255,255,0.9)';
ctx.font = '14px monospace';
ctx.fillText('De Jong Attractor: a=1.641, b=1.902, c=0.316, d=1.525', 20, 30);
ctx.fillText('3M iterations, sqrt-density mapping', 20, 50);
Note we're using square root mapping instead of logarithmic — this gives a slightly different feel, with more mid-tone detail and less extreme compression. Both are valid choices. Experiment with Math.pow(val, 0.3) for yet another aesthetic. The "right" mapping depends on the specific attractor and the look you're going for.
5. Aizawa attractor — the three-dimensional knot
The Aizawa attractor is a 3D system that produces a shape resembling a torus that's been squeezed and twisted. Unlike the Lorenz butterfly (which is relatively flat), the Aizawa attractor has genuine three-dimensional depth. It looks like a strange cosmic object — a donut made of smoke, collapsing and reforming endlessly.
const canvas = document.createElement('canvas');
canvas.width = 800; canvas.height = 700;
document.body.appendChild(canvas);
const ctx = canvas.getContext('2d');
const a = 0.95, b = 0.7, c = 0.6, d = 3.5, e = 0.25, f = 0.1;
const dt = 0.005;
let x = 0.1, y = 0, z = 0;
const points = [];
for (let i = 0; i < 80000; i++) {
const dx = (z - b) * x - d * y;
const dy = d * x + (z - b) * y;
const dz = c + a * z - (z * z * z) / 3
- (x * x + y * y) * (1 + e * z) + f * z * x * x * x;
x += dx * dt;
y += dy * dt;
z += dz * dt;
points.push({ x, y, z });
}
ctx.fillStyle = '#0a0a0a';
ctx.fillRect(0, 0, 800, 700);
const cx = 400, cy = 380, scale = 180;
const cosA = Math.cos(0.5), sinA = Math.sin(0.5);
for (let i = 1; i < points.length; i++) {
const p = points[i];
const prev = points[i - 1];
// Simple 3D rotation
const rx = p.x * cosA - p.y * sinA;
const ry = p.x * sinA + p.y * cosA;
const prx = prev.x * cosA - prev.y * sinA;
const pry = prev.x * sinA + prev.y * cosA;
const depth = (ry + 2) / 4;
const hue = 30 + p.z * 80;
const alpha = 0.15 + depth * 0.4;
ctx.strokeStyle = `hsla(${hue}, 70%, ${40 + depth * 30}%, ${alpha})`;
ctx.lineWidth = 0.3 + depth * 0.5;
ctx.beginPath();
ctx.moveTo(cx + prx * scale, cy - prev.z * scale * 0.9);
ctx.lineTo(cx + rx * scale, cy - p.z * scale * 0.9);
ctx.stroke();
}
ctx.fillStyle = '#fff';
ctx.font = '14px monospace';
ctx.fillText('Aizawa Attractor', 20, 30);
ctx.fillText('a=0.95, b=0.7, c=0.6, d=3.5, e=0.25, f=0.1', 20, 50);
The depth-dependent alpha and line width create a pseudo-3D effect without actual 3D rendering. Points "closer" to the viewer (higher ry values after rotation) are brighter and thicker. This is a technique that works beautifully for any 3D attractor visualization — it suggests volume without the complexity of z-buffering or ray tracing.
6. Lorenz parameter explorer — the edge of chaos
The Lorenz attractor is famous for ρ=28, but other parameter values produce completely different behaviors. At ρ=13, you get a stable fixed point (no chaos). At ρ=21, you get a limit cycle. At ρ=24.74, chaos appears — the system undergoes what mathematicians call a homoclinic bifurcation. This explorer shows 9 different ρ values simultaneously, so you can see the transition from order to chaos.
const canvas = document.createElement('canvas');
canvas.width = 900; canvas.height = 900;
document.body.appendChild(canvas);
const ctx = canvas.getContext('2d');
ctx.fillStyle = '#0a0a0a';
ctx.fillRect(0, 0, 900, 900);
const rhoValues = [10, 15, 21, 24, 24.74, 26, 28, 35, 99.96];
const sigma = 10, beta = 8 / 3;
const cellW = 300, cellH = 300;
rhoValues.forEach((rho, idx) => {
const col = idx % 3, row = Math.floor(idx / 3);
const ox = col * cellW, oy = row * cellH;
const dt = 0.003;
let x = 0.1, y = 0, z = 0;
ctx.save();
ctx.beginPath();
ctx.rect(ox, oy, cellW, cellH);
ctx.clip();
const localCx = ox + cellW / 2;
const localCy = oy + cellH * 0.65;
const scale = rho > 50 ? 2 : 4;
for (let i = 0; i < 15000; i++) {
const dx = sigma * (y - x);
const dy = x * (rho - z) - y;
const dz = x * y - beta * z;
x += dx * dt;
y += dy * dt;
z += dz * dt;
const hue = (i / 15000) * 300;
ctx.fillStyle = `hsla(${hue}, 75%, 55%, 0.5)`;
ctx.fillRect(
localCx + x * scale,
localCy - z * scale + y * 0.3,
1, 1
);
}
ctx.restore();
// Label
ctx.fillStyle = rho === 28 ? '#ff6' : '#aaa';
ctx.font = rho === 28 ? 'bold 13px monospace' : '12px monospace';
ctx.fillText(`ρ = ${rho}`, ox + 10, oy + 20);
if (rho < 24) ctx.fillText('(stable)', ox + 10, oy + 36);
else if (rho === 24.74) ctx.fillText('(onset of chaos)', ox + 10, oy + 36);
else if (rho === 28) ctx.fillText('(classic chaos)', ox + 10, oy + 36);
else if (rho === 99.96) ctx.fillText('(Lorenz knot)', ox + 10, oy + 36);
});
ctx.strokeStyle = '#333';
ctx.lineWidth = 1;
for (let i = 1; i < 3; i++) {
ctx.beginPath(); ctx.moveTo(i * 300, 0); ctx.lineTo(i * 300, 900); ctx.stroke();
ctx.beginPath(); ctx.moveTo(0, i * 300); ctx.lineTo(900, i * 300); ctx.stroke();
}
The ρ=99.96 case is particularly interesting — it produces a "Lorenz knot," a chaotic attractor with a more complex topology than the classic butterfly. And ρ=24.74 is the bifurcation point: right at the edge of chaos, where the system can't decide between order and disorder. These boundary regions are often the most visually interesting, just as they are the most mathematically rich.
7. Multi-trajectory phase portrait — sensitivity to initial conditions
The hallmark of chaos is sensitive dependence on initial conditions — the "butterfly effect." Two trajectories that start almost identically will eventually diverge completely. This visualization shows it directly: we launch 12 trajectories from nearly identical starting points and watch them peel apart. They start as one, stay together for a while, then scatter across the attractor unpredictably.
const canvas = document.createElement('canvas');
canvas.width = 800; canvas.height = 600;
document.body.appendChild(canvas);
const ctx = canvas.getContext('2d');
const sigma = 10, rho = 28, beta = 8 / 3;
const dt = 0.004;
const numTrajectories = 12;
const steps = 8000;
const epsilon = 0.0001; // tiny starting difference
const trajectories = [];
for (let t = 0; t < numTrajectories; t++) {
const angle = (t / numTrajectories) * Math.PI * 2;
let x = 1 + Math.cos(angle) * epsilon;
let y = 1 + Math.sin(angle) * epsilon;
let z = 1;
const pts = [];
for (let i = 0; i < steps; i++) {
const dx = sigma * (y - x);
const dy = x * (rho - z) - y;
const dz = x * y - beta * z;
x += dx * dt;
y += dy * dt;
z += dz * dt;
pts.push({ x, y, z });
}
trajectories.push(pts);
}
function draw(frame) {
ctx.fillStyle = 'rgba(10, 10, 15, 0.08)';
ctx.fillRect(0, 0, 800, 600);
const cx = 400, cy = 340, scale = 8;
const end = Math.min(frame, steps);
for (let t = 0; t < numTrajectories; t++) {
const hue = (t / numTrajectories) * 360;
const pts = trajectories[t];
// Draw the last 200 points as a trail
const start = Math.max(0, end - 200);
for (let i = start + 1; i < end; i++) {
const p = pts[i];
const prev = pts[i - 1];
const alpha = (i - start) / 200;
ctx.strokeStyle = `hsla(${hue}, 80%, 60%, ${alpha * 0.7})`;
ctx.lineWidth = 0.5 + alpha;
ctx.beginPath();
ctx.moveTo(cx + prev.x * scale, cy - prev.z * scale + prev.y * 0.4);
ctx.lineTo(cx + p.x * scale, cy - p.z * scale + p.y * 0.4);
ctx.stroke();
}
// Draw a bright dot at the current position
if (end > 0) {
const p = pts[end - 1];
ctx.fillStyle = `hsl(${hue}, 90%, 70%)`;
ctx.beginPath();
ctx.arc(cx + p.x * scale, cy - p.z * scale + p.y * 0.4, 3, 0, Math.PI * 2);
ctx.fill();
}
}
ctx.fillStyle = '#fff';
ctx.font = '13px monospace';
ctx.fillText(`Butterfly Effect: ${numTrajectories} trajectories, ε = ${epsilon}`, 20, 30);
ctx.fillText(`Step ${end} / ${steps}`, 20, 50);
if (frame < steps) requestAnimationFrame(() => draw(frame + 8));
}
draw(0);
The moment of divergence is dramatic. For the first few hundred steps, all 12 trajectories overlap perfectly — they look like a single line. Then, around step 1000-2000 (depending on starting conditions), you see them start to spread. By step 3000, they're scattered across both lobes of the attractor. This is chaos: deterministic, but practically unpredictable. The Lyapunov exponent of the Lorenz system is about 0.9, meaning nearby trajectories separate by a factor of e ≈ 2.72 every ~1.1 time units.
8. Generative chaos art — layered attractor composition
For the final piece, we combine multiple attractor types into a single generative composition. Each layer uses a different attractor with different parameters and colors, blended together using additive color mixing. The result is something that looks like deep space photography — nebulae, stellar nurseries, cosmic filaments — all generated from pure mathematics.
const canvas = document.createElement('canvas');
canvas.width = 800; canvas.height = 800;
document.body.appendChild(canvas);
const ctx = canvas.getContext('2d');
const w = 800, h = 800;
const r = new Float32Array(w * h);
const g = new Float32Array(w * h);
const b = new Float32Array(w * h);
// Layer 1: Clifford attractor in blue
{
const a = -1.7, b2 = 1.3, c = -0.1, d = -1.21;
let x = 0, y = 0;
for (let i = 0; i < 3000000; i++) {
const nx = Math.sin(a * y) + c * Math.cos(a * x);
const ny = Math.sin(b2 * x) + d * Math.cos(b2 * y);
x = nx; y = ny;
const px = Math.floor((x + 2.5) * w / 5);
const py = Math.floor((y + 2.5) * h / 5);
if (px >= 0 && px < w && py >= 0 && py < h) {
const idx = py * w + px;
b[idx] += 1;
g[idx] += 0.3;
}
}
}
// Layer 2: De Jong attractor in warm
{
const a = -2.24, b2 = 0.43, c = -0.65, d = -2.43;
let x = 0, y = 0;
for (let i = 0; i < 2000000; i++) {
const nx = Math.sin(a * y) - Math.cos(b2 * x);
const ny = Math.sin(c * x) - Math.cos(d * y);
x = nx; y = ny;
const px = Math.floor((x + 2.5) * w / 5);
const py = Math.floor((y + 2.5) * h / 5);
if (px >= 0 && px < w && py >= 0 && py < h) {
const idx = py * w + px;
r[idx] += 1;
g[idx] += 0.4;
}
}
}
// Layer 3: another Clifford in green
{
const a = 1.5, b2 = -1.8, c = 1.6, d = 0.9;
let x = 0, y = 0;
for (let i = 0; i < 1500000; i++) {
const nx = Math.sin(a * y) + c * Math.cos(a * x);
const ny = Math.sin(b2 * x) + d * Math.cos(b2 * y);
x = nx; y = ny;
const px = Math.floor((x + 3) * w / 6);
const py = Math.floor((y + 3) * h / 6);
if (px >= 0 && px < w && py >= 0 && py < h) {
const idx = py * w + px;
g[idx] += 1;
r[idx] += 0.2;
}
}
}
// Find max for normalization
let maxR = 0, maxG = 0, maxB = 0;
for (let i = 0; i < w * h; i++) {
if (r[i] > maxR) maxR = r[i];
if (g[i] > maxG) maxG = g[i];
if (b[i] > maxB) maxB = b[i];
}
const imgData = ctx.createImageData(w, h);
for (let i = 0; i < w * h; i++) {
const rv = Math.pow(r[i] / (maxR || 1), 0.4);
const gv = Math.pow(g[i] / (maxG || 1), 0.4);
const bv = Math.pow(b[i] / (maxB || 1), 0.4);
imgData.data[i * 4] = Math.min(255, rv * 255);
imgData.data[i * 4 + 1] = Math.min(255, gv * 255);
imgData.data[i * 4 + 2] = Math.min(255, bv * 255);
imgData.data[i * 4 + 3] = 255;
}
ctx.putImageData(imgData, 0, 0);
ctx.fillStyle = 'rgba(255,255,255,0.85)';
ctx.font = '14px monospace';
ctx.fillText('Generative Chaos Art: 3 layered attractors', 20, 30);
ctx.fillText('6.5M total iterations, gamma-mapped additive blending', 20, 50);
The gamma mapping (raising to the power of 0.4) is crucial for multi-layer compositions. Without it, you'd see either blown-out hotspots or invisible detail. Gamma compression brings out the delicate filaments where the attractors overlap. The additive blending means that where a blue attractor crosses a red one, you get purple — creating color complexity that no single attractor could produce alone.
The mathematics of chaos
All strange attractors share certain mathematical properties:
- Sensitive dependence on initial conditions — nearby trajectories diverge exponentially (positive Lyapunov exponents)
- Topological mixing — any region of the attractor eventually maps onto any other region
- Dense periodic orbits — unstable periodic orbits are densely packed within the attractor
- Fractal dimension — the Lorenz attractor has a fractal dimension of approximately 2.06, meaning it's more than a 2D surface but less than a 3D volume
The fractal dimension is what makes attractors visually interesting. A trajectory confined to a simple curve (dimension 1) or a simple surface (dimension 2) would be boring. But a dimension of 2.06 means the trajectory fills space in a way that's almost — but not quite — a surface. It folds and refolds on itself, creating layers of detail at every scale.
Where to go from here
- Explore mathematical art for more equation-driven visuals — golden spirals, rose curves, and superformula shapes
- Add gravity simulation techniques to create n-body systems that exhibit chaotic behavior — three or more gravitational bodies produce their own strange attractors
- Use particle systems to visualize attractor flow fields — launch thousands of particles and let the attractor equations guide their motion
- Combine attractors with color theory — map attractor velocity, divergence, or local Lyapunov exponent to color for information-rich visualizations
- Apply Perlin noise to attractor parameters for slowly evolving, never-repeating animations
- Build audio-reactive attractors — map frequency bands to attractor parameters so the chaos responds to music
- On Lumitree, several micro-worlds use attractor dynamics — particles flowing through strange attractor fields, creating organic patterns that emerge from pure mathematical chaos