Choropleth
Colour countries by value. The bundled geometry carries an ISO alpha-2 code, a numeric id and a name for each country, so most data joins without any mapping work.
import { createGlobe, colorScale } from "canvas-globe";
const visits = { IN: 940, US: 720, GB: 480, JP: 300, BR: 260 };
const scale = colorScale([0, 1000], ["#e0f2fe", "#0369a1"]);
createGlobe(canvas, {
mode: "map",
countryColors: Object.fromEntries(
Object.entries(visits).map(([iso, value]) => [iso, scale(value)]),
),
onCountryClick: (shape) => console.log(shape.iso, shape.name),
});
How keys are matched
countryColors keys are matched case-insensitively, in this order:
- Your own
countryKeyfunction, if provided - ISO alpha-2 code:
"IN","in" - Numeric id:
"356" - Country name:
"India","india"
countryColors: { IN: "#f00", Brazil: "#0f0", 250: "#00f" }
If your data uses an internal region ID or another key, map it yourself:
countryKey: (shape) => myRegionIdFor(shape.iso),
A function instead of a map
countryColor receives each country shape and returns a colour or null. It takes precedence over
countryColors.
createGlobe(canvas, {
countryColor: (shape) => {
const value = data[shape.iso];
return value == null ? null : scale(value);
},
});
Returning null falls back to the theme's land colour, which is how you show "no data".
Colour scales
colorScale(domain, range) builds a linear ramp. Multi-stop scales work too.
const scale = colorScale([0, 500, 1000], ["#f7fbff", "#6baed6", "#08306b"]);
scale(0); // "rgb(247,251,255)"
scale(250); // interpolated
scale(9999); // clamped to the last stop
scale("n/a"); // null: not a number
A distinct colour per country
countryColors: "auto" gives the printed-atlas look. It runs a greedy graph colouring over country
adjacency, so no two neighbours ever share a fill.
Supply your own palette with countryPalette:
createGlobe(canvas, {
countryColors: "auto",
countryPalette: ["#e8c39e", "#a8c8a0", "#e5b8b8", "#c5b8dd"],
});
Interaction
createGlobe(canvas, {
onCountryHover: (shape, position) => { /* shape is null on leave */ },
onCountryClick: (shape) => drillInto(shape.iso),
});
Adding either handler enables country hit testing and the hover highlight. Hit testing un-projects the pointer to a coordinate and ray-casts against the polygons, so it works identically on the globe, on every flat projection, and at any zoom.
You can also test a point directly:
globe.countryAt(x, y); // → shape or null
globe.unproject(x, y); // → [lon, lat] or null
A legend
legend: {
title: "Visits",
scale: { domain: [0, 1000], range: ["#e0f2fe", "#0369a1"] },
position: "bottom-left",
}
Or discrete swatches:
legend: {
title: "Tier",
items: [
{ color: "#0369a1", label: "Enterprise" },
{ color: "#7bbfe3", label: "Growth" },
],
}
India
India is drawn on the Survey of India boundary, as part of the bundled country geometry rather than
as an overlay. It joins, colours, clicks and labels like every other country: key it as IN, 356
or India, and countryColors: "auto" gives it a fill that does not clash with its neighbours.
See the note on India's boundary for where the geometry comes from.