Fractals
Explore the Mandelbrot set and Julia sets interactively. Zoom deep into infinite complexity.
Click to zoom in. Shift-click to zoom out. Drag to pan. On mobile: drag to pan, pinch to zoom. Switch to Julia mode to explore the parameter space.
Core algorithm
For every pixel, the renderer runs one experiment and uses the result to pick a colour. The experiment itself does not know about zoom — zoom only changes how a pixel maps to a complex number.
1. Pixel to complex number
The visible region is a window onto the complex plane. Its width in plane units is the view'sscale; its centre is (centreX, centreY). Each pixel gets the complex number c sitting under it. Zooming shrinks scale, so the same canvas covers a finer slice of the plane while the iteration math stays untouched.
2. The iteration
Apply the recurrence repeatedly:
zn+1 ← zn2 + c
The only outputs we care about are:
- does the orbit escape to infinity?
- if so, after how many steps n, and how big was |z| when we stopped?
None of the intermediate z values are drawn. The iteration exists purely to label each pixel "in" or "out" of the set, plus a continuous escape speed for the points that left.
- Mandelbrot mode
- start with z0 = 0, and let c equal the pixel's complex coordinate. The image shows which c keep the orbit bounded.
- Julia mode
- start with z0 = the pixel's complex coordinate, and hold c at a fixed constant. The image shows which starting points z0 keep the orbit bounded for that one c.
3. Why the escape test is |z| past 2
Once |z| climbs past 2, the orbit must fly off to infinity. The argument is three lines. By the triangle inequality,
|zn2 + c| ≥ |zn|2 − |c|
and once |z| has grown past |c| (which is guaranteed in the regime we care about),
|zn|2 − |c| ≥ |zn|2 − |zn| = |zn| · (|zn| − 1)
With |z| past 2, the factor (|z| − 1) exceeds 1, so every step multiplies the magnitude by a fixed ratio greater than one — exponential blow-up, with no way back.
The bound is tight: c = −2 produces the orbit 0, −2, 2, 2, 2, … which sits on |z| = 2 forever and marks the leftmost point of the set. Any larger |c| escapes on its first step.
If |z| has not escaped within the chosen iteration limit, treat the pixel as being in the set and paint it black. That limit is arbitrary — any finite cap will misclassify some slow-escaping boundary points as in-set, producing a fuzzy black coastline that thickens at deep zoom. This renderer scales the limit gently with zoom (more patience the further in you go) but the fundamental tradeoff between render time and boundary fidelity cannot be avoided.
4. Smooth colouring
Two pixels that both escape on step n could finish at |z| barely past 2 or at |z| in the hundreds. Colouring by the integer n alone produces hard concentric rings where n jumps from 14 to 15 to 16 — toggle Smooth colouring off above to see the bands appear.
Once |z| is large, the +c term is negligible and the iteration is approximately zn+1 ← zn2, so |z| squares every step:
|zn+k| ≈ |zn|2k
Two logarithms strip those two exponentials off, leaving a quantity that ticks up by exactly 1 per iteration:
log2(log|zn+k|) ≈ k + log2(log|zn|)
So log2(log|z|) is a continuous version of the iteration counter. Subtracting it from n gives the smooth count
μ = n + 1 − log2(log|z|)
Neighbouring pixels now get μ values that differ smoothly instead of in integer jumps, and the bands dissolve into a continuous gradient. Map μ to a hue and cycle through the colour wheel.