A hex tile map is a cartogram, not geography: every region becomes one equal hexagon at a
hand-authored position, so Rhode Island is as legible as Texas and land area stops deciding
how loud a value looks. Built in a chart core it is a grid of tiles, which leaves the
reader supplying 51 hex coordinates alongside every value. Here it is a
layout: geometry keyed exactly the way the boundary pack is keyed, so one
dataset and one joinBy serve both, and the scale, legend, tooltip, labels,
selection and keyboard navigation are the machinery that already exists.
geo: { map: 'us', layout: 'hex' }geo: { map: 'us' } · the same series, same scalePress it and watch the states, not the map. Toggling the layout morphs each region between the two representations rather than swapping them, which is the answer to the one real problem with a cartogram: a reader who cannot navigate a honeycomb can navigate the map it grew out of, and watching Texas walk to its cell is what tells them which cell is Texas. Each outline is resampled at equal arc length, rotated to its closest match and walked across; Alaska travels furthest, from the corner it is inset into to the corner the grid parks it in. Labels step out for the flight, because they are placed against the geometry that has already arrived.
Illustrative data. A smooth function of longitude and latitude, not a real
indicator. The layout is hand-authored and verified against the boundary pack it claims to
represent: npm run check:layout scores order against state centroids and
adjacency against shared TopoJSON arcs.
A layout is a diagram, and it knows that about itself. Every one of these was set by hand while prototyping, which is the evidence that none of them belongs to the caller:
identity projection. The coordinates are a grid, so
projecting them would be meaningless. The pack recommends it the same way
us/states@10m recommends albersUsa.
Each is a default, not a rule: pass geo.projection,
dataLabels.field or interaction.zoom.enabled and yours wins.
…
…
A layout has no canonical answer, so each of these is a judgement about which real adjacencies matter most, scored against the boundary pack it claims to represent. Each one has a shape that fights the grid, and the interesting part is which compromise it forces:
map: 'au', layout: 'hex' · 8 statesmap: 'ca', layout: 'hex' · 13 provincesmap: 'de', layout: 'hex' · 16 Laendermap: 'br', layout: 'hex' · 27 statesmap: 'jp', layout: 'hex' · 47 prefecturesmap: 'eu', layout: 'hex' · 37 NUTS0Europe's set is not the EU. NUTS is a statistical classification, so level 0 also covers the United Kingdom, the four EFTA countries and the five candidate countries. Boundaries © EuroGeographics; the cell positions are ours.
registerLayout takes a table of key → [col, row], row 0 north and
col 0 west, keyed by whatever field your boundary pack joins on. There is no canonical
layout for any country, so curating the table is the work: it is a judgement about
which real adjacencies matter most, and the numbers below are how you tell whether the
judgement was good.
ApexMaps.registerLayout('demo/quad@hex', {
keyField: 'code',
cells: { NW: [0, 0], NE: [1, 0], SW: [0, 1], SE: [1, 1] },
names: { NW: 'North west', NE: 'North east',
SW: 'South west', SE: 'South east' },
})
// geo: { map: 'demo/quad@hex' }
The generator rejects a malformed pack rather than drawing it: two regions on one cell,
a cell that is not [col, row], or an offset convention that contradicts
the orientation. All three render something plausible and wrong, which is worse than a
throw.