A WebGL globe and a real data pipeline, in the same repository.
AI Atlas visualizes four dimensions of the global AI landscape — adoption, investment, model development and research. Every figure on screen traces to a row in Postgres, which traces to a dated snapshot of a published dataset. Nothing is illustrative.
- Countries with data
- 194
- Metric rows
- 3,419
- Notable models
- 1,052
- Sources
- 4
Read live from the database on this page load.
The data, and what was wrong with it
Four layers, each verified against source before any interface existed. Of the seven chart slugs originally planned, four were wrong — two had been renamed upstream and returned 404, and two were not country-level at all: their entity column held a category (deal type, or academia versus industry) rather than a country.
Replacements were found by querying Our World in Data’s search endpoint, which returns the available entities for each chart. Testing whether that list contains real country names is a one-request check for whether a chart is country-level. That check now ships as a script, because hand-guessing a slug is how the original four went wrong.
147 countries · Q1 2026
92 countries · 2025
34 countries · 1950–2026
181 countries · 2024
Country identity is the hard part
Four ID systems collide here. Our World in Data publishes ISO3 codes; Epoch AI publishes official ISO 3166 long-form names; the globe’s TopoJSON geometry keys on ISO 3166-1 numeric; curated data uses whatever gets typed.
A single crosswalk resolves all of them — 250 countries, 1,088 indexed aliases, zero collisions — with a token-key fallback so “Korea (Republic of)” matches “Republic of Korea” without hand-writing every permutation.
The ingest throws on a name it cannot resolve. The natural failure mode of a name-matching pipeline is to silently skip what it cannot match, and a map that quietly loses fifteen countries looks completely fine until somebody checks.
The globe is one sphere, not 177 meshes
The obvious approach renders each country as its own mesh. Instead the choropleth is painted into a 2048×1024 canvas and mapped onto a single sphere. The sphere is one draw call regardless of how many countries carry data, and switching layers becomes a GPU crossfade between two textures rather than 177 mesh updates.
Picking runs on the CPU: the raycast hit converts to latitude and longitude, then point-in-polygon with a bounding-box prefilter. Exact, and no ID-buffer readback. That works because the globe never rotates — the camera orbits, so world space and globe space stay identical and a hit needs no rotation unwound.
Everything that was freed up went into atmosphere, bloom and a starfield.
Adding a layer is a data change, not a migration
Metrics are stored tall — one row per country, per metric, per period — with metric metadata in its own table the app reads at runtime. A fifth globe layer is a row in that table plus an ingest source: no migration, no component change. The layer switcher discovers it.
The cost is real and worth naming: every read filters on a metric key, and type safety over those keys lives in a registry rather than in the column list. Ingest rejects any row whose key has no definition, so a typo cannot create a phantom metric.
What this deliberately does not do
There is no overall AI score. It would be the obvious feature. The United Arab Emirates leads adoption, the United States leads investment and model development, China leads research — three dimensions, three different winners. Producing one ranking means deciding how many published papers a billion dollars is worth, and there is no defensible answer. Publishing one anyway would launder an opinion into something that looks like a measurement.
Missing data is never a zero. It has its own colour token and a permanent place in every legend. The development layer covers 34 countries; most of the map is empty because most of the world has never produced a frontier model. That emptiness is the finding.
A planned “fastest rising” view is also on hold. The adoption estimates are modelled, and low-data countries are imputed in regional blocks — twelve West African countries share exactly 10.1%. Rank movement inside such a block is an artefact of the model, not a national trend, so the feature needs an honest answer before it ships.
Stack
Next.js 16 with the App Router and React 19, TypeScript in strict mode with unchecked indexed access enabled — this project parses a lot of CSV. three.js through React Three Fiber for the globe, with custom shaders. Tailwind v4 over a single file of design tokens. Charts are hand-built React and SVG on d3 scales, with no chart library. Neon serverless Postgres through Drizzle. Zustand for scene state, nuqs to mirror it into the URL. Vitest and Playwright.
Country pages are statically generated and revalidated hourly, so the database is never on the critical render path.