//
// The Mandelbrot Fractal Set
// Copyright c Brian P. Vogl
// Javascript version
// October 15 - 20, 2018
//
var canvas = document.getElementById("thecanvas");
canvas.style.backgroundColor = "black";
var P, X, Y, Xsq, Ysq;
var i, color, row, col;
var rgb = [ "#000000", "#A1A1A1", "#FF0000", "#00FF00", "#0000FF", "#FFFF10", "#52b552", "#31b5d6",
"#ffce9c", "#bdc6de", "#ce007b", "#527b10", "#ffb573", "#bdc6de", "#f7bdde", "#ffffff" ];
var maxcolor = rgb.length;
var maxcol = canvas.width;
var maxrow = canvas.height;
var maxiter = 512;
var maxsize = 4;
var Xmax = 0.5;
var Xmin = -2.0
var Ymax = 1.15;
var Ymin = -1.15;
var delP = (Xmax - Xmin)/maxcol;
var delQ = (Ymax - Ymin)/maxrow;
var Q = new Array(350);
Q[0] = Ymin;
for(row = 1; row < maxrow; row++)
Q[row] = Q[row-1] + delQ;
var timers = [];
var xs=-1,ys=-1,xn=-1,yn=-1; // Mouse position variables
var ctx;
if(ctx = canvas.getContext('2d'))
{ canvas.oncontextmenu = function (event) {
event.preventDefault();
MouseDn();
};
document.addEventListener("mousedown", MouseDn);
document.addEventListener("mouseup" , MouseUp);
document.addEventListener("mousemove", MouseMove);
do_display(ctx);
}
function MouseDn(event)
{ // Determine where on the canvas the button was pressed
for (var i = 0; i < timers.length; i++) // reset any timers.
{ clearTimeout(timers[i]);
}
var bbox = canvas.getBoundingClientRect();
var buttstate = event.which > 1 ? 1 : 0;
xs = event.clientX - bbox.left;
ys = event.clientY - bbox.top;
}
function MouseMove(event)
{ var bbox = canvas.getBoundingClientRect();
var buttstate = event.which > 1 ? 1 : 0;
var x1,y1,w1,h1;
if(ys != -1)
{ ctx.globalCompositeOperation = "lighter";
ctx.lineWidth = 1;
ctx.strokeStyle = "red";
xn = event.clientX - bbox.left;
yn = event.clientY - bbox.top;
if(xs>xn) { x1=xn; w1=xs-xn; }
else { x1=xs; w1=xn-xs; }
if(ys>yn) { y1=yn; h1=ys-yn; }
else { y1=ys; h1=yn-ys; }
ctx.strokeRect(x1, y1, w1, h1);
}
}
function MouseUp(event)
{ var bbox = canvas.getBoundingClientRect();
xe = event.clientX - bbox.left;
ye = event.clientY - bbox.top;
if(xs>xe) { tmp = xs; xs = xe; xe = tmp; }
if(ys>ye) { tmp = ys; ys = ye; ye = tmp; }
mintmp = ((Xmax - Xmin) * xs )/ maxcol + Xmin;
maxtmp = ((Xmax - Xmin) * xe )/ maxcol + Xmin;
Xmin = mintmp;
Xmax = maxtmp;
mintmp = ((Ymax - Ymin) * ys )/ maxrow + Ymin;
maxtmp = ((Ymax - Ymin) * ye )/ maxrow + Ymin;
Ymin = mintmp;
Ymax = maxtmp;
delP = (Xmax - Xmin)/maxcol;
delQ = (Ymax - Ymin)/maxrow;
Q[0] = Ymin;
for(row = 1; row < maxrow; row++)
Q[row] = Q[row-1] + delQ;
xs=-1; ys=-1; xn=-1; yn=-1;
ctx.globalCompositeOperation = "source-over";
do_display(ctx);
}
function do_display(ctx)
{ P = Xmin;
ctx.lineWidth = 1;
for(col = 0; col < maxcol; col++)
{ myfunc = function(col, P){ handler(col, P); };
// Save the timer id's So we can cancel later
timers.push(setTimeout(myfunc, 0, col, P));
P += delP;
}
//alert("Doing Display " + color + " " + col + " " + row);
}
function do_pixel(col, row, color, amp)
{ var id = ctx.createImageData(1,1); // only do this once per page
var d = id.data; // only do this once per page
var rgb_c = rgb[color % maxcolor];
d[0] = parseInt(rgb_c.substr(1, 2), 16);
d[1] = parseInt(rgb_c.substr(3, 2), 16);
d[2] = parseInt(rgb_c.substr(5, 2), 16);
d[3] = 200;
ctx.putImageData( id, col, row );
}
// Put a single line of data on the screen
function handler(col, P)
{ for(row = 0; row < maxrow; row++)
{ X=Y=Xsq=Ysq=0.0;
color=0;
while((color < maxiter) && (Xsq+Ysq < maxsize))
{ Xsq = X*X;
Ysq = Y*Y;
Y *= X;
Y += Y + Q[row];
X = Xsq - Ysq + P;
color++;
}
// 2 ways to do this
// The do_pixel method takes longer.
// do_pixel(col, row, color, 255);
ctx.strokeStyle = rgb[color % maxcolor];
ctx.strokeRect(col, row, 1, 1);
}
}