Botany and number theory

Phyllotaxis

Place each new seed at a fixed angle from the last one and push it slightly further out. Almost every angle wastes space. One angle does not, and it is the one sunflowers use.

EXP-006

Move the cursor over the canvas — it is part of the simulation.

Drag your finger across the canvas — it is part of the simulation.

What to look for

Spirals appear that nobody placed. Count them and you get Fibonacci numbers, and the count changes depending on whether you follow the clockwise or the counterclockwise family.

The maths

φ = 137.507…°

The golden angle: 360° divided by the golden ratio squared. It is the most irrational number available, meaning it is the hardest to approximate with a fraction.

r = c√n, θ = n·φ

Vogel's model. The square root is what keeps the density constant: area grows as r², so r must grow as √n for each seed to get the same room.

How it is built

420 points, each drawn directly from the formula with no simulation and no state. The slow rotation is just a phase added to θ. Point size and opacity grow with the index so the outer ring reads as the newest growth.

The code, step by step

Cut straight from lab.js. These are the real lines that run above, not a simplified version.

01

The divergence angle

const GA = P.angle * Math.PI / 180;
const n = P.count | 0, sc = Math.min(w, h) * P.spread, cx = w / 2, cy = h / 2;
const spin = M.in ? (M.x / w - 0.5) * 6 : t * 0.12;

The slider defaults to 137.507°, the golden angle. Move it a tenth of a degree in either direction and the spirals collapse into radial spokes: that is how sharp the optimum is.

02

Angle linear, radius as a square root

const a = i * GA + spin, r = sc * Math.sqrt(i);
const x = cx + r * Math.cos(a), y = cy + r * Math.sin(a);

The √i is the whole trick. Area grows as r², so for every seed to occupy the same area the radius has to grow as the square root of the index.

03

Newer points read as newer

const f = i / n;
ctx.fillStyle = `rgba(${acc},${(0.15 + f * 0.7).toFixed(3)})`;
ctx.beginPath(); ctx.arc(x, y, 0.8 + f * 2.1, 0, TAU); ctx.fill();

Size and opacity scale with the index, so the outer ring looks like fresh growth. No simulation: it is drawn straight from the formula every frame.

Why it matters

The plant is not doing number theory. It grows each primordium in the largest gap available, and that local greedy rule converges on the golden angle by itself. It is one of the cleanest cases of mathematics being discovered rather than invented.

The whole thing

Everything above, in one piece. This is the entire experiment as it lives in lab.js.

FULL

lab.js · phyllotaxis

{ id: 'phyllotaxis',
      name: L('Phyllotaxis', 'Filotaxis'),
      note: L('The golden angle. The same packing a sunflower solved first.',
              'El ángulo áureo. El mismo empaquetado que un girasol resolvió antes.'),
      params: [
        { key: 'angle', label: L('Divergence angle', 'Ángulo de divergencia'), min: 130, max: 145, step: 0.01, def: 137.507, unit: '°' },
        { key: 'count', label: L('Seeds', 'Semillas'),   min: 60, max: 900, step: 10, def: 420 },
        { key: 'spread', label: L('Spread', 'Dispersión'), min: 0.01, max: 0.06, step: 0.002, def: 0.028 }
      ],
      make() {
        return {
          step(ctx, w, h, t, acc, P, M) {
            const GA = P.angle * Math.PI / 180;
            const n = P.count | 0, sc = Math.min(w, h) * P.spread, cx = w / 2, cy = h / 2;
            const spin = M.in ? (M.x / w - 0.5) * 6 : t * 0.12;
            for (let i = 0; i < n; i++) {
              const a = i * GA + spin, r = sc * Math.sqrt(i);
              const x = cx + r * Math.cos(a), y = cy + r * Math.sin(a);
              const f = i / n;
              ctx.fillStyle = `rgba(${acc},${(0.15 + f * 0.7).toFixed(3)})`;
              ctx.beginPath(); ctx.arc(x, y, 0.8 + f * 2.1, 0, TAU); ctx.fill();
            }
          }
        };
      }
    },