Armillary Sphere
Globe visualization
Not a constellation but the instrument itself — it renders the sphere.
- Role
- Team f07 — data visualization coursework (GiVD, UB), agent-assisted build
- Timeline
- May 2026
- Stack
- globe.gl · Three.js · GLSL · D3 · Next.js 16 · React 19 · Zod · Jest
Problem
The assignment asked for a data visualization. The team built a
visualization instrument instead: a tool where any geospatial tabular
file — CSV, JSON, GeoJSON, or Excel — can be pointed at a 3D globe, and every
mapping decision (which columns are coordinates, value, color, time; which
of seven render methods; which of 40+ color scales; which of 19 statistical
transforms) is live configuration, not code. Five bundled datasets prove the
range: the classic 9,950-point world-population set, 16,944 earthquakes
colored by magnitude, UCDP/PRIO armed conflicts drawn as arcs between
belligerents, and two world-cities sets — one from GeoJSON, one straight out
of an .xlsx.
Approach
Everything is data, discovered at runtime. Datasets, JPEG planet
textures, GLSL shader groups, and even data transformers — ES modules
fetched and import()ed live, like the conflict geocoder that resolves
country names to centroids against a public API with an alias table and a
deterministic hash fallback — are files the app finds, not imports it was
built with. Configs are Zod-validated JSON, and a server action writes
edited configs back to disk, so the tool round-trips its own state.
Seven render strategies behind one factory — points, labels, hexbins, heatmap, rings, 3D objects, and arcs — each a class implementing the same interface. Multiple variables render simultaneously: strategies collect per-variable series into typed buckets, then each layer applies its bucket once. Value and color are deliberately decoupled columns with independent domains, so altitude can encode depth while color encodes magnitude — the earthquake config in one sentence.
Real GLSL, swappable at runtime. Shader groups live in public/shaders/
and are scanned server-side (a group is offered only if all five
vertex/fragment pairs exist). The red group tints the globe, adds a
Lambert term and a smoothstep rim atmosphere; object shaders do diffuse +
specular with a color uniform, wired through globe.gl’s material hooks onto
THREE.ShaderMaterial.
Performance by honest budget, not cleverness. No instancing, no LOD —
instead, hard caps chosen for the datasets at hand: 3,000 object meshes
(8×8-segment spheres), 1,500 arcs, 300 rings, hexbin resolution derived from
the radius slider, field inspection sampling only the first 150 rows per
time slice. And one battle scar worth recording: the heatmap layer’s WebGPU
path failed silently in some environments — rejected promises with no
handler — so the app deliberately deletes navigator.gpu to force the
CPU KDE path, trading GPU speed for a heatmap that always renders.
Result
A working instrument, not a demo: load any of the five projects, remap any column live, switch render methods and shader groups without a reload, scrub through time slices, and read the ranked bar chart, distribution histogram, and gradient legend that accompany the globe. The architecture is hexagonal (domain entities and repository interfaces with infrastructure implementations and thin service singletons), covered by 34 Jest test files over services, repositories, and hooks.
Two honest notes. This was a university team assignment, built agent-assisted — the repository says so on its face, and the human work was direction, integration, and review as much as line-writing. And the caps above are the whole performance story: nothing was profiled, and a serious next step would be instancing the object layer and measuring instead of capping.