The Mandelbrot Set

x [-2.50000000, 1.00000000]
y [-0.60000000, 0.60000000]
Renderer: WebGL

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.

z n + 1 = z n 2 + c , i = 1 z_{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.

x [-2.20000000, 0.80000000]
y [-1.50000000, 1.50000000]

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.

x [-2.20000000, 2.20000000]
y [-2.20000000, 2.20000000]

The set lies roughly on the real axis between −2 and 0.5, with the imaginary extent reaching about ±1.25.

x [-2.20000000, 0.80000000]
y [-1.25000000, 1.25000000]

Mandelbrot set equation

The equation using complex numbers is:

z n + 1 = z n 2 + c z_{n+1} = z_n^2 + c

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

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

Fractal images are generated by varying the value of c:

c = ( x 0 , y 0 ) c = (x_0, y_0)

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

x n + 1 = x n 2 y n 2 + x 0 x_{n+1} = x_n^2 - y_n^2 + x_0
y n + 1 = 2 x n y n + y 0 y_{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 = 255;

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++;
  }
  return [numIterations, x*x + y*y];
}

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] =
        iterateUntilEscape(x0, y0);
      const pixelIndex = 4*i*canvasWidth + 4*j;
      if (numIterations !== MAX_ITERATIONS) {
        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.

x [-2.20000000, 0.80000000]
y [-1.50000000, 1.50000000]

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, escapeDistance) {
  return numIterations + 1 -
    Math.log(escapeDistance) / Math.log(ESCAPE_RADIUS);
}

function getColor(mu) {
  return [ 255, mu * 7, 0, mu * 15 ];
}

Here's the same image as above colored with renormalized iteration counts.

x [-2.20000000, 0.80000000]
y [-1.50000000, 1.50000000]

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.

Sub-structures

Zoomed-in to various parts of the Mandelbrot world.

Seahorse Valley — the region between the main cardioid and the period-2 bulb, centred near −0.75 + 0.1i.

x [-0.79000000, -0.71000000]
y [ 0.06000000, 0.14000000]

Elephant Valley — the region near 0.3 on the real axis, with trunk-like spiral structures.

x [ 0.25000000, 0.35000000]
y [-0.05000000, 0.05000000]

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

x [-2.01000000, -1.94690000]
y [-0.02000000, 0.02000000]

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

TODO