Basic Mandelbrot

Below I have implemented a very basic version of the Mandelbrot set using the new features offered by HTML5. The mandelbrot set was first studied visually in 1978 by Robert W. Brooks and Peter Matelski, but the equation at its hear goes back much further and is rooted in the field of complex dynamics.

It is very simple recursive equation defined as z(n) = z(n-1)^2 + c where z(0) = 0. The variable c of this equate is a complex number meaning it takes the form a + bi, bi being an imaginary number.

Complex numbers as described above can be placed on a plane where the real component is placed along the x axis and the imaginary component is placed along the y. This is called the complex number plane. In this particular recursive function z always begins at 0, but the value chose for c is going to determine where it goes from there. In many instances the value chosen for c will cause the next step in the sequence to always grow. In other cases, however, it may be bounded or grow tend towards 0. It is the latter case that comprised the Mandelbrot set. The Mandelbrot set is all complex values of c which are unbounded. It is not mathematically possible with the equation to prove that value is truly unbounded, but an arbitrarily large number of iterations for the above equation can be carried out. If the value remains bounded by a circle with the radius of 2 for the chosen number of iterations it can be assumed to be part of the set.

Before computers visualizing a set like this was pretty difficult because for each value you wanted to test the equation had to be carried out for a large number of iterations to be sure it remained bounded. For a modern computer the computation is quite trivial. In the visualization below the computer has iterated over each pixel in the HTML5 canvas object. It treats the x, y values of these coordinates as the real and imaginary components of the complex plain. This becomes the value c used in the equation. Because it is already known that members of the Mandelbrot set lie in a small region of the complex plain, the x, y coordinates are first mapped onto a new range. Below I am mapping the x coordinates to the range -1 through 1 and the y values are mapped onto the range 1 to 1. The recursive function above is carried out for 1000 iterations. If the value of z remains bounded then c (the x, y) coordinate is a member of the mandelbrot set. The pixel is set to black.

Complete source code can be found at https://github.com/chriswininger/BasicMandelbrotSet/blob/master/basic-mandelbrot.js

The main loop for this simple JavaScript application can be written as follows

  
for (x = 0; x < canvasWidth; x++) {  
    for (y = 0; y < canvasHeight; y++) {
        // Map our canvas coordinates onto the complex plane
        cReal = Math.map(x, 0, canvasWidth, startX, endX);
        cImaginary = Math.map(y, 0, canvasHeight, startY, endY);

        if (inMandelbrotSet(cReal, cImaginary)) {
            setBlackPixel(x, y, imageData);
        }
    }
}

If you look at my complete code on github you'll notice that it looks a little different than the above code. The code above is easier to understand, but because the JavaScript is single threaded and this function may take a while to run the page will become unresponsive while the code executes. In order to avoid this in my read code the outer part of the loop has been replaced by a function which is called using setTimeout. setTimeout, when passed 0 for the second parameter, will call the requested function as soon as possible but leaving time to process events in the queue. This prevents the thread from become blocked, thus freezing the page and possibly leading to the browser terminating the script. The code you will find at github is as follows

  
var x = 0, batchX = function() {  
    if (x < canvasWidth) {
        // iterate over y coordinates
        for (y = 0; y < canvasHeight; y++) {
            // Map our canvas coordinates onto the complex plane
            cReal = Math.map(x, 0, canvasWidth, startX, endX);
            cImaginary = Math.map(y, 0, canvasHeight, startY, endY);

            if (inMandelbrotSet(cReal, cImaginary)) {
                setBlackPixel(x, y, imageData);
            }
          }

        x++;
        setTimeout(batchX, 0);
    } else {
        if (typeof complete !== 'undefined'){
            // done draw image and perform callback
            ctx.putImageData(imageData,0,0);
            complete({'status': 'success', 'message': ''});
        }
    }
};