# Mandelbrot Set

Location: Mathematics / Fractals / Mandelbrot Set

## Summary

This article aims to explain the theoretical basis of the Mandelbrot set and also to provide a JavaScript visualization of the Mandelbrot set fractal (HTML canvas, full source code, code guide, JsFiddle for experimenting, screenshots). The theoretical part is an almost complete copy of the article "Introduction to the Mandelbrot Set. A guide for people with little math experience.", 1996, by David Dewey, which is no longer unavailable online (the original publication URL is http://www.ddewey.net/mandelbrot/ ). ## Status

• The "Source Code" section should be merged into the "Code Guide" section with detailed explanation of every part of the source code.

## Introduction to the Mandelbrot Set. A guide for people with little math experience. By David Dewey

The Mandelbrot set, named after Benoit Mandelbrot, is a fractal. Fractals are objects that display self-similarity at various scales. Magnifying a fractal reveals small-scale details similar to the large-scale characteristics. Although the Mandelbrot set is self-similar at magnified scales, the small scale details are not identical to the whole. In fact, the Mandelbrot set is infinitely complex. Yet the process of generating it is based on an extremely simple equation involving complex numbers.

### Understanding complex numbers

The Mandelbrot set is a mathematical set, a collection of numbers. These numbers are different than the real numbers that you use in everyday life. They are complex numbers. A complex number consists of a real number plus an imaginary number. The real number is an ordinary number, for example, -2. The imaginary number is a real number times a special number called i, for example, 3i. An example of a complex number would be -2 + 3i.

The number i was invented because no real number can be squared (multiplied by itself) and result in a negative number. This means that you can not take the square root of a negative number and get a real number. By definition, when you take the square root of a number, you find a number that can be squared to get that number. The number i is defined to be the square root of -1. This means that i squared is equal to -1. So when you square an imaginary number you can get a negative number. For example, 3i squared is -9.

#### The real number line Real numbers can be represented on a one dimensional line called the real number line. Negative numbers like -2 are plotted to the left of zero and positive numbers like 2 are plotted to the right of zero. Any real number can be graphed on the real number line.

#### The complex number plane Since complex numbers have two parts, a real one and an imaginary one, we need a second dimension to graph them. We simply add a vertical dimension to the real number line for the imaginary part. Since our graph is now two-dimensional, it is a plane, the complex number plane. We can graph any complex number on this plane. The colored dots on this graph represent the complex numbers `[2 + 1i]` (red), `[-1.5 + 0.5i]` (blue), `[2 - 2i]` (lime), `[-0.5 - 0.5i]` (green), `[0 + 1i]` (pink), and `[2 + 0i]` (khaki).

### Graphing the Mandelbrot set

The Mandelbrot set is a set of complex numbers, so we graph it on the complex number plane. However, first we have to find many numbers that are part of the set. To do this we need a test that will determine if a given number is inside the set or outside the set. The test is based on the equation `Z = Z² + C`. `C` represents a constant number, meaning that it does not change during the testing process. `C` is the number we are testing, the point on the complex plane that will be plotted when testing is complete. `Z` starts out as zero, but it changes as we repeatedly iterate this equation. With each iteration we create a new `Z` that is equal to the old `Z` squared plus the constant `C`. So the number `Z` keeps changing throughout the test.

We're not really interested in the actual value of `Z` as it changes, we just look at its magnitude. The magnitude of a number is its distance from zero. For example, the number -9 is a distance of 9 from zero, so it has a magnitude of 9. The magnitude of a complex number is harder to measure. To calculate it, we add the square of the number's distance from the x-axis (the horizontal real axis) to the square of the number's distance from the y-axis (the imaginary vertical axis) and take the square root of the result. In this illustration, `a` is the distance from the y-axis, `b` is the distance from the x-axis, and `d` is the magnitude, the distance from zero. As we iterate our equation, `Z` changes and the magnitude of `Z` also changes. The magnitude of `Z` will do one of two things. It will either stay equal to or below 2 forever, or it will eventually surpass two. Once the magnitude of `Z` surpasses 2, it will increase forever. In the first case, where the magnitude of `Z` stays small, the number we are testing is part of the Mandelbrot set. If the magnitude of `Z` eventually surpasses 2, the number is not part of the Mandelbrot set (look below at the "Formal definition" section for a proff). As we test many complex numbers we can graph the ones that are part of the Mandelbrot set on the complex number plane. If we plot thousands of points, an image of the set will appear: The Mandelbrot set is an incredible object. It's really amazing that the simple iterated equation `Z = Z² + C` can produce such beautiful works of mathematical art.

If you're interested in learning more about the Mandelbrot set, fractals, and chaos theory I highly recommend reading James Gleick's classic book Chaos: Making a New Science.

--David Dewey

## Formal definition

Taken all number sequences `S(c) -> f(z) = z*z + c` starting with `z = 0`, where `z` and `c` are complex numbers, the Mandelbrot set is the set of all values of `c` for which `S(c)` is bounded.

### Examples

In the following examples, the sequence values `z = (real, imaginary)` are printed in the format `(real, imaginary, magnitude)`, where `magnitude = ||z||` (see Operations with vectors).

#### For c = (1, 1), S(c) is unbounded (magnitude diverges towards infinity)

````S(c) = (1, 1, 1.41), (1, 3, 3.16), (-7, 7, 9.9), (1, -97, 97.01), (-9407, -193, 9408.98), (88454401, 3631103, 88528899.04), (7810996147272193, 642374081668607, 7837365965265411), (6.059901635190146e+31, 1.0035162954042004e+31, 6.142430527350064e+31), (3.5715362873038435e+63, 1.2162420078919742e+63, 3.772945278332198e+63), (1.1276626829767021e+127, 8.687704930658948e+126, 1.4235116073289227e+127), (5.168609569562561e+253, 1.9593601302033588e+254, Infinity)` ```

#### For c = (0.1, 0.1), S(c) is bounded (magnitude tends to 0.15)

````S(c) = (0.1, 0.1, 0.14), (0.1, 0.12, 0.16), (0.1, 0.12, 0.16), (0.09, 0.12, 0.16), (0.09, 0.12, 0.15), (0.09, 0.12, 0.15), (0.09, 0.12, 0.15), (0.09, 0.12, 0.15), (0.09, 0.12, 0.15), (0.09, 0.12, 0.15), (0.09, 0.12, 0.15), (0.09, 0.12, 0.15), (0.09, 0.12, 0.15), (0.09, 0.12, 0.15), (0.09, 0.12, 0.15), (0.09, 0.12, 0.15), (0.09, 0.12, 0.15), (0.09, 0.12, 0.15), (0.09, 0.12, 0.15), (0.09, 0.12, 0.15)` ```

The Wikipedia article elaborates further on the formal mathematical definition of the Mandelbrot set. It also states that if the magnitude of `S(c)` ever exceeds `2` the sequence can be safely considered to be unbounded.

Here is the proof of the above statement, proposed by Mike Hurley (mgh3@po.cwru.edu) in April 1993 on USENET (source: Mandelbrot Set - Escape Radius, Robert P. Munafo, 1997 Nov 19.):

```The sequence generated by f(z) = z^2 + c beginning with 0 begins 0,c,c^2 + c, ...Call these terms z0, z1, z2, ... . In fact this sequence will be unbounded if any of its terms has magnitude larger than 2.  The main idea is that if the magnitude |z| is bigger than both 2 and the magnitude |c|, then |f(z)|/|z| > |z| - 1 > 1, so thatby induction the sequence of magnitudes will grow geometrically.  To establish the inequality, note that   |f(z)|/|z| = |z^2 + c|/|z|        >= (|z|^2-|c|)/|z|          [ triangle inequality ]         = |z| - (|c|/|z|)          > |z| - 1                  [ |z| > |c| ]         > 1                        [ |z| > 2 ] If  |c| <= 2 and  |zn| > 2 then certainly z = zn satisfies these hypotheses, so the sequence is unbounded.   If |c| > 2 then |c^2+c| >= |c|^2 - |c| = |c|(|c|-1) > |c| > 2so that z=z2 satisfies the hypotheses of the argument above. ```

## Mandelbrot set coloring

The black-and-white image above plots the complex numbers that are within the Mandelbrot set. But what about the complex numbers that do not belong to the set? Do they have any structure? One quality of a given complex number `C` that provides structure is how early the sequence diverges (i.e. exceeds 2) for this number.

Let's select a complex number `C` that does not belong to the Mandelbrot set. By definition, at some point the iteration of `Z = Z² + C` will diverge; let's say, the first element of the sequence that exceeds 2 is its n-th element (we will call `n` a mandelbrot value). In order to visualize the magnitude of `n` we plot `C`on the chart using a shade of gray that corresponds to the value `n` - the higher the number (i.e. the later the sequence diverges), the brighter the color.

In practice the number of iterations performed ot determine whether the sequence `Z = Z² + C, Z0 = 0` is bounded is always capped to some constant, let's call it MAXN. This means that any plausible plot of the Mandelbrot set is an approximation, because no matter how large MAXN, there is always chance that there will be undetected sequences that diverge after MAXN iterations. On the other hand, the existance of the MAXN value allows for a map between the mandelbrot value, which would be a number in the interval `[1, NAXN]`, and a RGB grayscale color value in the interval `[1, 255]`.

## Source Code

Because Mandelbrot set is based on complex numbers, some complex number operation library is required. In this Mandelbrot set implementation, all complex number math is incapsulated in a global `utility.complex` namespace. Some of the `utility.complex.*` functions are never used in the program and are provided here for completeness. Note that any complex number can be also considered as a two dimensional vector, e.g. the complex number a+bi is equivalent to the vector `(a, b)`.

Here is a pseudo-code summary of the `utility.complex` interface:

```12345678910111213namespace utility.complex{    newComplex(real: number, imaginary: number): object;    // creates a complex number with both real and imaginary parts    newImaginary(imaginary: number): object;    // creates a pure imaginary complex number    newReal(real: number): object;    // creates a pure real complex number    magnitude(c: object): number;    // calculates the magnitude `|c|` for the complex number's vector `c`    scale(c: object, scale: number): object;    // multiplies a complex number's vector by a scalar    translate(c: object, dre: number, dim: number): object;    // translates a complex number's vector    sum(a: object, b: object): object;    // summates two complex numbers    sub(a: object, b: object): object;    // subtracts two complex numbers    mul(a: object, b: object): object;    // multiplies two complex numbers    div(a: object, b: object): object;    // devides two complex numbers} ``` ```123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596var utility ={    complex:    {        //    creates a complex number with both real and imaginary parts        newComplex: function(real, imaginary)        {            return {                re: real,                im: imaginary,            };        },        //    creates a pure imaginary complex number        newImaginary: function(imaginary)        {            return {                re: 0,                im: imaginary,            };        },        //    creates a pure real complex number        newReal: function(real)        {            return {                re: real,                im: 0,            };        },        //    calculates the magnitude |c| for the complex number's vector c        magnitude: function(c)        {            return Math.sqrt(c.im * c.im + c.re * c.re);        },        //    multiplies a complex number's vector by a scalar        scale: function(c, scale)        {            return {                re: c.re * scale,                im: c.im * scale,            };        },        //    translates a complex number's vector        translate: function(c, dre, dim)        {            return {                re: c.re + dre,                im: c.im + dim,            };        },        //    summates two complex numbers        sum: function(a, b)        {            return {                re: a.re + b.re,                im: a.im + b.im,            };        },        //    subtracts two complex numbers        sub: function(a, b)        {            return {                re: a.re - b.re,                im: a.im - b.im,            };        },        //    multiplies two complex numbers        mul: function(a, b)        {            //  (a.re + a.im * i) * (b.re + b.im * i) =            //  = a.re * (b.re + b.im * i) + a.im * i * (b.re + b.im * i) =            //  = a.re * b.re + a.re * b.im * i + a.im * i * b.re + a.im * i * b.im * i =            //  = a.re * b.re + a.re * b.im * i + a.im * i * b.re  =            //  = (a.re * b.re - a.im * b.im) + (a.re * b.im + a.im * b.re) * i            return {                re: a.re * b.re - a.im * b.im,                im: a.re * b.im + a.im * b.re,            };        },        //    devides two complex numbers        div: function(a, b)        {            //  http://www.mathportal.org/formulas/algebra/complex.php            //  a.re = a            //  a.im = b            //  b.re = c            //  b.im = d            //  (a.re*b.re + a.im*b.im)/(b.re*b.re + b.im*b.im) + i*(a.im*b.re - a.re*b.im)/(b.re*b.re + b.im*b.im)            //  r.re = (a.re*b.re + a.im*b.im)/(b.re*b.re + b.im*b.im)            //  r.im = (a.im*b.re - a.re*b.im)/(b.re*b.re + b.im*b.im)            return {                re: (a.re * b.re + a.im * b.im) / (b.re * b.re + b.im * b.im),                im: (a.im * b.re - a.re * b.im) / (b.re * b.re + b.im * b.im),            };        },    },}; ```

The goal of this program is to visualise the Mandelbrot set in an HTML canvas. For that purpose a simple graphics device is provided (`class CanvasDevice`), which wraps around the native canvas element and adds the following functionality:

• buffering - all drawing operations are performed on a hidden canvas and are flushed to the screen at once, on demand;
• image scaling and translation;
• vector and raster (pixel) drawing; the vector drawing is limited to drawing a rectangle;
• pixel-accurate visualization of both vector and raster graphics.

Here is a pseudo-code summary of the `CanvasDevice` interface:

```1234567891011121314151617class CanvasDevice{    constructor(canvasElement: HTMLElement, width: number, height: number);     setScale(value: number): void;    getScale(): number;    setOrigin(offsetX, offsetY): void;    getScaledOriginX(): number;    getScaledOriginY(): number;     lockVectorGraphics(): object;    releaseVectorGraphics(): void;    lockRasterGraphics(): object;    releaseRasterGraphics(): void;     flush(): void;} ``` ```123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178function CanvasDevice(canvasElement, width, height){    this.width = width;    this.height = height;     this.scale = 1;    this.offsetX = 0;    this.offsetY = 0;    this.scaledOffsetX = 0;    this.scaledOffsetY = 0;     this.canvasElement = canvasElement;    this.canvasElement.style.width = this.width + "px";    this.canvasElement.style.height = this.height + "px";    this.canvasElement.width = this.canvasElement.offsetWidth;    this.canvasElement.height = this.canvasElement.offsetHeight;     this.canvasGraphics = this.canvasElement.getContext("2d", {alpha: false});    this.canvasGraphics.imageSmoothingEnabled = false;     this.bufferElement = document.createElement("canvas");    this.bufferElement.style.width = this.canvasElement.style.width;    this.bufferElement.style.height = this.canvasElement.style.height;    this.bufferElement.width = this.canvasElement.width;    this.bufferElement.height = this.canvasElement.height;     this.bufferGraphics = this.bufferElement.getContext("2d", {alpha: false});    this.bufferGraphics.imageSmoothingEnabled = false;    this.bufferGraphics.translate(0.5, 0.5);    //  decrease antialiasing, grant pixel-accurate h- and v-lines     this.lockType = CanvasDevice.LOCK_TYPE_NONE;    this.pixelGraphics = null;    this.vectorGraphics = null;     this.setScale = function(value)    {        this.canvasGraphics.scale(1/this.scale, 1/this.scale);        this.scale = value;        this.scaledOffsetX = Math.round(this.offsetX / this.scale);        this.scaledOffsetY = Math.round(this.offsetY / this.scale);        this.canvasGraphics.scale(this.scale, this.scale);    };     this.getScale = function()    {        return this.scale;    };     this.setOrigin = function(offsetX, offsetY)    {        this.bufferGraphics.translate(-this.scaledOffsetX, -this.scaledOffsetY);        this.offsetX = offsetX;        this.offsetY = offsetY;        this.scaledOffsetX = Math.round(this.offsetX / this.scale);        this.scaledOffsetY = Math.round(this.offsetY / this.scale);        this.bufferGraphics.translate(this.scaledOffsetX, this.scaledOffsetY);    };     this.getScaledOriginX = function()    {        return this.scaledOffsetX;    };     this.getScaledOriginY = function()    {        return this.scaledOffsetY;    };     this.lockVectorGraphics = function()    {        if(this.lockType != CanvasDevice.LOCK_TYPE_NONE)        {            throw "ASSERTION FAILED: this.lockType == CanvasDevice.LOCK_TYPE_NONE";        }        this.lockType = CanvasDevice.LOCK_TYPE_VECTOR;         this.vectorGraphics =        {            graphics: this.bufferGraphics,            device: this,            clear: function(fillStyle)            {                this.graphics.beginPath();                this.graphics.fillStyle = fillStyle;                this.graphics.fillRect(                    -this.device.getScaledOriginX() - 1,                    -this.device.getScaledOriginY() - 1,                    this.device.width + 1,                    this.device.height + 1                );                this.graphics.fill();            },            drawRect: function(x, y, width, height, strokeStyle)            {                this.graphics.beginPath();                this.graphics.strokeStyle = strokeStyle.strokeStyle;                this.graphics.lineWidth = strokeStyle.lineWidth;                this.graphics.rect(x, y, width, height);                this.graphics.stroke();            },        };         return this.vectorGraphics;    };     this.releaseVectorGraphics = function()    {        if(this.lockType != CanvasDevice.LOCK_TYPE_VECTOR)        {            throw "ASSERTION FAILED: this.lockType == CanvasDevice.LOCK_TYPE_VECTOR";        }        this.vectorGraphics = null;        this.lockType = CanvasDevice.LOCK_TYPE_NONE;    };     this.lockRasterGraphics = function()    {        if(this.lockType != CanvasDevice.LOCK_TYPE_NONE)        {            throw "ASSERTION FAILED: this.lockType == CanvasDevice.LOCK_TYPE_NONE";        }        this.lockType = CanvasDevice.LOCK_TYPE_RASTER;         this.pixelGraphics =        {            pixelData: this.bufferGraphics.getImageData(0, 0, this.bufferElement.width, this.bufferElement.height),            device: this,            setPixel: function(x, y, r, g, b, a)            {                if(this.device.lockType != CanvasDevice.LOCK_TYPE_RASTER)                {                    throw "ASSERTION FAILED: this.lockType == CanvasDevice.LOCK_TYPE_RASTER";                }                 x = Math.round(x + this.device.scaledOffsetX);                y = Math.round(y + this.device.scaledOffsetY);                 if(x >= this.pixelData.width || x < 0)                {                    return;                }                if(y >= this.pixelData.height || y < 0)                {                    return;                }                 var index = (x + y * this.pixelData.width) * 4;                this.pixelData.data[index++] = r;                this.pixelData.data[index++] = g;                this.pixelData.data[index++] = b;                this.pixelData.data[index++] = a;            },        };         return this.pixelGraphics;    };     this.releaseRasterGraphics = function()    {        if(this.lockType != CanvasDevice.LOCK_TYPE_RASTER)        {            throw "ASSERTION FAILED: this.lockType == CanvasDevice.LOCK_TYPE_RASTER";        }         this.bufferGraphics.putImageData(this.pixelGraphics.pixelData, 0, 0);        this.pixelGraphics = null;        this.lockType = CanvasDevice.LOCK_TYPE_NONE;    };     this.flush = function()    {        this.canvasGraphics.drawImage(this.bufferElement, 0, 0);    };} CanvasDevice.LOCK_TYPE_NONE = 0;CanvasDevice.LOCK_TYPE_VECTOR = 1;CanvasDevice.LOCK_TYPE_RASTER = 2; ``` ```123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331function MandelbrotView(device, width, height, style){    this.device = device;    this.device.setOrigin(this.device.width / 2, this.device.height / 2);     this.width = width;    this.height = height;     this.zoom = 1;    this.offsetX = 0;    this.offsetY = 0;     this.sensitivity = 100;     this.mandelbrotDocument = null;     this.refresh = function()    {        var v = this.device.lockVectorGraphics();        try        {            v.clear("white");        }        finally        {            this.device.releaseVectorGraphics();        }         var r = this.device.lockRasterGraphics();        try        {            var start = new Date();             var height = this.height;            var width = this.width;            var scaled_height = Math.ceil(height / this.device.getScale());            var scaled_width = Math.ceil(width / this.device.getScale());             var c_scale = this.device.getScale()/this.zoom;            var c_dre = -this.offsetX;            var c_dim = -this.offsetY;             for(var x = -this.device.getScaledOriginX(); x < scaled_width; ++x)            {                for(var y = -this.device.getScaledOriginY(); y < scaled_height; ++y)                {                    var c = utility.complex.newComplex(x / width, y / height);                    var c_scaled = utility.complex.scale(c, c_scale);                    var c_scaled_translated = utility.complex.translate(c_scaled, c_dre, c_dim);                     var mandelbrotValue = this.mandelbrotDocument.getMandelbrotValue(c_scaled_translated) / this.sensitivity;                     if(mandelbrotValue > 1)                    {                        mandelbrotValue = 1;                    }                     var red = 0, green = 0, blue = 0;                    if(mandelbrotValue != -1)                    {                        red = Math.ceil(255 * mandelbrotValue * 1.9);                        if(red > 255) red = 255;                        green = Math.ceil(255 * mandelbrotValue * 1.3);                        if(green > 255) green = 255;                        blue = Math.ceil(255 * mandelbrotValue / 1.4);                        if(blue > 255) blue = 255;                    }                    r.setPixel(x, y, red, green, blue, 255);                }            }             var end = new Date();            var time = end.getTime() - start.getTime();            console.log(912, Math.round(time/10)/100 + "s");        }        finally        {            this.device.releaseRasterGraphics();        }    };     this.setDocument = function(value)    {        this.mandelbrotDocument = value;    };     this.getDocument = function(value)    {        return this.mandelbrotDocument;    };     this.setCoarsness = function(value)    {        this.device.setScale(value);    };     this.getCoarsness = function()    {        return this.device.getScale();    };     this.setZoom = function(value)    {        this.zoom = value;    };     this.getZoom = function()    {        return this.zoom;    };     this.setOffsetX = function(value)    {        this.offsetX = value;    };     this.getOffsetX = function()    {        return this.offsetX;    };     this.setOffsetY = function(value)    {        this.offsetY = value;    };     this.getOffsetY = function()    {        return this.offsetY;    };     this.setSensitivity = function(value)    {        this.sensitivity = value;    };     this.getSensitivity = function()    {        return this.sensitivity;    };} //  http://www.ddewey.net/mandelbrot/function MandelbrotDocument(){    this.detail = 250;     this.setDetail = function(value)    {        this.detail = value;    };     this.getDetail = function()    {        return this.detail;    };     //  c: complex, c.re (- [0, 1], c.im (- [0, 1]    //  returns (- [0, 1]    this.getMandelbrotValue = function(c)    {        var length = this.detail;        var z = c;        var i = 0;        for(; i < length; ++i)        {            var z_magnitude = utility.complex.magnitude(z);            if(z_magnitude > 2)            {                break;            }             var z_squared = utility.complex.mul(z, z);            z = utility.complex.sum(z_squared, c);        }         if(i >= length)        {            return -1;        }         return i;    }     this.getMandelbrotSequence = function(c, iterationCount)    {        var result = [];         var length = iterationCount;        var z = c;        var i = 0;        for(; i < length; ++i)        {            var z_magnitude = utility.complex.magnitude(z);             result.push({re: z.re, im: z.im, m:z_magnitude});             var z_squared = utility.complex.mul(z, z);            z = utility.complex.sum(z_squared, c);        }         return result;    }} function Application(){    this.run = function()    {        var deviceCanvas = document.createElement("canvas");        document.body.appendChild(deviceCanvas);         this.device = new CanvasDevice(deviceCanvas, 800, 800);         this.mandelbrotDocument = new MandelbrotDocument();         this.mandelbrotView = new MandelbrotView(this.device, this.device.width, this.device.height);        this.mandelbrotView.setDocument(this.mandelbrotDocument);         this.mandelbrotView.setCoarsness(2);         //  provides interesting details up to zoom of 1024000000        this.mandelbrotView.setOffsetX( 1.7480271785);        this.mandelbrotView.setOffsetY(-0.0000087174);         //  BEGIN: uncomment only one of the comment blocks         //this.mandelbrotView.setZoom(1024000000);        //this.mandelbrotView.setSensitivity(400);        //this.mandelbrotDocument.setDetail(1500);         //this.mandelbrotView.setZoom(102400000);        //this.mandelbrotView.setSensitivity(300);        //this.mandelbrotDocument.setDetail(400);         //this.mandelbrotView.setZoom(10240000);        //this.mandelbrotView.setSensitivity(300);        //this.mandelbrotDocument.setDetail(400);         //this.mandelbrotView.setZoom(1024000);        //this.mandelbrotView.setSensitivity(200);        //this.mandelbrotDocument.setDetail(300);         //this.mandelbrotView.setZoom(102400);        //this.mandelbrotView.setSensitivity(150);        //this.mandelbrotDocument.setDetail(300);         //this.mandelbrotView.setZoom(10240);        //this.mandelbrotView.setSensitivity(140);        //this.mandelbrotDocument.setDetail(250);         //this.mandelbrotView.setZoom(1024);        //this.mandelbrotView.setSensitivity(140);        //this.mandelbrotDocument.setDetail(250);         //this.mandelbrotView.setZoom(102);        //this.mandelbrotView.setSensitivity(140);        //this.mandelbrotDocument.setDetail(250);         //this.mandelbrotView.setZoom(10);        //this.mandelbrotView.setSensitivity(140);        //this.mandelbrotDocument.setDetail(250);         //this.mandelbrotView.setZoom(5);        //this.mandelbrotView.setSensitivity(140);        //this.mandelbrotDocument.setDetail(250);         //this.mandelbrotView.setZoom(1);        //this.mandelbrotView.setSensitivity(140);        //this.mandelbrotDocument.setDetail(250);         this.mandelbrotView.setZoom(0.35);        this.mandelbrotView.setOffsetX(0.8);        this.mandelbrotView.setOffsetY(0);        this.mandelbrotView.setSensitivity(120);        this.mandelbrotDocument.setDetail(250);         //  END: uncomment only one of the comment blocks         this.mandelbrotView.refresh();        this.device.flush();    };     this.runSequenceGenerator = function()    {        function _printSequence(c, sequence)        {            var sb = [];             sb.push("c = ");            sb.push("(");            sb.push(c.re);            sb.push(", ");            sb.push(c.im);            sb.push("), ");             sb.push("S(c) = ");            for(var length = sequence.length, i = 0; i < length; ++i)            {                var z = sequence[i];                if(isNaN(z.m)) break;                if(i != 0) sb.push(", ");                sb.push("(");                sb.push(Math.round(z.re * 100) / 100);                sb.push(", ");                sb.push(Math.round(z.im * 100) / 100);                sb.push(", ");                sb.push(Math.round(z.m * 100) / 100);                sb.push(")");            }             return sb.join("")        }         this.mandelbrotDocument = new MandelbrotDocument();         var c1 = utility.complex.newComplex(1, 1);        var sequence1 = this.mandelbrotDocument.getMandelbrotSequence(c1, 20)        var text1 = _printSequence(c1, sequence1);        console.log(9111, text1);         var c2 = utility.complex.newComplex(0.1, 0.1);        var sequence2 = this.mandelbrotDocument.getMandelbrotSequence(c2, 20)        var text2 = _printSequence(c2, sequence2);        console.log(9112, text2);    };} var application = new Application();application.runSequenceGenerator();application.run(); ```

## Code Guide

### Code points of interest

#### CanvasDevice

`CanvasDevice` is a basic, general purpose buffered drawing device that uses HTML canvas. It is capable of drawing both raster and vector graphics, limited to what's needed for this example specifically.

• `this.bufferGraphics.translate(0.5, 0.5);` - this line of code decreases the antialiasing and makes horisontal and vertical single-pixel lines as well as single pixels more crisp.
• Device scaling is applied on the `canvasGraphics`. In order to make `this.bufferGraphics.translate(0.5, 0.5);` work, we need to keep `bufferGraphics`'s scale to 1.
• Device translation is performed by the `bufferGraphics`. (reasoning required, or refactor to perform translation on `canvasGraphics`)
• The HTML canvas stacks `.scale` and `.translate` calls, so when setting the scale or offset to an absolute value, the canvas must be reverted to it's original scale or translation first: `this.canvasGraphics.scale(1/this.scale, 1/this.scale);` or `this.bufferGraphics.translate(-this.scaledOffsetX, -this.scaledOffsetY);`

#### MandelbrotDocument

`MandelbrotDocument` represents the abstraction of the Mandelbrot set and is in charge of determining, which values of `c` belong to the set and which do not. For the values of `c`, which are not within the set, it provides information on how quickly `S(c)` diverges (`mandelbrotValue` as we call it in the code). The implementation is pretty straightforward.

##### MandelbrotDocument properties
• detail (`getDetail`, `setDetail`) - the number of iterations to perform before assuming that S(c) is bounded

#### MandelbrotView

`MandelbrotView` is the component that knows how to draw a `MandelbrotDocument` to a `CanvasDevice`. The complex plane, containing all possible values of `c`, is mapped to device coordinates: (re -> x, im -> y) - and positioned within the view port through offset and scaling. The glow level corresponds to the `mandelbrotValue`, returned by the document. The coloring we use in this implementation is a result of several experiments and its purpose is to reveal detail, rather than to create beauty.

• The `MandelbrotView` logs to the console the time it took to perform the rendering.
• Setting coarseness
```1234this.setCoarsness = function(value){    this.device.setScale(value);}; ```

preseves the perceived zoom of the view by scaling the device and negating that scale computationally during image refresh:

```1234var scaled_height = Math.ceil(height / this.device.getScale());var scaled_width = Math.ceil(width / this.device.getScale()); var c_scale = this.device.getScale()/this.zoom; ```
##### MandelbrotView properties
• offsetX (`getOffsetX`, `setOffsetX`) - x-offset of the image from the view center
• offsetY (`getOffsetY`, `setOffsetY`) - y-offset of the image from the view center
• zoom (`getZoom`, `setZoom`) - level of zooming (0.35 fits the while figure inside the viewing area)
• sensitivity (`getSensitivity`, `setSensitivity`) - the `mandelbrotValue`, returned by the document, increases with increasing the zoom; use the sensitivity value to compensate and to reduce the glow in the image; higher values yeld lower glow
• coarsness (`getCoarsness`, `setCoarsness`) - the size of the pixel (higher values produce more coarse images and yeld better performance speed);

#### Application

`Application`'s method `run` setups a device, a document and a view, and lets the view draw on the screen. A bunch of commented alternative document/view configurations can be uncommented and used to explore the Mandelbot set fractal with different zoom levels.

## Screenshots

These images are generated with the JavaScript code above. To produce a specific image, uncomment the corresponding `mandelbrotDocument` configuration block at the end of the code. Note that only one `mandelbrotDocument` configuration block at a time can stay uncommented.            