A Mandelbrot Set Viewer via Javascript and Canvas

Written by Ben Wendt

In mathematics, a fixed point is an input x for a function f such that

f(x) = x

The Mandelbrot Set is a visualization of which points near 0 in the complex plane are fixed points for the function f(z) = z^{2} - 1, where zinmathbb{C}. Points that are fixed are rendered in black, while all other points are colour-coded based on how quickly repeated application of the function to a point diverges.

Images of the Mandelbrot set should be familiar to most everyone. It’s an infinitely detailed, self-similar set of rainbows surrounding bubbles.

canvas6canvas

canvas2canvas3

canvas4canvas5

So here’s my javascript and canvas based Mandlebrot viewer:

var xScale, xOffext, yScale, yOffset, xVal, yVal;
var canvas, h, w;
updateScalesAndOffsets = function() {
    xScale = parseFloat(document.getElementById('xScale').value);
    xOffset = parseFloat(document.getElementById('xOffset').value);
    yScale = parseFloat(document.getElementById('yScale').value);
    yOffset = parseFloat(document.getElementById('yOffset').value);
};
updateCanvasAndDimensions = function() {
    canvas = document.getElementById('m');
    h = canvas.getAttribute('height');
    w = canvas.getAttribute('width');
};
doMandelbrot = function() {
    var iteration, max_iteration = 1000, l, x, y, x0, y0, xtemp;
    updateCanvasAndDimensions();
    var ctx = canvas.getContext('2d');
    updateScalesAndOffsets();

    for (var i=0; i < w; i++) {
        for (var j=0;j < h; j++) {
            // for each point in the image, generate the color value.
            x0 = xScale * (i / w) + xOffset;
            y0 = yScale * (j / h) + yOffset;

            x = 0;
            y = 0;

            iteration = 0;

            while (x*x + y*y < 4 && iteration < max_iteration) {
                // this is parametrically performing the complex function f(z) = z^2 -1.
                xtemp = x*x - y*y + x0;
                y = 2*x*y + y0;
                x = xtemp;
                iteration++;
            }

            if (x*x + y*y < 4) {
                ctx.fillStyle='rgb(0,0,0)';
            } else {
                l = iteration < 50? iteration : 50;
                // set colors using hsl so that the number of iterations to diverge maps to the hue.
                ctx.fillStyle='hsl('+Math.floor((iteration/max_iteration)*256)+',100%,' + l + '%)';
            }

            ctx.fillRect(i,j,i+1,j+1);
        }
    }
};
mouseMove = function(e) {


    xVal = xScale * (e.clientX / w) + xOffset;
    yVal = yScale * (e.clientY / h) + yOffset;
    var xCoordinateElement = document.getElementById('xCoordinate'), yCoordinateElement = document.getElementById('yCoordinate');
    xCoordinateElement.innerHTML = xVal;
    yCoordinateElement.innerHTML = yVal;
};

zoomIn = function() {
    document.getElementById('xScale').value = parseFloat(document.getElementById('xScale').value) / 2;
    document.getElementById('xOffset').value = xVal - parseFloat(document.getElementById('xScale').value) / 2;
    document.getElementById('yScale').value = parseFloat(document.getElementById('yScale').value) / 2;
    document.getElementById('yOffset').value = yVal - parseFloat(document.getElementById('yScale').value) / 2;
    doMandelbrot();

}
zoomOut = function() {
    document.getElementById('xScale').value = parseFloat(document.getElementById('xScale').value) * 2;
    document.getElementById('yScale').value = parseFloat(document.getElementById('yScale').value) * 2;
    doMandelbrot();

}
moveDir = function(dir) {
    switch (dir) {
        case 'up' : document.getElementById('yOffset').value = yOffset - yScale / 10; break;
        case 'down' : document.getElementById('yOffset').value = yOffset + yScale / 10; break;
        case 'right' : document.getElementById('xOffset').value = xOffset + xScale / 10; break;
        case 'left' : document.getElementById('xOffset').value = xOffset - xScale / 10; break;
    }
    updateScalesAndOffsets();
    doMandelbrot();
}