Plotting the Mandelbrot set
Posted on: February 28, 2021I stumbled upon some videos of the Mandelbrot set on YouTube, and I was instantly fascinated by it. The shape of the set is so intriquing, fractals are super cool, and the mathematics behind it sparked an interest in doing something with the set. I decided that I'm going to make some form of a visualizer of the Mandlebrot set, and for that I had to get a grip of the math behind the set, and how that math would translate into code.
This post is about exploring the mathematics behind the Mandelbrot set, and the basic code behind my Fractal viewer.
What is the Mandelbrot set?
The mandelbrot set is the set of complex numbers for which the function does not diverge, when iterated from , or remains bounded in absolute value. Its definition is credited to Adrien Douady who named it in tribute to the mathematician Benoit Mandelbrot, a pioneer of fractal geometry. [1]
The set itself is a very beautiful looking piece of mathematical excelence. The boundary of the Mandelbrot set is an infinitely complicated, recursive fractal curve that one can easily get lost into. Given more iterations, the fractals get finer and finer, and the closer the set is inspected, the more detail and complexity can be found. One can also find countless "minibrots" all over the fractals, which I find just lovely.
The math used to plot the set
Iterating the function starts at , so we use this as an example for looking at how iterating the function looks like:
We can quickly start seeing a pattern for how the iteration continues until infinity. We take the result from the previous iteration, and add the complex number to the result. This boils down to calculating the value of every iteration.
The Mandelbrot set is a set of complex numbers that are composed of real and imaginary numbers. The imaginary unit in an imaginary number is represented by which satisfies the equation . Complex numbers can be expressed in the form where and are the real components, and is the imaginary component. If we think about how this can be used in our function, we can see that the expression can be represented as
We can simplify the expression as follows:
So at the end, we get the expression , which we can reorder into due to the commutative property. Now if we think of using this expression to plot our graph, we can split it to represent the real part and the imaginary part as:
Finally, we need to check for the diverging numbers. The formal definition of the Mandelbrot set states that the of remains bounded for all . The Mandelbrot set is contained in a disk with a radius of 2 around the origin. We can use this number to check if the value of stays bounded in the set, or if it blows out towards infinity. So to reiterate, if we get a value where we know for certain it is outside of the set.
Let's look at two examples, and :
When we can see that the value explodes pretty fast outside of our boundary of 2, so is not a part of the Mandelbrot set.
When we can see that the value floats in the same range, so is a part of the Mandelbrot set.
Also, if we look at the in these examples, we notice it doesn't change as the iteration goes on. so the initial value of is a constant during the iteration of the function.
An interesting note here, is that if you change the value of into different constants, you get the visualization of the Julia set instead of the Mandelbrot set. For example is this Julia set:
Now we have explored all the requred math to actually start looking at how one could program a visualization of the Mandelbrot set.
The visualization
To plot the Mandelbrot set on a computer screen, we can map the real part and the imaginary part to the screen's coordinates as an , which allows us to visualize the set in a 2D grid of pixels, i.e. your screen.
In the previous section we established the iterative calculations for and , which we can transform into an and representation as:
Another thing that is important to remember: The Mandelbrot set is contained in a disk with a radius of 2 around the origin.
So how does one plot an image on a screen that is possibly thousands of pixels? We need to normalize the values of and to be and . We can use this function where is the input values and is the output values:
If you think about the value range of and here, you can see that it forms a with a width and a height of 2. Most monitors and phones are rectangles, which would make our visualization skewed, if we just plot it to a square range. We will need to also normalize the range of or to represent a rectangle instead of a square.
Let's use the range of for this. We'll calculate a new range for with:
Which on a 1900 x 1080 pixel screen comes out to instead of our original 2. We can check that this is correct because and
So now we know all the necessary pre-processing we need to do for our visualization.
The Code
Harnessing all this knowledge, we can start writing the code. As a refresher, the original representation of the set states that:
The mandelbrot set is the set of complex numbers for which the function does not diverge, when iterated from
We know we have to start iterating from 0 and that we need to iterate the function untill infinity. Iterating to infinity is not very practical, so let's replace infinity with a variable amount of iterations, like 64. The complex number we plotted out to represent our screen's coordinates , and we established the necessary calculations to get the value of and each iteration. Considering we need to calculate each pixels position relative to the Mandelbrot set, we can do this by looping over all the pixels of our screen.
A common practice is to plot the number of iterations it took to diverge, to some color representation. In this example we use this to make a grayscale representation. We should also clearly visualize the Mandelbrot set itself. We can do that by checking if our loop reaches the iteration limit, and coloring those points black.
Here is an example of the visualization using the HTML canvas element's API with 2D rendering context to make a rendering of the mandelbrot set:
const width = 700;
const height = 500;
const cv = document.createElement("canvas");
cv.width = width;
cv.height = height;
const ctx = cv.getContext("2d");
const buffer = new Uint8ClampedArray(width * height * 4);
const idata = ctx.createImageData(width, height);
const iterations = 64;
const scale = 2;
const xScale = scale * (width / height);
for (let x = 0; x < width; x++) {
for (let y = 0; y < height; y++) {
let a = (x * (xScale + xScale)) / width - xScale;
let b = (y * (scale + scale)) / height - scale;
const constantA = a;
const constantB = b;
let tempA;
let n = 0;
for (let i = 0; i < iterations; i++) {
tempA = a * a - b * b + constantA;
b = 2 * (a * b) + constantB;
a = tempA;
if (a * a + b * b > 4.0) break;
n++;
}
n = n == iterations ? 0 : (n * 255) / iterations;
const pos = (y * width + x) * 4;
buffer[pos + 0] = n;
buffer[pos + 1] = n;
buffer[pos + 2] = n;
buffer[pos + 3] = 255;
}
}
idata.data.set(buffer);
ctx.putImageData(idata, 0, 0);
document.body.appendChild(cv);
And here's the example code running in a CodePen:
Now, you could add a lot of things to this example, like a user interface and coloring using something like the HSV color model.
But if you wish to just play around with such an app, here is the Website I developed for it: Fractal viewer.