Skip to content

Commit 68f0440

Browse files
authored
Add Mandelbrot
1 parent cb9c29e commit 68f0440

File tree

1 file changed

+192
-0
lines changed

1 file changed

+192
-0
lines changed

Maths/Mandelbrot.js

Lines changed: 192 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,192 @@
1+
/**
2+
* The Mandelbrot set is the set of complex numbers "c" for which the series "z_(n+1) = z_n * z_n +
3+
* c" does not diverge, i.e. remains bounded. Thus, a complex number "c" is a member of the
4+
* Mandelbrot set if, when starting with "z_0 = 0" and applying the iteration repeatedly, the
5+
* absolute value of "z_n" remains bounded for all "n > 0". Complex numbers can be written as "a +
6+
* b*i": "a" is the real component, usually drawn on the x-axis, and "b*i" is the imaginary
7+
* component, usually drawn on the y-axis. Most visualizations of the Mandelbrot set use a
8+
* color-coding to indicate after how many steps in the series the numbers outside the set cross the
9+
* divergence threshold. Images of the Mandelbrot set exhibit an elaborate and infinitely
10+
* complicated boundary that reveals progressively ever-finer recursive detail at increasing
11+
* magnifications, making the boundary of the Mandelbrot set a fractal curve. (description adapted
12+
* from https://en.wikipedia.org/wiki/Mandelbrot_set ) (see also
13+
* https://en.wikipedia.org/wiki/Plotting_algorithms_for_the_Mandelbrot_set )
14+
*/
15+
16+
/*
17+
Doctests
18+
Test black and white
19+
Pixel outside the Mandelbrot set should be white.
20+
Pixel inside the Mandelbrot set should be black.
21+
> getRGBData(800, 600, -0.6, 0, 3.2, 50, false)[0][0]
22+
[255, 255, 255]
23+
> getRGBData(800, 600, -0.6, 0, 3.2, 50, false)[400][300]
24+
[0, 0, 0]
25+
26+
Test color-coding
27+
Pixel distant to the Mandelbrot set should be red.
28+
Pixel inside the Mandelbrot set should be black.
29+
> getRGBData(800, 600, -0.6, 0, 3.2, 50, true)[0][0]
30+
[255, 0, 0]
31+
> getRGBData(800, 600, -0.6, 0, 3.2, 50, true)[400][300]
32+
[0, 0, 0]
33+
*/
34+
35+
/**
36+
* Method to generate the image of the Mandelbrot set. Two types of coordinates are used:
37+
* image-coordinates that refer to the pixels and figure-coordinates that refer to the complex
38+
* numbers inside and outside the Mandelbrot set. The figure-coordinates in the arguments of this
39+
* method determine which section of the Mandelbrot set is viewed. The main area of the Mandelbrot
40+
* set is roughly between "-1.5 < x < 0.5" and "-1 < y < 1" in the figure-coordinates.
41+
*
42+
* @param {number} imageWidth The width of the rendered image.
43+
* @param {number} imageHeight The height of the rendered image.
44+
* @param {number} figureCenterX The x-coordinate of the center of the figure.
45+
* @param {number} figureCenterY The y-coordinate of the center of the figure.
46+
* @param {number} figureWidth The width of the figure.
47+
* @param {number} maxStep Maximum number of steps to check for divergent behavior.
48+
* @param {number} useDistanceColorCoding Render in color or black and white.
49+
* @return {object} The RGB-data of the rendered Mandelbrot set.
50+
*/
51+
function getRGBData (
52+
imageWidth = 800,
53+
imageHeight = 600,
54+
figureCenterX = -0.6,
55+
figureCenterY = 0,
56+
figureWidth = 3.2,
57+
maxStep = 50,
58+
useDistanceColorCoding = true) {
59+
if (imageWidth <= 0) {
60+
throw new Error('imageWidth should be greater than zero')
61+
}
62+
63+
if (imageHeight <= 0) {
64+
throw new Error('imageHeight should be greater than zero')
65+
}
66+
67+
if (maxStep <= 0) {
68+
throw new Error('maxStep should be greater than zero')
69+
}
70+
71+
const rgbData = []
72+
const figureHeight = figureWidth / imageWidth * imageHeight
73+
74+
// loop through the image-coordinates
75+
for (let imageX = 0; imageX < imageWidth; imageX++) {
76+
rgbData[imageX] = []
77+
for (let imageY = 0; imageY < imageHeight; imageY++) {
78+
// determine the figure-coordinates based on the image-coordinates
79+
const figureX = figureCenterX + (imageX / imageWidth - 0.5) * figureWidth
80+
const figureY = figureCenterY + (imageY / imageHeight - 0.5) * figureHeight
81+
82+
const distance = getDistance(figureX, figureY, maxStep)
83+
84+
// color the corresponding pixel based on the selected coloring-function
85+
rgbData[imageX][imageY] =
86+
useDistanceColorCoding
87+
? colorCodedColorMap(distance)
88+
: blackAndWhiteColorMap(distance)
89+
}
90+
}
91+
92+
return rgbData
93+
}
94+
95+
/**
96+
* Black and white color-coding that ignores the relative distance. The Mandelbrot set is black,
97+
* everything else is white.
98+
*
99+
* @param {number} distance Distance until divergence threshold
100+
* @return {object} The RGB-value corresponding to the distance.
101+
*/
102+
function blackAndWhiteColorMap (distance) {
103+
return distance >= 1 ? [0, 0, 0] : [255, 255, 255]
104+
}
105+
106+
/**
107+
* Color-coding taking the relative distance into account. The Mandelbrot set is black.
108+
*
109+
* @param {number} distance Distance until divergence threshold
110+
* @return {object} The RGB-value corresponding to the distance.
111+
*/
112+
function colorCodedColorMap (distance) {
113+
if (distance >= 1) {
114+
return [0, 0, 0]
115+
} else {
116+
// simplified transformation of HSV to RGB
117+
// distance determines hue
118+
const hue = 360 * distance
119+
const saturation = 1
120+
const val = 255
121+
const hi = (Math.floor(hue / 60)) % 6
122+
const f = hue / 60 - Math.floor(hue / 60)
123+
124+
const v = val
125+
const p = 0
126+
const q = Math.floor(val * (1 - f * saturation))
127+
const t = Math.floor(val * (1 - (1 - f) * saturation))
128+
129+
switch (hi) {
130+
case 0:
131+
return [v, t, p]
132+
case 1:
133+
return [q, v, p]
134+
case 2:
135+
return [p, v, t]
136+
case 3:
137+
return [p, q, v]
138+
case 4:
139+
return [t, p, v]
140+
default:
141+
return [v, p, q]
142+
}
143+
}
144+
}
145+
146+
/**
147+
* Return the relative distance (ratio of steps taken to maxStep) after which the complex number
148+
* constituted by this x-y-pair diverges. Members of the Mandelbrot set do not diverge so their
149+
* distance is 1.
150+
*
151+
* @param {number} figureX The x-coordinate within the figure.
152+
* @param {number} figureX The y-coordinate within the figure.
153+
* @param {number} maxStep Maximum number of steps to check for divergent behavior.
154+
* @return {number} The relative distance as the ratio of steps taken to maxStep.
155+
*/
156+
function getDistance (figureX, figureY, maxStep) {
157+
let a = figureX
158+
let b = figureY
159+
let currentStep = 0
160+
for (let step = 0; step < maxStep; step++) {
161+
currentStep = step
162+
const aNew = a * a - b * b + figureX
163+
b = 2 * a * b + figureY
164+
a = aNew
165+
166+
// divergence happens for all complex number with an absolute value
167+
// greater than 4 (= divergence threshold)
168+
if (a * a + b * b > 4) {
169+
break
170+
}
171+
}
172+
return currentStep / (maxStep - 1)
173+
}
174+
175+
// plot the results if the script is executed in a browser with a window-object
176+
if (typeof window !== 'undefined') {
177+
const rgbData = getRGBData()
178+
const width = rgbData.length
179+
const height = rgbData[0].length
180+
const canvas = document.createElement('canvas')
181+
canvas.width = width
182+
canvas.height = height
183+
const ctx = canvas.getContext('2d')
184+
for (let x = 0; x < width; x++) {
185+
for (let y = 0; y < height; y++) {
186+
const rgb = rgbData[x][y]
187+
ctx.fillStyle = 'rgb(' + rgb[0] + ',' + rgb[1] + ',' + rgb[2] + ')'
188+
ctx.fillRect(x, y, 1, 1)
189+
}
190+
}
191+
document.body.append(canvas)
192+
}

0 commit comments

Comments
 (0)