Visualising Mandelbrot’s Equation

David Banks
5 min readApr 19, 2023

--

Image by DALL-E

Introduction

The Mandelbrot equation is very easy to understand (assuming you understand complex numbers).

f(Z) = Z² + C

Where Z and C are complex numbers. For the Mandelbrot set we set Z to be 0 and C to be the coordinates of the pixel we want to draw where the SVG represents the complex plane.

We iterate the equation above, for some coordinates the magnitude will increase to infinity, for others they won’t. Those that do not get large are in the set.

This isn’t a description of how to build the pretty fractal associated with the equation, it describes an important precursor to it, also it has some cool patterns.

We’re going to build a system so we can see what these iterations look like.

Setup

svg {
border: 1px solid grey;
}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Visualise Iterations</title>
<link href="../../style.css" rel="stylesheet" />
</head>
<body>
<h1>Mandelbrot Iterations</h1>
<svg id="mandelbrot-iterations"
width="750px"
height="750px"
viewBox="-1.5 -1.5 3 3"></svg>
<script src="visualise-iterations.js" type="module"></script>
</body>
</html>

For the complex arithmetic I’m using the Complex class I created and described in Complex Numbers in JavaScript. You can go there and copy and paste the complete code.

The code for f(x) (a single iteration) looks like this:

/***
* <code>f(z) = z^2 + c</code>
* @param z Complex result of the iteration.
* @param c Complex constant, set to the coordinates on the complex plain of the point we are interested in.
***/
function f(z, c) {
(function assertArgs(z, c) {
if (!(z instanceof Complex))
throw new Error(
`Expected z to be Complex but was ${z?.constructor.name}`
);
if (!(c instanceof Complex))
throw new Error(
`Expected c to be Complex but was ${z?.constructor.name}`
);
})(z, c);

return z.multiply(z).add(c);
}

For Mandelbrot, we call this with z set to 0 and tell it how many times we want to iterate.

/***
* Call <code>f(z,c)</code> for multiple iterations, returning an array of all the results.
* @param c
* @param maxIterations
* @returns {Complex[]}
*/
function mandelbrot(c, maxIterations) {
const results = new Array(maxIterations);

results[0] = f(Complex.zero(), c);

for (let i = 1; i < maxIterations; i++) {
const prev = results[i - 1];
results[i] = f(prev, c);
}

return results;
}

In order to make things things a little clearer I added some axes to the SVG and some rings as each multiple of 0.25.

Some axes and concentric rings to make things easier to see.

Drawing the Iterations

The user is going to click and drag the mouse about, we’ll calculate the iterations based on where the mouse pointer is.

To do this we need to create the event listener, capture the raw user click coordinates and then convert them to coordinates on the SVG.

I’ve discussed how to so this in Sierpiński Triangle and the Chaos Game. Look at the section Recording the User Click.

The extra work we need to do here is to also support the user dragging the mouse pointer. This is pretty easy, create a mouseDown variable and set or unset it on mouse down and mouse up events.

let mouseDown = false;
(function addEventListeners() {
SVG.addEventListener('mousedown', (e) => {
mouseDown = true;
handleMouseClick(e.clientX, e.clientY);
});

SVG.addEventListener('mouseup', () => {
mouseDown = false;
});

SVG.addEventListener('mousemove', (e) => {
if (mouseDown) {
handleMouseClick(e.clientX, e.clientY);
}
});
})();

The handleClick function is then responsible for creating/updating a polyline that traces the iterations on the SVG.

let path = document.createElementNS(NS, 'polyline');
path.setAttribute('stroke', 'black');
path.setAttribute('stroke-width', '0.005');
path.setAttribute('fill', 'none');
SVG.appendChild(path);

function handleMouseClick(x, y) {
const clickPoint = new DOMPoint(x, y, 0);
const svgPoint = convertToSvgSpace(clickPoint, SVG);

const results = mandelbrot(
new Complex(svgPoint.x, svgPoint.y),
maxIterations
);

let points = `${results[0].real},${results[0].imaginary}`;

for (let i = 1; i < results.length; i++) {
points += ` ${results[i].real},${results[i].imaginary}`;
if (results[i].real > 1000 || results[i].imaginary > 1000) break;
}

path.setAttribute('points', points);
}

We iterate over the results from the call to mandelbrot and create a path string for the polyline, replacing any existing string.

The Results

Iterations of the Mandelbrot Formula

The above animation shows regions where the iterations form a nice patterns that stay vaguely in the region of the original coordinated (i.e. where the mouse pointer is) and other regions where they just explode away.

The regions with the pretty patterns are the regions that are in the set.

Conclusion

The code for this was surprisingly simple. The complexities were all things I’d actually addressed in previous articles, e.g. capturing mouse clicks and handling complex numbers.

I didn’t know what shapes to expect, but they are pretty. Some patterns discovered:

I should be able to paint a fractal by coloring pixels under the mouse pointer some colour if the function converges and get a depiction on the Mandelbrot fractal. Expect another article discussing how this goes.

Thank you for reading, please feel free to ask any questions.

--

--