HTML5 Mandelbrot Set w/ Realtime Julia Set Explorer

Programmed in 225 lines of Javascript Code by Chris Sunami
var M = (function ($) { "use strict"; var constants = { testRadius: 2, maxCycles: 100 }, properties = { grid: { center: {r: -0.5, i: 0}, resolution: {r: 320, i: 320}, zoom: {r: 0.75, i: 0.75}, dim: {r: {min: 0, max: 0}, i: {min: 0, max: 0}}, iter: {r: 0, i: 0} }, display: { currentType: 'M', M: { $canvas: $("<canvas/>"), img: null, context: null, type: 'M', offset: 0 }, J: { $canvas: $("<canvas/>"), img: null, context: null, type: 'J', offset: 0, cfactor: {r: 0, i: 0} } }, color: { } }, methods = (function () { var math = (function () { var complexSquare = function (n) { return {r: n.r * n.r - n.i * n.i, i: 2 * n.r * n.i}; }, complexAdd = function (n1, n2) { return {r: n1.r + n2.r, i: n1.i + n2.i}; }, complexTestAgainstRadius = function (n, rad) { return (n.r * n.r + n.i * n.i) > rad * rad; }, iterate = function (n, test) { return complexAdd(complexSquare(n), test); }; return {test: complexTestAgainstRadius, iterate: iterate}; }()), display = (function (cd, pg) { var toDisplay = function (n) { return { r: Math.round((n.r - pg.center.r) * pg.zoom.r * pg.resolution.r / 2 + pg.resolution.r / 2), i: Math.round((n.i - pg.center.i) * pg.zoom.i * pg.resolution.i / 2 + pg.resolution.i / 2) }; }, fromDisplay = function (n) { return { r: (n.r - pg.resolution.r / 2) * 2 / (pg.zoom.r * pg.resolution.r) + pg.center.r, i: (n.i - pg.resolution.i / 2) * 2 / (pg.zoom.i * pg.resolution.i) + pg.center.i }; }, getPixel = function (point) { var pt = toDisplay(point); return 4 * (pt.i * pg.resolution.r + pt.r); }; return { getPixel: getPixel, fromDisplay: fromDisplay }; }(constants.display, properties.grid)), color = (function (pd) { var plotPixel = function (pixel, hue) { var pdx = pd[pd.currentType]; pdx.img.data[pixel] = hue.r; pdx.img.data[pixel + 1] = hue.g; pdx.img.data[pixel + 2] = hue.b; pdx.img.data[pixel + 3] = hue.a; }, tint = function (value) { if (value < 10) { return {r: value * 10, g: 0, b: value, a: 180}; } return {r: 0, g: value, b: value * 10, a: 180}; }, plotPoint = function (point, value) { var pixel = display.getPixel(point); plotPixel(pixel, tint(value)); }; return plotPoint; }(properties.display)), construction = (function (pg, c) { var grid = { update: (function () { var dim = function () { pg.dim.r.min = pg.center.r - 1 / pg.zoom.r; pg.dim.r.max = pg.center.r + 1 / pg.zoom.r; pg.dim.i.min = pg.center.i - 1 / pg.zoom.i; pg.dim.i.max = pg.center.i + 1 / pg.zoom.i; }, iter = function () { pg.iter.r = 2 / (pg.zoom.r * pg.resolution.r); pg.iter.i = 2 / (pg.zoom.i * pg.resolution.i); }, center = function (center) { pg.center = center; dim(); }, resolution = function (resolution) { pg.resolution = resolution; iter(); }, zoom = function (zoom) { pg.zoom = zoom; dim(); iter(); }, init = function () { dim(); iter(); }; return {center: center, resolution: resolution, zoom: zoom, init: init}; }()), run: function (callback) { var x, y; for (x = pg.dim.r.min; x <= pg.dim.r.max; x += pg.iter.r) { for (y = pg.dim.i.min; y <= pg.dim.i.max; y += pg.iter.i) { callback({r: x, i: y}); } } } }, pointTest = function (orbitPoint) { var iter, cfactor, pd = properties.display; switch (pd.currentType) { case "M": cfactor = orbitPoint; break; case "J": cfactor = pd.J.cfactor; break; } for (iter = 0; iter <= c.maxCycles; iter += 1) { orbitPoint = math.iterate(orbitPoint, cfactor); if (math.test(orbitPoint, c.testRadius)) { return iter; } } return iter; }, plot = function (testPoint) { var cycles = pointTest(testPoint); color(testPoint, cycles); }; return {run: grid.run, update: grid.update, plot: plot}; }(properties.grid, constants)), screen = (function (pd, pg) { var createCanvas = function (pdx) { pdx.context = pdx.$canvas[0].getContext("2d"); pdx.$canvas.width(pg.resolution.r); pdx.$canvas.height(pg.resolution.i); pdx.$canvas[0].width = pg.resolution.r; pdx.$canvas[0].height = pg.resolution.i; pdx.img = pdx.context.createImageData(pg.resolution.r, pg.resolution.i); $("h1").after(pdx.$canvas); }, init = function () { createCanvas(pd.J); createCanvas(pd.M); pd.M.offset = pd.M.$canvas.offset(); }; return init; }(properties.display, properties.grid)), test = (function (pdx) { var gridSpec = function (point) { color(point, 80); pdx.context.putImageData(pdx.img, 0, 0); }, run = function () { construction.run(gridSpec); }; return run; }(properties.display.M)), fractal = (function (pd) { var run = function (pdx) { pd.currentType = pdx.type; construction.run(construction.plot); pdx.context.putImageData(pdx.img, 0, 0); }, init = function () { construction.update.init(); screen(); run(pd.M); run(pd.J); }; return {init: init, run: run}; }(properties.display)), juliaTracker = (function (pd) { var init = function () { pd.M.$canvas.on("mousemove", function (e) { pd.J.cfactor = display.fromDisplay({ r: e.pageX - pd.M.offset.left, i: e.pageY - pd.M.offset.top }); fractal.run(pd.J); }); }; return init; }(properties.display)); return {init: fractal.init, run: fractal.run, update: construction.update, julia: juliaTracker, test: test}; }()); return { init: methods.init, run: methods.run, update: methods.update, properties: properties.grid, test: methods.test, julia: methods.julia }; }(jQuery));