Skip to main content

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),
});
Loading globe…

How keys are matched

countryColors keys are matched case-insensitively, in this order:

  1. Your own countryKey function, if provided
  2. ISO alpha-2 code: "IN", "in"
  3. Numeric id: "356"
  4. 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.

Loading globe…

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.