# An initial rendering of the Mandelbrot set using canvas

Building on information presented in A brief overview of the Mandelbrot set, we describe here a basic method for rendering the Mandelbrot set using canvas.

Before presenting an algorithm for drawing the Mandelbrot set, we first create a JavaScript "class" for manipulating complex numbers. As indicated in A brief overview of the Mandelbrot set, the only operations needed are the ability to square, add, and take the absolute value of complex numbers.

<!DOCTYPE html> <html> <head> <meta content="text/html; charset=utf-8" http-equiv="Content-Type"> <title>Useful Complex Operations</title> <style> body { text-align: center; } </style> </head> <body> <h1>Useful Complex Operations</h1> <script> function Complex(x, y) { // Constructs the complex number x + yi. this.x = x || 0; // Default to 0 if this parameter is undefined. this.y = y || 0; } // Complex Complex.prototype.toString = function() { // Returns a string representing this complex number in the form "x + yi". return this.y >= 0 ? this.x + " + " + this.y + "i" : this.x + " - " + (-this.y) + "i"; } // toString Complex.prototype.modulus = function() { // Returns a real number equal to the absolute value of this complex number. return Math.sqrt(this.x*this.x + this.y*this.y); } // modulus Complex.prototype.add = function(z) { // Returns a complex number equal to the sum of the given complex number and this complex number. return new Complex(this.x + z.x, this.y + z.y); } // sum Complex.prototype.square = function() { // Returns a complex number equal to the square of this complex number. var x = this.x*this.x - this.y*this.y; var y = 2*this.x*this.y; return new Complex(x, y); } // square var z1 = new Complex(0.2, 0.4); var z2 = new Complex(-0.6, 0.5); var z3 = z1.add(z2.square()); // Square z2 and add z1 to it. alert(z3.toString()); // Print the complex result in x + yi form. alert(z3.modulus()); // Print absolute value of z3. </script> </body> </html>

In the Useful complex operations example, we have:

*z*₁ = 0.2 + 0.4*i**z*₂ = -0.6 + 0.5*i**z*₃ = (*z*₂)² +*z*₁ = (-0.6 + 0.5*i*)² + (0.2 + 0.4*i*) = 0.31 - 0.2*i*- |
*z*₃| = z3.modulus() ≈ 0.3689

With the complex JavaScript "class" in place, we present a simple algorithm for drawing the Mandelbrot set:

- Divide a 4 x 4 square, centered at the complex origin, into a large number of grid points (not shown):
- Treat each grid point as a
*c*value. - For each such
*c*value, check whether |*z*ₙ| escapes within a reasonable number of iterations (under*z*ₙ₊₁ =*z*ₙ +*c*; where*z*₀ = 0). - If not, color
*c*black (black indicates that*c*is a member of the Mandelbrot set). This assumes we're drawing the Mandelbrot set on a white background.

Implementing this algorithm in JavaScript produces an image similar to this:

In this image, both red coordinate axes have a length of 4. That is, a circle of radius 2, centered at the origin of the complex plane, would exactly contain these axes. As stated previously, the Mandelbrot set lies entirely within a circle of radius 2, and this fact is leveraged in the code used to create this image, as shown next:

<!DOCTYPE html> <html> <head> <meta content="text/html; charset=utf-8" http-equiv="Content-Type"> <title>Mandelbrot 1</title> <style> body { text-align: center; } canvas { border: 1px black solid; } </style> </head> <body> <h1>Mandelbrot 1</h1> <p>This example demonstrates a basic algorithm for drawing the Mandelbrot set using complex plane coordinates.</p> <canvas width="600" height="600">Canvas not supported - upgrade your browser</canvas> <script> var CPS = 2; // CPS stands for "complex plane square". That is, we are examining a 2*CPS by 2*CPS square region of the complex plane such that this square (or box) is centered at the complex plane's origin. var MAX_ITERATIONS = 300; // Increase to improve detection of complex c values that belong to the Mandelbrot set. var DELTA = 0.008; // Decreasing this value increases the number of "pixels" on the canvas, thereby increasing the size of the rendering but without losing image resolution. function Complex(x, y) { // Constructs the complex number x + yi. If any parameter is undefined, 0 is used instead. this.x = x || 0; this.y = y || 0; } // Complex Complex.prototype.toString = function() { // Returns a string representing this complex number in the form "x + yi". return this.y >= 0 ? this.x + " + " + this.y + "i" : this.x + " - " + (-this.y) + "i"; } // toString Complex.prototype.modulus = function() { // Returns a real number equal to the absolute value of this complex number. return Math.sqrt(this.x*this.x + this.y*this.y); } // modulus Complex.prototype.add = function(z) { // Returns a complex number equal to the sum of the given complex number and this complex number. return new Complex(this.x + z.x, this.y + z.y); } // sum Complex.prototype.square = function() { // Returns a complex number equal to the square of this complex number. var x = this.x*this.x - this.y*this.y; var y = 2*this.x*this.y; return new Complex(x, y); } // square var globals = {}; // Store all would-be-global-variables in one handy global object. globals.canvas = document.getElementsByTagName('canvas')[0]; globals.canvas.ctx = globals.canvas.getContext('2d'); globals.canvas.ctx.fillStyle = "black"; initializeCoordinateSystem(); drawMandelbrotSet(); drawCoordinateAxes(); function initializeCoordinateSystem() { var ctx = globals.canvas.ctx; ctx.translate(globals.canvas.width / 2, globals.canvas.height / 2); // Move the canvas's coordinate system to the center of the canvas. ctx.scale(1/DELTA, -1/DELTA); // Flip the y-axis to produce a standard Cartesian coordinate system and scale the canvas coordinate system to match the region of the complex plane under consideration. } // initializeCoordinateSystem function drawMandelbrotSet() { var ctx = globals.canvas.ctx; for (var Re = -CPS; Re <= CPS; Re = Re + DELTA) { // Represents the Re-axis. Re represents the real part of a complex c value. next_c_value: // "continue next_c_value;" is equivalent to an old school GOTO statement (which can be very handy in deeply nested loops). for (var Im = -CPS; Im <= CPS; Im = Im + DELTA) { // Represents the Im-axis. Im represents the imaginary part of a complex c value. var z = new Complex(0, 0); // Represents Zo (where "o" indicates subscript 0). var c = new Complex(Re, Im); // Represents a complex c value, which either does or does not belong to the Mandelbrot set, as determined in the next FOR loop. for (var iterationCount = 1; iterationCount <= MAX_ITERATIONS; iterationCount++) { z = c.add( z.square() ); // Performs Zn+1 = (Zn)^2 + c if (z.modulus() > 2) { continue next_c_value; // The complex c value is not part of the Mandelbrot set, so immediately check the next one. } // if } // for // Assert: z.modulus() <= 2, therefore the complex c value is probably a member of the Mandelbrot set - increase MAX_ITERATIONS to improve this determination. ctx.fillRect(Re, Im, DELTA, DELTA); // This c value is probably part of the Mandelbrot set, so color this pixel black. A "pixel" for the canvas is a DELTA x DELTA black square. } // for } // for } // drawMandelbrotSet function drawCoordinateAxes() { /* Draws coordinate axes that are exactly as long as the (square) complex plane region under consideration. */ var ctx = globals.canvas.ctx; ctx.lineWidth = DELTA; ctx.strokeStyle = "red"; // Draw the x-axis: ctx.beginPath(); ctx.moveTo(CPS, 0); ctx.lineTo(-CPS, 0); ctx.stroke(); // Draw the y-axis: ctx.beginPath(); ctx.moveTo(0, CPS); ctx.lineTo(0, -CPS); ctx.stroke(); } // drawCoordinateAxes </script> </body> </html>

The key functions to examine are `initializeCoordinateSystem`

and `drawMandelbrotSet`

.

### initializeCoordinateSystem

As the name implies, `initializeCoordinateSystem`

initializes the coordinate system used in the 600 pixel by 600 pixel canvas. By default, the point (0, 0) is always in the upper-left corner of the canvas. To use our familiar Cartesian coordinate system, we must **translate** this origin to the center of the canvas as follows:

ctx.translate(globals.canvas.width / 2, globals.canvas.height / 2);

We now have the origin of the coordinate system centered but the positive y-axis is pointing down (increasing) when it should be pointing up (increasing). To correct this, we can use the **scale** method as follows:

ctx.scale(1, -1);

This has the effect of flipping the *y*-axis (while leaving the *x*-axis unchanged), resulting in a traditional Cartesian coordinate system.

### drawMandelbrotSet

The **scale** method can also be used to increase or decrease the size of a rendered image (the larger the scaling factor, the larger the rendered image becomes). We use this method in the example code as follows:

ctx.scale(1/DELTA, -1/DELTA);

If `DELTA`

is small (say 0.008) and also represents the size of a canvas pixel, a normally tiny image is significantly increased in size in that `ctx.scale(1/DELTA, -1/DELTA)`

becomes `ctx.scale(125, -125)`

. This becomes apparent when we examine `drawMandelbrotSet`

:

drawMandelbrotSet

function drawMandelbrotSet() { var ctx = globals.canvas.ctx; for (var Re = -CPS; Re <= CPS; Re = Re + DELTA) { // Represents the Re-axis. Re represents the real part of a complex c value. next_c_value: // "continue next_c_value;" is equivalent to an old school GOTO statement (which can be very handy in deeply nested loops). for (var Im = -CPS; Im <= CPS; Im = Im + DELTA) { // Represents the Im-axis. Im represents the imaginary part of a complex c value. var z = new Complex(0, 0); // Represents Zo (where "o" indicates subscript 0). var c = new Complex(Re, Im); // Represents a complex c value, which either does or does not belong to the Mandelbrot set, as determined in the next FOR loop. for (var iterationCount = 1; iterationCount <= MAX_ITERATIONS; iterationCount++) { z = c.add( z.square() ); // Performs Zn+1 = (Zn)^2 + c if (z.modulus() > 2) { continue next_c_value; // The complex c value is not part of the Mandelbrot set, so immediately check the next one. } // if } // for // Assert: z.modulus() <= 2, therefore the complex c value is probably a member of the Mandelbrot set - increase MAX_ITERATIONS to improve this determination. ctx.fillRect(Re, Im, DELTA, DELTA); // This c value is probably part of the Mandelbrot set, so color this pixel black. A "pixel" for the canvas is a DELTA x DELTA black square. } // for } // for } // drawMandelbrotSet

In that `CPS`

equals 2, we’re examining each grid point in the following square (grid points not shown):

The number of grid points within this square is determined by `DELTA`

. As `DELTA`

decreases, the number of grid points increases:

for (var Re = -CPS; Re <= CPS; Re = Re + DELTA) { … for (var Im = -CPS; Im <= CPS; Im = Im + DELTA) { … } }

We next set *z*₀ to 0 + 0*i* = 0 as required and create *c* based on which grid point (Re, Im) we’re currently examining:

var z = new Complex(0, 0); var c = new Complex(Re, Im);

We can now determine if *c* is (more than likely) in the Mandelbrot set by calculating |*z*ₙ| (within a reasonable number of iterations) and checking if |*z*ₙ| is greater than 2:

for (var iterationCount = 1; iterationCount <= MAX_ITERATIONS; iterationCount++) { z = c.add( z.square() ); // Performs Zn+1 = (Zn)^2 + c if (z.modulus() > 2) { continue next_c_value; // The complex c value is not part of the Mandelbrot set, so immediately check the next one. } // if } // for

Why |*z*ₙ| > 2? Because, and as described in Basic properties, if the absolute value of *z*ₙ ever becomes larger than 2, the sequence will always escape to infinity (indicating that *c* is not part of the Mandelbrot set).

If, after `MAX_ITERATIONS`

, the absolute value of *z*ₙ is less than or equal to 2 (`z.modulus() <= 2`

), the *c* value associated with *z*ₙ is more than likely a member of the Mandelbrot set. Thus, we color *c*’s grid point black:

ctx.fillRect(cx, cy, DELTA, DELTA);

Lastly, we draw two red coordinate axes by calling `drawCoordinateAxes`

.

One of the most egregious issues with Mandelbrot 1 is that we cannot zoom in to examine the finer details of the Mandelbrot set. In order to facilitate this necessary feature, we must first determine how to map a canvas screen point (in pixels) to the complex plane, which is discussed in Mapping screen coordinates to the complex plane.

## Related topics