GeoMap

Chart
A world map with a bubble per place, its area proportional to its count, over coastlines and country borders precomputed at build time. Optionally zoomable, merging crowded places into one bubble that splits as you zoom in.

Playground

Every control sets a real prop of GeoMapProps. The JSX below updates as you go.
Active installs
Props
compact
boolean
zoomable
boolean
JSX · GeoMapProps
<GeoMap zoomable />

Variants

4 examples from src/charts/geo-map/geo-map.md, each rendered live from its own JSX.
Edge nodes
Compact
<GeoMap
compact
title="Edge nodes"
points={[
{ label: "Ashburn", lat: 39.0438, lng: -77.4874, count: 42 },
{ label: "Frankfurt", lat: 50.1109, lng: 8.6821, count: 36 },
{ label: "Mumbai", lat: 19.076, lng: 72.8777, count: 18 },
{ label: "Sydney", lat: -33.8688, lng: 151.2093, count: 9 },
]}
/>
Sessions by city
Zoomable
<GeoMap
zoomable
title="Sessions by city"
points={[
{ label: "San Francisco", lat: 37.7749, lng: -122.4194, count: 4820 },
{ label: "Oakland", lat: 37.8044, lng: -122.2712, count: 1100 },
{ label: "San Jose", lat: 37.3382, lng: -121.8863, count: 2400 },
{ label: "Sacramento", lat: 38.5816, lng: -121.4944, count: 700 }
]}
/>
Support tickets
Inspected
<GeoMap
title="Support tickets"
defaultSelected={1}
points={[
{ label: "Toronto", lat: 43.6532, lng: -79.3832, count: 310 },
{ label: "Dublin", lat: 53.3498, lng: -6.2603, count: 940 },
{ label: "Cape Town", lat: -33.9249, lng: 18.4241, count: 220 },
{ label: "Seoul", lat: 37.5665, lng: 126.978, count: 470 }
]}
/>
Bandwidth served
Formatted counts
<GeoMap
title="Bandwidth served"
formatValue={(v) => `${v} TB`}
points={[
{ label: "Los Angeles", lat: 34.0522, lng: -118.2437, count: 88 },
{ label: "Amsterdam", lat: 52.3676, lng: 4.9041, count: 64 },
{ label: "Nairobi", lat: -1.2921, lng: 36.8219, count: 12 },
{ label: "Osaka", lat: 34.6937, lng: 135.5023, count: 39 }
]}
/>

Anatomy

Active installs
1
Map
2
Point
3
Region outline
4
Zoom control
Dimmed parts appear only in some states or variants.

Props

16 props on GeoMapProps, grouped as they are declared in source. Required props are marked *.
Prop
Type
Default
Description
Props
2 props
points *
GeoMapPoint[]
–
The places to plot; every bubble takes the primary token.
title
string
–
Optional heading shown above the map.
Density
14 props
compact
boolean
false
detailed
boolean
false
Bordered geography panel with individually outlined place bubbles and an inline readout.
plain
boolean
false
Strip the card surface for nesting inside an existing card (mirrors Stats).
formatValue
(v: number) => string
–
Formats the flagged and accessible counts (data formatting, not styling).
selected
number | null
–
Press-to-inspect: the selected point index (controlled). Pass null for none.
defaultSelected
number
–
Press-to-inspect: the initially selected point (uncontrolled).
onSelect
(index: number | null) => void
–
Fired when a press selects a point (or clears it with null).
zoomable
boolean
false
Zoom and pan the map, and aggregate crowded places into one bubble that splits into its members as the map is driven in. The wheel zooms about the pointer, two fingers pinch, a drag pans once zoomed, and the zoom controls and arrow keys give the same reach without a pointer.
zoom
number
–
The zoom factor, 1 (the whole world) upward (controlled). Needs zoomable.
defaultZoom
number
–
The zoom factor the map opens at (uncontrolled). Needs zoomable.
onZoomChange
(zoom: number) => void
–
Fired whenever the zoom factor changes, by gesture, control or key.
onSelectPlaces
(indices: number[]) => void
–
Fired alongside onSelect with EVERY point index inside the pressed bubble. A single place reports one index and an aggregated bubble reports all of its members, so onSelect's own meaning never silently changes.
testID
string
–
E2E hook forwarded to the root element.
style
LayoutStyle
–
Composition within a parent only, never a restyle hook and never a width: the parent layout container provides the bounds.

Accessibility and keyboard

Keyboard
Tab
Focuses the plot
ArrowLeft
ArrowRight
Steps through points when inspectable
Escape
Clears the inspected point
Semantics
Roles
group
image
img
ARIA on web
aria-label
From the docs
Single-identity encoding, so there is no tone axis and no legend. Press a bubble to flag its label and count (the others dim); the accessible name carries the biggest places with their values, because a screen reader user cannot see bubbles.

Platform skins

The playground's props on each platform: same tree, native shapes, type and touch sizes.
Edge nodes
Web
control 10 · field 10 · card 16 · Instrument Sans
Edge nodes
iOS
capsule controls · field 8 · card 12 · SF Pro · 44 pt
Edge nodes
Android
Material 3 stadium · field 4 · card 12 · Roboto · 48 dp

Usage

<GeoMap
title="Active installs"
points={[
{ label: "New York", lat: 40.7128, lng: -74.006, count: 6310 },
{ label: "London", lat: 51.5072, lng: -0.1276, count: 5170 },
{ label: "Bengaluru", lat: 12.9716, lng: 77.5946, count: 3890 },
{ label: "Sydney", lat: -33.8688, lng: 151.2093, count: 1260 }
]}
/>
The land silhouette and the shared country borders are two paths precomputed from Natural Earth 1:50m (no runtime fetch, no map dependency, no tiles), and each point is a circle at its projected coordinate whose AREA is proportional to its count, so doubling the count doubles the disc rather than its width.
Add zoomable and the wheel zooms about the pointer, two fingers pinch, a drag pans, and places too close together to draw separately merge into one bubble carrying their summed count, splitting into their members as you zoom in.
Single-identity encoding, so there is no tone axis and no legend. Press a bubble to flag its label and count (the others dim); the accessible name carries the biggest places with their values, because a screen reader user cannot see bubbles.

Tokens

Read from the playground render. Values follow the scheme and the ion hue.
--fd
--fm
--ft
--ion
--ion-glow
--ion-soft
--ln
--ln2
--sf
--sf4
--sh-sm
--tx3

Related components