The Mandelbrot Set

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.

zn+1=zn2+c,i=−1z_{n+1} = z_n^2 + c, \quad{i = \sqrt{-1}}

The main cardioid is centered near the origin, with the period-2 bulb to its left at about −1.

World overview

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.

Sub-structures

Zoomed-in to various parts of the Mandelbrot world.

Seahorse Valley

A close-up of the curled branches between the main cardioid and the period-2 bulb, centred near −0.747 + 0.108i.

Elephant Valley

The region near 0.3 on the real axis, with trunk-like spiral structures.

Antenna

The tip of the antenna at −2.0 — Misiurewicz point.

Feigenbaum Cascade

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.

Double-spiral Julia Medallion

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.

Elephant’s Trunk

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.

Double Spiral Valley

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.

Triple Spiral Valley

Near the upper period-3 bulb, the boundary curls into spirals with three arms. Zoom in to follow the smaller spirals along each branch.

Quad Spiral Valley

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.

Mini Mandelbrot

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.

Scepter Valley

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.

Snowflake

Fine, feathery branches form clusters resembling snowflakes near −0.658448 − 0.466852i. Zoom in to find the pattern repeating along smaller branches.

Mandelbrot set equation

The equation using complex numbers is:

zn+1=zn2+cz_{n+1} = z_n^2 + c

z is a complex number composed of real and imaginary parts:

z=x+iy,i=−1z = x + iy, \quad{i = \sqrt{-1}}

Fractal images are generated by varying the value of c:

c=(x0,y0)c = (x_0, y_0)

If we expand the complex squaring, the iteration reduces to:

xn+1=xn2−yn2+x0x_{n+1} = x_n^2 - y_n^2 + x_0
yn+1=2xnyn+y0y_{n+1} = 2x_ny_n + y_0

A common way of exploring fractals is by putting different starting (x0, y0) numbers into the equation and making note of whether the series:

  • diverges to infinity or stays within a limit
  • if the series diverges, we make note of:
    • the number of iterations until reaching the escape radius
    • the distance from zero (0, 0) during the escape

Fractal coloring

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.

About

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

  • shift + scroll mouse wheel — zoom in/out
  • pinch — zoom in/out
  • click and drag — pan the view
  • cmd-z — undo the last camera action

TODO

  • Plot more areas of the Mandelbrot set
  • Improve coloring code for better dynamic range
  • Enable customizing colors
  • Enable resetting each canvas to its original state
  • Allow editing x and y ranges: text inputs or click/drag slider
  • Maybe show the (x0, y0) center of each canvas
  • Draw boxes when selecting parts of the canvas to zoom in on