Here is a source code for static part of the project. It renders the orbit inside browser. I didn't catch a time to finish dynamic rotation stuff, but if anyone wants to build up upon the static part, they are very welcome.
function orbital (svg) {
"use strict";
var ratio = 0.5;//575;
var minRadius = 0.5;
var branchCount = 42;
var pixelPrecision = 1 / 32;
var fill1 = "rgb(255, 255, 150)";//"lightgray";
var stroke1 = "gray";
var fill2 = stroke1;
var stroke2 = fill1;
var svgns = "http://www.w3.org/2000/svg";
function insertGroup () {
var g;
g = document.createElementNS(svgns, "g");
g.setAttribute('id', 'group');
g.setAttribute('shape-rendering', 'inherit');
g.setAttribute('pointer-events', 'all');
return g;
}
function insertCircle (x, y, r, fill, stroke) {
var el = document.createElementNS(svgns, 'ellipse');
el.setAttribute('cx', x * squashX);
el.setAttribute('cy', y * squashY);
el.setAttribute('rx', r * squashX);
el.setAttribute('ry', r * squashY);
el.setAttribute('fill', fill);
el.setAttribute('stroke-width', 1);
el.setAttribute('stroke', stroke);
return el;
}
function insertRect (x, y, width, height, fill, stroke) {
var rect = document.createElementNS(svgns, 'rect');
rect.setAttribute('x', x);
rect.setAttribute('y', y);
rect.setAttribute('height', height);
rect.setAttribute('width', width);
rect.setAttribute('fill', fill);
rect.setAttribute('stroke-width', 1);
rect.setAttribute('stroke', stroke);
return rect;
}
function node() {
var children = (function () {
return {
alphaOffset: Math.PI,
itemOffset: 0,
items: (function (itemCount) {
var i, items;
items = [];
for (i = 0; i < itemCount; i++)
items.push (i);
return items;
})(branchCount),
getX: function (index) {
return (rLarge + rSmall) * Math.cos (-Math.PI / 2 - index * alpha);
},
getY: function (index) {
return (rLarge + rSmall) * Math.sin (-Math.PI / 2 - index * alpha);
}
};
})();
var render = function (minRadius, x1, y1, r1, alpha0, rec) {
function getCircle (alpha) {
var beta = alpha0 + alpha - Math.PI / 2;
var ra = 0;
var xa = x0 + r0 * Math.cos (beta);
var ya = y0 + r0 * Math.sin (beta);
var rb = 2 * r1;
var xb = x0 + (r0 + rb) * Math.cos (beta);
var yb = y0 + (r0 + rb) * Math.sin (beta);
var dr = (rb - ra) / 2;
var dx = (xb - xa) / 2;
var dy = (yb - ya) / 2;
ra += dr;
xa += dx;
ya += dy;
var j;
do {
dx /= 2;
dy /= 2;
dr /= 2;
var d = Math.sqrt (Math.pow ((xa - x1), 2) + Math.pow ((ya - y1), 2));
if (Math.abs (ra - r1) <= d) {
xa -= dx;
ya -= dy;
ra -= dr;
} else {
xa += dx;
ya += dy;
ra += dr;
}
} while (dr > pixelPrecision);
return {
x: (r0 + ra) * Math.cos (beta),
y: (r0 + ra) * Math.sin (beta),
r: ra,
alpha: alpha
};
}
function getNeighbor (c1) {
var alpha = c1.alpha / 2;
var dalpha = alpha;
var j;
var datmp = 2 * Math.pow (2 * r1, 2);
var da = Math.acos ((datmp - Math.pow (pixelPrecision, 2)) / datmp);
do {
var c2 = getCircle (alpha);
dalpha /= 2;
var d = Math.sqrt (Math.pow ((c1.x - c2.x), 2) + Math.pow ((c1.y - c2.y), 2));
if ((c1.r + c2.r) >= d) {
alpha -= dalpha;
} else {
alpha += dalpha;
}
} while (dalpha > da);
return c2;
}
var r0 = r1 * ratio;
var x0 = x1 + (r1 - r0) * Math.cos (alpha0 - Math.PI / 2);
var y0 = y1 + (r1 - r0) * Math.sin (alpha0 - Math.PI / 2);
if (r0 > minRadius) {
var g = svg;
g.appendChild (insertCircle (x0, y0, r0, fill1, stroke1));
var i;
var alpha = 4 * Math.PI / 2 - 0.3941;
var c1 = getCircle (alpha);
for (i = 0; i < branchCount; i++) {
if (c1.r > minRadius)
render (minRadius, x0 + c1.x, y0 + c1.y, c1.r, alpha0 + alpha - Math.PI, rec);
c1 = getNeighbor (c1);
alpha = c1.alpha;
}
}
};
return {
children: children,
render: render
};
}
var ww = window.innerWidth;
var hh = window.innerHeight;
var rr, squashX, squashY;
if (ww > hh) {
rr = hh / 2;
squashX = ww / hh;
squashY = 1;
} else {
rr = ww / 2;
squashX = 1;
squashY = hh / ww;
}
var r1 = rr;
var x1 = ww / squashX - rr;
var y1 = hh / squashY - rr;
var n = node();
var i = 8;
var repeat = function () {
if (i >= minRadius) {
var s = svg.cloneNode(false);
svg.parentNode.replaceChild(s, svg);
svg = s;
svg.appendChild(insertRect (0, 0, ww, hh, fill2, stroke2));
n.render (i, x1, y1, r1, 0, 1);
setTimeout (repeat, 0);
}
i/=2;
};
repeat();
}