A reimplementation of Mike Bostock's wonderful work with Charming.js. After I finished, I thought it would be interesting to reverse the colors of the circles and background. Here's the final result:
The crucial element of this animation lies in calculating the rotation angle:
const l = ((Math.hypot(x, y) + Math.atan2(y, x) / (Math.PI * 2) - (now / 3000)) % 1) * -360;
Let’s break it down:
const dist = Math.hypot(x, y);
const normalizedAngle = Math.atan2(y, x) / (Math.PI * 2);
const time = now / 3000;
const rawRotation = dist + normalizedAngle - time;
const normalizedRotation = rawRotation % 1;
const l = normalizedRotation * -360;
The is the final code:
const width = 640;
const height = 640;
const inset = 40;
const radius = 12;
const data = d3.cross(d3.ticks(-1, 1, 20), d3.ticks(-1, 1, 20));
const circle = d3.geoCircle()();
const projection = d3.geoOrthographic().translate([0, 0]).scale(radius);
const path = d3.geoPath(projection);
const scaleX = d3.scaleLinear([-1, 1], [inset, width - inset]);
const scaleY = d3.scaleLinear([-1, 1], [inset, height - inset]);
return SVG.svg({
width,
height,
style: "background:black",
children: [
SVG.g(data, {
transform: ([x, y]) => `translate(${scaleX(x)}, ${scaleY(y)})`,
children: [
([x, y]) => {
// prettier-ignore
const l = ((Math.hypot(x, y) + Math.atan2(y, x) / (Math.PI * 2) - (now / 3000)) % 1) * -360;
projection.rotate([0, l, -l]);
return SVG.path({d: path(circle), fill: "white"});
},
],
}),
],
});