In this entry I want to share with you, a little of my knowledge to show you how to plot a Mandelbrot Set without any library just JS, so go ahead!.
Note: If you are not familiar with the mathematical terms used here, I recommend you to read about complex numbers and fractals.
First of all, What a heck is a Mandelbrot Set?, according to mathworld:
A Mandelbrot Set, is used to refer both to a general class of fractal sets and to a particular instance of such a set. In general, a Mandelbrot set marks the set of points in the complex plane such that the corresponding Julia set is connected and not computable.
In a nutshell, a mandelbrot set is one of the most beautiful and famous fractal, that is defined by the set of complex numbers.
In the next picture you can see what I mean.
Figure 1: Mandelbrot set - black and white
In this entry you will learn how to plot in JS, the previous image.
But before, let's see a little more theory about how this set works.
Definition
As we mention before, the mandelbrot set is defined in the set of complex numbers c for which the function,
does not diverge when iterated from z = 0.
Below, the previous equation is broken down:
As you can see, the Mandelbrot set is obtained from the quadratic recursive equation where, z and c represents a complex number.
To represent visually this set we need to find, for each point c
of a part of the complex plane, if Zn is bounded. When the modulus of Zn is greater than 2, means that the number does not belong to Mandelbrot set, otherwise it is part of the set. The number of iterations to reach a modulus greater than 2 can be used to determine the color to use when we are plotting.
A complex plane can be represented in a normal 2D plane, where the X axis, will represent the real number, meanwhile the Y axis will represent the imaginary number, for instance, x = 3; y= 5i.
For a better understanding please refer to this video.
Plot of the Mandelbrot Set
Finally, after understand a bit of the Mandelbrot equation, it's time to implement it:
const MAX_ITERATION = 80
function mandelbrot(c) {
let z = { x: 0, y: 0 }, n = 0, p, d;
do {
p = {
x: Math.pow(z.x, 2) - Math.pow(z.y, 2),
y: 2 * z.x * z.y
}
z = {
x: p.x + c.x,
y: p.y + c.y
}
d = Math.sqrt(Math.pow(z.x, 2) + Math.pow(z.y, 2))
n += 1
} while (d <= 2 && n < MAX_ITERATION)
return [n, d <= 2]
}
- The mandelbrot function receives a complex number, it has real and imaginary part, here we are representing those components as x and y.
- The p variable contains the computing of the square of z, which is also a complex number, please refer to this, to understand how basic operations such as addition, subtraction and multiplication works in complex numbers.
- z is recalculated and now it contains the addition of the received complex number (c) and the previous z.
- d is the modulus of the new complex number (z), computed before.
- Add one when each iteration is completed (n += 1).
- The whole process is repeated while the modulus of z belongs to Mandelbrot set and the iteration number is less than 80.
- Finally, the function returns a tuple, with the iteration number that it took to reach a modulus greater than 2, and whether the complex number passed to it, belongs to Mandelbrot set.
And that's it!, we have implemented a mandelbrot equation.
Now is time to plot.
var canvas = document.getElementById('myCanvas')
var ctx = canvas.getContext('2d')
const WIDTH = window.innerWidth
const HEIGHT = window.innerHeight
ctx.canvas.width = WIDTH
ctx.canvas.height = HEIGHT
const REAL_SET = { start: -2, end: 1 }
const IMAGINARY_SET = { start: -1, end: 1 }
const colors = new Array(16).fill(0).map((_, i) => i === 0 ? '#000' : `#${((1 << 24) * Math.random() | 0).toString(16)}`)
function draw() {
for (let i = 0; i < WIDTH; i++) {
for (let j = 0; j < HEIGHT; j++) {
complex = {
x: REAL_SET.start + (i / WIDTH) * (REAL_SET.end - REAL_SET.start),
y: IMAGINARY_SET.start + (j / HEIGHT) * (IMAGINARY_SET.end - IMAGINARY_SET.start)
}
const [m, isMandelbrotSet] = mandelbrot(complex)
ctx.fillStyle = colors[isMandelbrotSet ? 0 : (m % colors.length - 1) + 1]
ctx.fillRect(i, j, 1, 1)
}
}
}
- In the first lines, the canvas element is found and its context, then the window width and height is assigned to the canvas.
- REAL_SET constant represent the real numbers of Mandlebrot set, as you saw the Figure 1 of this entry, the domain in x axis goes from -2 to 1.
- IMAGINARY_SET represent, the imaginary numbers in y axis, the domain in y axis goes from -1 to 1.
- colors, store a list of 16 random hexadecimal colors.
-
draw function:
- The entire width and height of the canvas are iterated.
- The "complex" variable store a complex number; x and y, are calculated getting a relative value, of the width and height of the canvas, plus the values ββthat belong to the mandelbrot set.
- mandelbrot function is called
- A color is assigned to canvas pixel, if the complex number passed to mandelbrot function belongs to set, the black color is chosen, otherwise the color depends of the number of iteration made by mandelbrot function.
- A pixel (rectangle of 1 width and height) is plotted in the position i, j.
That's all, we've finished, it was so easy! isn't it?
If we run the code, it shows the next figures.
Figure 2. Mandelbrot set, example 1.
Figure 3. Mandelbrot set, example 2.
Figure 4. Mandelbrot set, example 3.
Figure 5. Mandelbrot set, example 4.
Figure 6. Mandelbrot set, example 5.
I hope you've enjoyed!.
By the way, It's my first blog, written entirely in English (I'm not a native speaker), maybe you've already realized, so sorry for the misspellings!, please If you have any recommendation, or comment you can leave in the comments section.
Next steps
Our fractal is plotted, so in the next entry I will implement the zoom, when a part of the image is clicked.
Stay safe and thanks for reading!
Top comments (10)
Super cool, looking forward to that zoom!
Here you have dev.to/foqc/mandelbrot-set-in-js-z....
Thanks for the great post Fabian it was really helpful.
I had a quick question :
at line 12 where you are defining an array of random colors, why is 1 shifted 24 times?
more specifically I would like to know how does that bitwise logic really work?
I tried 16 and 32 and got different color schemes but I couldn't figure out why.
I know it probably sounds like a stupid question but I am totally new to bitwise operations :)
A08M31, the only stupid question is the one that you don't ask.
When you play with values ββ8, 16, 24 ..., it generates a set of colors; with 16 bits, obviously the number of colors generated is small (65,535 colors) unlike the 24 bits which can represent 16,777,216 different colors (by the way the human eye can only distinguish 10 million colors), if you change it to 32 bits it probably can not be represented on certain computers. Thanks to your comment I decided to analyze this line of code in depth, and I realized that sometimes it does not generate a valid color, because the code used here is copy/paste ( :D ), from stackoverflow, the solution to this problem is in the following link, codegolf.stackexchange.com/questio...
Do not forget to read the next part of this blog, because you will find more interesting things!
Thank You!
Really enjoyed this one, working on extending it a bit. Currently, I've made the real/imaginary start/end points editable via simple number input fields, and made those points write to the URL as query parameters, allowing sharing of a particular range.
Working on making the canvas zoomable, making an interactive "map" of the Mandel-world.
Also, once it is zoomable/pannable, I plan to make it support alternative computation functions, making it a viewer for other coloring sets (Julia sets, for example).
Thank you for this great rabbit-hole!
Awesome Toby!! great job!!
I recommend you to read the next part of this blog. dev.to/foqc/mandelbrot-set-in-js-z...
Im sorry, I couldn't resist cleaning up your code.
github.com/userse31/fabians_mandel...
But it did help me learn how to do this mandelbrot thing! Also, mandelbrot on a really bad graphing calculator!
@userse31 I really love seeing other people make their own versions and share them.
I saw that you only use black color and a fixed window size in order to run this code on a really bad graphing calculator.
please, Could you explain me why my code kinda triggered you?
what were the things you cleaned up? and why?
feedback is always welcome!
Thanks in advance.
Oh, I was half joking on the "it triggers me" part.
Basically, I simplified it to make it easier to understand.
I will say, the way you set the variables up? Not what I'd do. Makes things more cluttered and actually takes more lines of code.
Idk, maybe its a weird optimization thing?
Also, the calculator in question is a clone of the Casio fx6300g. Bad calculator, thats why I love it! Good programming challenge!
I see your point.
Thanks for sharing!