If we plot the escape times for points in this complex plane series, we end up with one of the most famous visualizations in mathematics.
The main cardioid is centered near the origin, with the period-2 bulb to its left at about −1.
If we consider an escape radius of 2, the world also has a radius of 2, since any points starting outside of the world diverge right away.
The set lies roughly on the real axis between −2 and 0.5, with the imaginary extent reaching about ±1.25.
Zoomed-in to various parts of the Mandelbrot world.
A close-up of the curled branches between the main cardioid and the period-2 bulb, centred near −0.747 + 0.108i.
The region near 0.3 on the real axis, with trunk-like spiral structures.
The tip of the antenna at −2.0 — Misiurewicz point.
Follow the shrinking chain of bulbs as their periods double from 2 to 4, 8, 16, and beyond. Zoom toward the point near −1.401155 on the real axis to explore where the chain accumulates.
Near a miniature Mandelbrot's Seahorse Valley, curling filaments form a pattern resembling a Julia set. Explore the paired spirals and the smaller curls along their edges.
Deeper inside Elephant Valley, long curved branches curl into tightly wound tips. Follow a trunk to find smaller versions of the same pattern along its sides.
On the period-2 bulb side of Seahorse Valley, two intertwined arms wind into a shared centre. Zoom in to find smaller curls repeating along their branching edges.
Near the upper period-3 bulb, the boundary curls into spirals with three arms. Zoom in to follow the smaller spirals along each branch.
Near the upper period-4 bulb, the filaments form spirals with four arms. Follow each arm outward to see it branch into smaller spirals, adding another turn to the patterns of Double and Triple Spiral Valley.
Near −1.75 on the real axis, a miniature Mandelbrot set appears among delicate filaments. This period-3 island repeats the familiar cardioid and bulbs, with its own valleys waiting to be explored.
Also known as Seahorse Valley West, this region lies between the period-2 and period-4 bulbs. Its curled filaments sprout long, branching spikes that resemble ornate scepters.
Fine, feathery branches form clusters resembling snowflakes near −0.658448 − 0.466852i. Zoom in to find the pattern repeating along smaller branches.
The equation using complex numbers is:
z is a complex number composed of real and imaginary parts:
Fractal images are generated by varying the value of c:
If we expand the complex squaring, the iteration reduces to:
A common way of exploring fractals is by putting different starting (x0, y0) numbers into the equation and making note of whether the series:
Escape time: given a starting point (x0, y0), we can give each point a color by calculating the number of iterations it takes for the series to diverge, and the distance from zero when escaping.
const ESCAPE_THRESHOLD = 4;
const MAX_ITERATIONS = 1024;
function iterateUntilEscape(x0, y0) {
let x = 0, y = 0;
let numIterations = 0;
while ((x*x + y*y <= ESCAPE_THRESHOLD) &&
(numIterations < MAX_ITERATIONS)) {
const x_next = x*x - y*y + x0;
const y_next = 2*x*y + y0;
x = x_next;
y = y_next;
numIterations++;
}
// Check the last permitted iterate too; radius 2 itself is not escape.
const escaped = x*x + y*y > ESCAPE_THRESHOLD;
return [numIterations, x*x + y*y, escaped];
}We'll set the RGBA colors of every pixel in the image based on the number of iterations. Here's an example of a fiery yellow/red color palette by scaling just the green and alpha values of each pixel.
function getColor(numIterations) {
return [
255, // red
numIterations * 7, // green
0, // blue
numIterations * 15 // alpha
];
}Here's a general function for drawing fractals onto canvas elements. Changing the iterateUntilEscape function will change the drawn fractal.
function drawFractal(canvasEl, xRange, yRange, getColor) {
const canvasWidth = canvasEl.width;
const canvasHeight = canvasEl.height;
const context = canvasEl.getContext('2d');
const canvasImageData =
context.createImageData(canvasWidth, canvasHeight);
const image = canvasImageData.data;
for (let i = 0; i < canvasHeight; i++) {
for (let j = 0; j < canvasWidth; j++) {
const x0 = xRange[0] +
j*(xRange[1] - xRange[0]) / canvasWidth;
const y0 = yRange[0] +
i*(yRange[1] - yRange[0]) / canvasHeight;
const [numIterations, escapeDistance, escaped] =
iterateUntilEscape(x0, y0);
const pixelIndex = 4*i*canvasWidth + 4*j;
if (escaped) {
const mu = getMu(numIterations, escapeDistance);
const pixels = getColor({ numIterations, mu });
for (let p = 0; p < 4; p++) {
image[pixelIndex + p] = pixels[p];
}
}
}
}
context.putImageData(canvasImageData, 0, 0);
}Here's the main cardioid colored based on number of iterations alone.
By considering escape distances on a logarithmic scale, we can use this extra information to smooth the color transitions.
const ESCAPE_RADIUS = 2;
function getMu(numIterations, distanceSquared) {
return numIterations + 1 -
Math.log(0.5 * Math.log(distanceSquared)) /
Math.log(ESCAPE_RADIUS);
}
function getColor(mu) {
return [ 255, mu * 7, 0, mu * 15 ];
}Here's the same image with continuous escape counts and a smooth, repeating navy, teal, ivory, and amber palette. Extra iterations after escape soften contour seams, while a 1,024-iteration budget reveals finer boundary detail.
Nebulabrots are a way of rendering fractals where pixels are colored based on how frequently they're visited while calculating the iterations until escape across the viewport.
All fractals on the page are interactive and rendered when they're in view. The renderer uses WebGL by default for high performance, falling back to canvas if WebGL is not available.
Fractal image controls when hovering
TODO