Coverage dashboard
Revenue, campaign spend or account coverage by country: with a legend, hover, click-to-drill and an animated timeline.
The code
import { createGlobe, colorScale } from "canvas-globe";
const revenue = { IN: 940, US: 880, GB: 620, DE: 540, JP: 430, BR: 280 };
const scale = colorScale([0, 1000], ["#eff3ff", "#08306b"]);
const globe = createGlobe(canvas, {
scene: "coverage",
countryColors: Object.fromEntries(
Object.entries(revenue).map(([iso, value]) => [iso, scale(value)]),
),
legend: {
title: "Revenue",
scale: { domain: [0, 1000], range: ["#eff3ff", "#08306b"] },
},
tooltip: (shape, kind) =>
kind === "country"
? `${shape.name}: ${revenue[shape.iso] ? `$${revenue[shape.iso]}k` : "no data"}`
: shape.label,
onCountryClick: (shape) => drillInto(shape.iso),
});
Data without a colour of its own
Returning null from countryColor falls back to the theme's land colour, which is how you say
"no data" without inventing a value:
countryColor: (shape) => {
const value = revenue[shape.iso];
return value == null ? null : scale(value);
},
Say so in the legend too: an unlabelled grey is easy to misread as zero.
Drill-down
onCountryClick plus focusOn gives a two-level dashboard with no extra data:
onCountryClick: (shape) => {
globe.focusOn(shape.iso, { isolate: false, dim: 0.12, outlineWidth: 2 });
showRegionPanel(shape.iso);
},
Call clearFocus() to zoom back out. Sub-national geometry is not bundled: pass your own via
world if you need state-level detail.
Adding volume on top
A choropleth shows rate; markers or spikes show volume. Layering both answers "where is it dense, and where is it valuable" in one graphic.
createGlobe(canvas, {
countryColors,
spikes: { height: 0.24 },
markers: offices,
});
Growth over time
Give each marker a date and animate the range:
const markers = deals.map((d) => ({
lat: d.lat, lon: d.lon, count: d.value, date: d.closedAt,
}));
globe.setMarkers(markers);
globe.playTimeline({ duration: 8000, loop: true });
Exporting for the board deck
globe.exportImage({ preset: "wide" }); // 1920 × 1080 slide
await globe.exportBlob({ preset: "og" }); // for an email or a link preview
Keeping keys straight
countryColors matches ISO alpha-2, then numeric id, then name: case-insensitively. If your
warehouse uses alpha-3 or an internal id, map it once:
countryKey: (shape) => alpha3For[shape.iso] ?? shape.iso,