A Mandelbrot viewer

Apr 02, 2024

I realized the other day that I had never drawn a mandelbrot fractal before, and that seemed like something I ought to have done, so today I wrote a little mandelbrot viewer. Click and drag to zoom in on an area.

The core function

Javascript's lack of support for complex numbers means you have to do a tiny bit of legwork before you can write the core mandelbrot loop.

Here's how I defined simple, inefficient complex number addition and multiplication:

const cmul = (c1, c2) => {
  let [a, b] = c1;
  let [c, d] = c2;
  return [a * c - b * d, a * d + b * c];
};

const cadd = (c1, c2) => {
  return [c1[0] + c2[0], c1[1] + c2[1]];
};

The $mandelbrot$ function accepts a complex number $c$, sets $z = c$ and runs the recursion $z = z^2 + c$ in a loop, incrementing a counter $count$ as it goes.

Here's a javascript implementation, where complex numbers are represented as a two-element array:

export const mandelbrot = (c, max_iter) => {
  let z = c;
  let count = 1;
  while (Math.sqrt(z[0] * z[0] + z[1] * z[1]) <= 2 && count < max_iter) {
    z = cadd(cmul(z, z), c);
    count += 1;
  }
  return count;
};

Coloring in the lines

To draw the pretty picture that so many people recognize:

Here's a javascript function to draw a mandelbrot on a canvas. It makes use of a few d3.js functions:

import { mandelbrot } from "./components/mandelbrot.js";

// constants
const MAX_ITER = 100;
const W = 800;
const H = 800;

// zoom in the scales a bit; [-2, 2] for both will give a full view
const xscale = d3.scaleLinear([0, W], [-2, 0.6]);
const yscale = d3.scaleLinear([0, H], [1.25, -1.25]);

// set up a color scale
// https://d3js.org/d3-scale-chromatic/sequential#interpolateInferno
const colorscale = d3
  .scaleSequentialLog(d3.interpolateInferno)
  .domain([1, MAX_ITER]);
const colors = new Map();
for (let i = 1; i < MAX_ITER; i++) {
  colors.set(i, colorscale(i));
}
colors.set(MAX_ITER, `rgb(0, 0, 0)`);

// get a reference to the canvas context, loop through every pixel,
// and draw the color given by mandelbrot on the pixel
const ctx = document.querySelector("canvas#mcanv").getContext("2d");
const drawRow = (ctx, y = 0, rows = 20) => {
  for (let y = 0; y < H; y++) {
    for (let x = 0; x <= W; x++) {
      const l = mandelbrot([xscale(x), yscale(y + i)], MAX_ITER);
      ctx.fillStyle = colors.get(l);
      ctx.fillRect(x, y, 1, 1);
    }
  }
};

Resources

Go give the toy a try and maybe implement your own Mandelbrot - it's really not too difficult and it's quite fun.

↑ up