Skip to Content
DevelopersBuildingLive view globe

3 min read

Live view globe

The admin draws the last 24 hours of orders on a globe in two places:

WhereRendererWhy
Home → Live view cardcobe  (MIT), patched with a detailed land mapSmall card, already cached on Home; cobe’s ~32k-dot ceiling is fine at that size.
Analytics → Live view (/analytics/live)Our own WebGL globe, live-globe/webgl-globe-canvas.tsxFull-page hero where merchants zoom in on India. Solid land instead of dots: any dot pattern shimmered (moiré) at full-page size.

Both take the same props (GlobeCanvasProps in live-globe/types.ts) and share the motion and input code (globe-controls.ts: ease onto India, auto-rotate, drag to spin and tilt with inertia, wheel / trackpad / touch-pinch zoom, reduced motion), the palette (globe-palette.ts, read from the --muted-foreground, --primary and --chart-2 tokens) and the overlay (globe-overlay.tsx: one focusable button per marker, the tooltip, the zoom buttons). Only the drawing differs.

Our WebGL globe

No three.js: about 6 KB minified for the engine (webgl/), 12 KB for the whole lazy chunk including the shared controls and overlay.

  • Solid land, coast and borders in one shader (webgl/globe-renderer.ts, WebGL 1 + OES_standard_derivatives). One full-screen triangle shades the sphere and the halo. Each pixel is turned back into latitude/longitude and reads the map texture (mipmapped, so it stays clean at every size): red is land, drawn darker than the ocean; the coast is where red crosses 0.5, and dividing by fwidth turns that into an on-screen distance, so the coastline is about one pixel wide at any zoom. Green holds country borders blurred on purpose: the shader turns the blurred value back into a distance (sqrt(-2 ln g)) in screen pixels for the same constant width. Faint 15° grid lines use the same trick. The longitude seam (where atan jumps from π to −π) would make the mipmap blur a line down the 180° meridian, so two u coordinates with seams half a turn apart are computed and the continuous one is used. Coast, borders and grid fade towards the rim. Zoom goes to 4× (cobe: 2.6×).
  • Markers and arcs are point sprites on top (arcs are cobe’s quadratic curve drawn as a chain of dots). Orthographic projection; anything behind the disc is dropped in the vertex shader. rotation() / anglesFor() are the same maths the overlay uses to place the marker buttons, so tooltips sit exactly on their markers.
  • Lazy loading. live-globe-hero.tsx imports webgl-globe-canvas with a dynamic import() two animation frames after mount, so the stats cards paint first and nothing of the globe is in the Home bundle. The map (apps/admin/public/globe/globe-2048.png, about 330 KB) is fetched when the globe starts; until it arrives the sphere, markers and arcs draw without land.

The maps

Live view: Natural Earth 1:10m admin-0 countries, India point of view (public domain; “Made with Natural Earth”), rasterised by scripts/globe/make-globe-map.py to a 2048×1024 equirectangular RGB PNG: red = land, green = borders between countries (outlines near the sea are masked out; coasts come from the red channel). The point-of-view dataset draws India’s boundary as the Government of India does — all of Jammu & Kashmir and Ladakh (including Aksai Chin) and Arunachal Pradesh. Never replace it with the default-view file. To rebuild, see the script’s docstring.

Home card: Natural Earth 1:50m land, rasterised by scripts/globe/make-land-map.py to a 1-bit 2048×1024 PNG inlined in patches/cobe@2.0.1.patch (cobe draws land only, no borders).

Tests

webgl/globe-renderer.test.ts covers the rotation (the focus point faces the viewer; east is right, north is up) and the arc and marker point buffers. The shader itself is not exercised in unit tests; check /analytics/live after changing it.

Was this helpful?