Sunflower head
Placing each sunflower floret 137.5 degrees round from the last produces the head’s crossing spirals.
Made with Claude Opus 5.5
- Technique
- stippling and hatching
- Shape
- Any screen
- Added
- 27 September 2026
Export
Colors
- #1C1B1Abackground
- #DAD8CEforeground
- #CF6A4Caccent
Notes
Each floret sits at a distance from the center proportional to the square root of its number, as in Helmut Vogel’s model. The angle is a full turn divided by φ². At that angle the florets numbered 377, 610, 987, 1,597 and 2,584, all Fibonacci numbers, land close to one straight ray. Each of them is picked out with its six nearest neighbors.
Sources
- Helmut Vogel, A better way to construct the sunflower head, 1979.
- Fermat’s spiral.
Source code
wallpapers/
"""Vogel's sunflower spiral of dots with the Fibonacci florets along one ray."""
import math
import numpy as np
from scipy.spatial import cKDTree
from walldye import ACCENT, ACCENT_2, ACCENT_3, BG, BG_ALT, Canvas, Vec, design, ladder, mix
N, C = 2800, 18.9 # florets; spacing, so the head's radius is C * sqrt(N), about 1000
DIVERGENCE = 2 * math.pi / ((1 + 5**0.5) / 2) ** 2 # 2 pi / phi^2 radians between florets
FIB = (377, 610, 987, 1597, 2584) # florets that land on the ray at angle 0
# angle between that ray and the long side of the screen: 16:9's diagonal
RAY = math.degrees(math.atan2(9, 16))
# body in BG_ALT, the outer 15% dissolving toward the background
FADE = ladder((BG_ALT, mix(BG, BG_ALT, 0.5)), 4)
TONES = (*FADE, ACCENT_3, ACCENT_2, ACCENT)
RING, LEAD_RING, HOT = 4, 5, 6 # indices into TONES
@design(aspects="any")
def draw(s: Canvas) -> None:
# center just off the lower right corner, so the tiny central florets are cropped
c = Vec(s.w + 40, s.h + 30)
# the ray points up and to the left, into the screen
tilt = math.radians(180 + (RAY if s.landscape else 90 - RAY))
i = np.arange(1, N + 1)
r, a = C * np.sqrt(i), i * DIVERGENCE + tilt
pts = c + r[:, None] * np.c_[np.cos(a), np.sin(a)]
size = 1.2 + 6 * np.sqrt(i / N)
half = np.asarray(s.center)
seen = (np.abs(pts - half) < half + size[:, None]).all(axis=1)
tone = FADE.rung((r / r[-1] - 0.85) / 0.15)
# each Fibonacci floret with its six nearest neighbors; the outermost rosette leads the
# chain, larger and with brighter neighbors
tree = cKDTree(pts)
hot = [k - 1 for k in FIB]
for h in hot:
lead = h == hot[-1]
ring = [k for k in tree.query(pts[h], 7)[1][1:] if k not in hot]
tone[ring], tone[h] = LEAD_RING if lead else RING, HOT
rosette = [h, *ring]
size[rosette] = np.maximum(size[rosette], 3) * (1.2 if lead else 1)
with s.buckets(TONES, "fill") as b:
for k in np.flatnonzero(seen):
b[tone[k]].circle(pts[k], size[k])"""Vogel's sunflower spiral of dots with the Fibonacci florets along one ray."""
import math
import numpy as np
from scipy.spatial import cKDTree
from walldye import ACCENT, ACCENT_2, ACCENT_3, BG, BG_ALT, Canvas, Vec, design, ladder, mix
N, C = 2800, 18.9 # florets; spacing, so the head's radius is C * sqrt(N), about 1000
DIVERGENCE = 2 * math.pi / ((1 + 5**0.5) / 2) ** 2 # 2 pi / phi^2 radians between florets
FIB = (377, 610, 987, 1597, 2584) # florets that land on the ray at angle 0
# angle between that ray and the long side of the screen: 16:9's diagonal
RAY = math.degrees(math.atan2(9, 16))
# body in BG_ALT, the outer 15% dissolving toward the background
FADE = ladder((BG_ALT, mix(BG, BG_ALT, 0.5)), 4)
TONES = (*FADE, ACCENT_3, ACCENT_2, ACCENT)
RING, LEAD_RING, HOT = 4, 5, 6 # indices into TONES
@design(aspects="any")
def draw(s: Canvas) -> None:
# center just off the lower right corner, so the tiny central florets are cropped
c = Vec(s.w + 40, s.h + 30)
# the ray points up and to the left, into the screen
tilt = math.radians(180 + (RAY if s.landscape else 90 - RAY))
i = np.arange(1, N + 1)
r, a = C * np.sqrt(i), i * DIVERGENCE + tilt
pts = c + r[:, None] * np.c_[np.cos(a), np.sin(a)]
size = 1.2 + 6 * np.sqrt(i / N)
half = np.asarray(s.center)
seen = (np.abs(pts - half) < half + size[:, None]).all(axis=1)
tone = FADE.rung((r / r[-1] - 0.85) / 0.15)
# each Fibonacci floret with its six nearest neighbors; the outermost rosette leads the
# chain, larger and with brighter neighbors
tree = cKDTree(pts)
hot = [k - 1 for k in FIB]
for h in hot:
lead = h == hot[-1]
ring = [k for k in tree.query(pts[h], 7)[1][1:] if k not in hot]
tone[ring], tone[h] = LEAD_RING if lead else RING, HOT
rosette = [h, *ring]
size[rosette] = np.maximum(size[rosette], 3) * (1.2 if lead else 1)
with s.buckets(TONES, "fill") as b:
for k in np.flatnonzero(seen):
b[tone[k]].circle(pts[k], size[k])
Run it yourself
$ git clone https://github.com/nickolaj-jepsen/walldye && cd walldye$ uv run walldye render phyllotaxis --theme fireproof -o phyllotaxis-fireproof-16x9.svg