Skip to content
Documentation menu
docs / examples / choropleth
Example

Choropleth

Polygons coloured by a number on each one, with a graduated ramp you can read at a glance and white outlines that keep neighbouring areas apart. The same pattern fits wards, postcode districts, sales territories or delivery zones.

ts
import { createMap, DataLayer } from "@mapmap/maps";

const map = createMap({
  container: "map",
  apiKey: "snk_...",
  attributionPosition: "bottom-left",
  center: [-0.12, 51.5],
  zoom: 10.3,
});
await map.whenReady();

// Sample data: a grid of cells over London, each with a value. Your own
// polygons work the same: data: "https://cdn.example.com/wards.geojson".
const cells: GeoJSON.Feature[] = [];
for (let x = 0; x < 18; x++) {
  for (let y = 0; y < 12; y++) {
    const west = -0.42 + x * 0.034, south = 51.36 + y * 0.025;
    const d = Math.hypot(x - 9, (y - 6) * 1.4);
    cells.push({
      type: "Feature",
      properties: { cell: x * 100 + y, value: Math.round(9000 / (1 + d * d * 0.3)) },
      geometry: {
        type: "Polygon",
        coordinates: [[
          [west, south], [west + 0.034, south], [west + 0.034, south + 0.025],
          [west, south + 0.025], [west, south],
        ]],
      },
    });
  }
}

// A fill and an outline at slot "middle", coloured along the stops.
new DataLayer(map, {
  data: { type: "FeatureCollection", features: cells },
  promoteId: "cell",
  style: {
    simple: { opacity: 0.7, width: 0.6, outlineColor: "#ffffff" },
    graduated: {
      property: "value",
      stops: [
        [0, "#eff3ff"],
        [1000, "#bdd7e7"],
        [3000, "#6baed6"],
        [6000, "#3182bd"],
        [9000, "#08519c"],
      ],
    },
  },
  popup: (f) => {
    const el = document.createElement("div");
    el.textContent = "Value " + String(f.properties?.value);   // text, never HTML
    return el;
  },
});

Worth knowing

  • graduated blends linearly between the stops, which must be strictly ascending (DataLayer throws on stops that are not). For hard classes, draw the layer yourself with map.addPersistentLayer and a step expression.
  • Keep the value you colour on as a top-level number in properties. MapLibre reads nested objects as JSON strings, so properties.stats.value cannot be styled on.
  • Hover and click highlights are on by default (feature-state, so no setData). promoteId names a unique property so the highlight lands on one cell.