v2.0.0

<bmx-map>

A map drawn by the library, with no tile server and nothing fetched: the world's countries are built in (Natural Earth, public domain; boundaries as they are on the ground), or give shapes of your own as GeoJSON or TopoJSON.

32 properties · 3 events · 5 methods · 7 parts

Example

Hover or tab to the map, then use the arrow keys. Click countries to select them; click a group of cities to zoom in.

Zoom to Europe Copy as SVG

Change either side of zero, with names

A diverging scale colours growth and decline differently; labels="auto" names the countries there is room for.

Shapes of your own

Give any GeoJSON or TopoJSON - here, made-up sales territories - and colour them by category.

Show markup
<bmx-map id="ex-map" scale="quantile" steps="6" format="decimal:1" unit=" m" legend-title="Population (millions, approximate)" label="World population" cluster selectable="multiple"></bmx-map>
<p class="note" id="ex-map-out" role="status">Hover or tab to the map, then use the arrow keys. Click countries to select them; click a group of cities to zoom in.</p>

<div class="row" style="gap: 0.75rem; flex-wrap: wrap; align-items: center; margin-block: 0.5rem 1.5rem">
  <label>Projection
    <select id="ex-map-proj">
      <option value="equal-earth" selected>Equal Earth</option>
      <option value="natural-earth">Natural Earth</option>
      <option value="mercator">Mercator</option>
      <option value="equirectangular">Equirectangular</option>
    </select>
  </label>
  <label><input type="checkbox" id="ex-map-grat"> Graticule</label>
  <bmx-button variant="outline" tone="neutral" id="ex-map-europe">Zoom to Europe</bmx-button>
  <bmx-button variant="outline" tone="neutral" id="ex-map-svg">Copy as SVG</bmx-button>
</div>

<h3>Change either side of zero, with names</h3>

<p class="note">A diverging scale colours growth and decline differently; <code>labels="auto"</code> names the countries there is room for.</p>
<bmx-map id="ex-map-growth" scale="diverging" steps="6" format="decimal:1" unit="%" fit-to="DEU,FRA,ESP,POL,SWE,ITA,GBR" labels="auto" legend-title="Population change, 2013-2023 (approximate)" label="Population change in Europe" style="--bmx-map-height: 26rem"></bmx-map>

<h3>Shapes of your own</h3>

<p class="note">Give any GeoJSON or TopoJSON - here, made-up sales territories - and colour them by category.</p>
<bmx-map id="ex-map-own" projection="equirectangular" scale="category" label="Sales territories" labels="all" style="--bmx-map-height: 18rem"></bmx-map>

<script type="module">
  await customElements.whenDefined('bmx-map');
  const pop = {
    CHN: 1410, IND: 1428, USA: 335, IDN: 277, PAK: 240, NGA: 224, BRA: 216, BGD: 173, RUS: 144, MEX: 128, ETH: 126, JPN: 124, PHL: 117,
    EGY: 113, COD: 102, VNM: 99, IRN: 89, TUR: 85, DEU: 84, THA: 72, GBR: 68, FRA: 68, TZA: 67, ZAF: 60, ITA: 59, KEN: 55, MMR: 54,
    COL: 52, KOR: 52, UGA: 48, SDN: 48, ESP: 48, ARG: 46, DZA: 46, IRQ: 45, AFG: 42, POL: 37, CAN: 39, MAR: 37, SAU: 37, UKR: 37,
    AGO: 36, UZB: 35, PER: 34, MYS: 34, MOZ: 34, GHA: 34, YEM: 34, NPL: 31, VEN: 28, MDG: 30, CMR: 28, AUS: 27, PRK: 26, NER: 27,
    LKA: 22, MLI: 23, CHL: 20, KAZ: 20, ROU: 19, NLD: 18, SEN: 18, ECU: 18, GTM: 18, SWE: 10.5, NOR: 5.5, FIN: 5.6, IRL: 5.3,
    NZL: 5.2, PRT: 10.4, GRC: 10.4, CZE: 10.9, HUN: 9.6, BLR: 9.2, AUT: 9.1, CHE: 8.8, ISR: 9.8, BOL: 12.4, CUB: 11.2, TUN: 12.5,
    BEL: 11.7, DNK: 5.9, MNG: 3.4, GRL: 0.06, ISL: 0.38,
  };
  const map = document.getElementById('ex-map');
  map.data = pop;
  map.markers = [
    { label: 'London', lon: -0.13, lat: 51.51, value: 9 },
    { label: 'Paris', lon: 2.35, lat: 48.86, value: 11 },
    { label: 'Brussels', lon: 4.35, lat: 50.85, value: 2.1 },
    { label: 'Amsterdam', lon: 4.9, lat: 52.37, value: 2.5 },
    { label: 'New York', lon: -74.0, lat: 40.71, value: 19 },
    { label: 'Tokyo', lon: 139.69, lat: 35.69, value: 37 },
    { label: 'Mumbai', lon: 72.88, lat: 19.08, value: 21 },
    { label: 'São Paulo', lon: -46.63, lat: -23.55, value: 22 },
    { label: 'Lagos', lon: 3.38, lat: 6.52, value: 15 },
    { label: 'Sydney', lon: 151.21, lat: -33.87, value: 5.3 },
  ];
  map.lines = [
    { from: [-0.13, 51.51], to: [-74.0, 40.71] },
    { from: [-0.13, 51.51], to: [139.69, 35.69] },
    { from: [-0.13, 51.51], to: [151.21, -33.87] },
  ];
  const out = document.getElementById('ex-map-out');
  map.addEventListener('bmxMapSelect', e => (out.textContent = e.detail.ids.length ? `Selected: ${e.detail.ids.join(', ')}` : 'Nothing selected.'));
  map.addEventListener('bmxMapItemClick', e => {
    if (e.detail.kind === 'marker') out.textContent = `${e.detail.name}: about ${e.detail.value} million people in the city.`;
  });
  document.getElementById('ex-map-proj').addEventListener('change', e => (map.projection = e.target.value));
  document.getElementById('ex-map-grat').addEventListener('change', e => (map.graticule = e.target.checked));
  document.getElementById('ex-map-europe').addEventListener('click', () => map.zoomTo(['FRA', 'DEU', 'POL', 'ESP', 'ITA', 'GBR', 'SWE']));
  document.getElementById('ex-map-svg').addEventListener('click', async () => {
    const svg = await map.toSvg();
    try {
      await navigator.clipboard.writeText(svg);
      out.textContent = `Copied ${Math.round(svg.length / 1024)} KB of SVG.`;
    } catch {
      out.textContent = 'The clipboard was not available.';
    }
  });

  document.getElementById('ex-map-growth').data = {
    DEU: 4.1, FRA: 3.6, GBR: 6.9, ESP: 3.5, ITA: -1.6, POL: -1.3, ROU: -4.9, BGR: -11.2, GRC: -5.9, PRT: -0.5, IRL: 13.7, NLD: 6.3,
    BEL: 6.2, SWE: 8.9, NOR: 9.1, FIN: 2.9, DNK: 5.4, AUT: 7.4, CHE: 10.6, CZE: 2.1, SVK: 0.4, HUN: -2.2, SRB: -5.1, HRV: -9.4,
    LTU: -7.6, LVA: -7.9, EST: 1.6, UKR: -14.0, BLR: -2.6, BIH: -8.0, ALB: -2.4, MKD: -1.1, SVN: 2.0, MDA: -8.5,
  };

  const box = (x, y, w, h) => [[[x, y], [x + w, y], [x + w, y + h], [x, y + h], [x, y]]];
  const territories = {
    type: 'FeatureCollection',
    features: [
      { type: 'Feature', id: 'N1', properties: { name: 'North 1' }, geometry: { type: 'Polygon', coordinates: box(0, 20, 20, 10) } },
      { type: 'Feature', id: 'N2', properties: { name: 'North 2' }, geometry: { type: 'Polygon', coordinates: box(20, 20, 25, 10) } },
      { type: 'Feature', id: 'C1', properties: { name: 'Central' }, geometry: { type: 'Polygon', coordinates: box(0, 8, 45, 12) } },
      { type: 'Feature', id: 'S1', properties: { name: 'South West' }, geometry: { type: 'Polygon', coordinates: box(0, 0, 15, 8) } },
      { type: 'Feature', id: 'S2', properties: { name: 'South East' }, geometry: { type: 'Polygon', coordinates: box(15, 0, 30, 8) } },
    ],
  };
  const own = document.getElementById('ex-map-own');
  own.regions = territories;
  own.data = [
    { id: 'N1', value: 'Ahead', label: 'Owner: Priya' },
    { id: 'N2', value: 'On track', label: 'Owner: Sam' },
    { id: 'C1', value: 'On track', label: 'Owner: Leo' },
    { id: 'S1', value: 'Behind', label: 'Owner: Ana' },
    { id: 'S2', value: 'Ahead', label: 'Owner: Kofi' },
  ];
</script>

Colour regions by value (a choropleth: equal steps, quantiles, your own breaks, diverging either side of a middle, or categories) with a legend; put markers on places, sized by value and grouped when they crowd; draw routes along great circles. Pan and zoom with the pointer, the wheel, a pinch or the keyboard; select regions; read every value as a table.

map.data = [{ id: 'GBR', value: 67.7e6 }, { id: 'FR', value: 68.2e6 }];

Keyboard: the map is one stop. The arrow keys move from region to region (and marker) in that direction, saying each name and value; Enter or Space selects; Shift and the arrows pan; + and - zoom; 0 shows the whole map.

Properties

PropertyAttributeTypeDefaultDescription
antarctica antarctica boolean false Draw Antarctica (the built-in world).
cluster cluster boolean false Group markers that crowd together; a group opens when it is chosen.
data data BmxMapDatum[] | Record<string, unknown> | string — Values for regions: [{ id, value, label, color }], or { "GBR": 67.7, "FR": 68.2 }, as a property or JSON.
fitTo fit-to string 'all' What the view starts on: all (default), data (the regions with values and the markers), or ids: "GBR,FRA,DEU".
format format BmxChartNumberFormat | string — How values are written: percent, currency:EUR, decimal:1, compact, or the object.
graticule graticule boolean false Draw lines of latitude and longitude.
idField id-field string 'id' The field of a record holding the region's id.
idProperty id-property string — The property holding a region's id in your shapes.
label label string — What the map shows: its accessible name.
labels labels 'none' | 'auto' | 'all' 'none' Names on the map: none (default), auto (regions large enough at the zoom, and markers), or all.
legend legend 'bottom' | 'none' 'bottom' Where the legend goes: bottom (default) or none.
legendTitle legend-title string — A heading for the legend.
lines lines BmxMapLine[] | string — Routes: [{ from, to, label }], each end [longitude, latitude] or an id.
locale locale string — A BCP 47 locale for numbers. Default: the page's lang.
markers markers BmxMapMarker[] | string — Places: [{ lon, lat, value, label, color }], as a property or JSON.
maxZoom max-zoom number 12 How far in the reader can zoom.
mid mid number 0 The middle of a diverging scale.
nameProperty name-property string — The property holding a region's name in your shapes.
projection projection BmxMapProjection 'equal-earth' equal-earth (default), natural-earth, mercator or equirectangular.
regions property only unknown — Shapes of your own: GeoJSON or TopoJSON, as a property or JSON. The world's countries, by default.
scale scale BmxMapScaleKind 'quantize' How values become colours: quantize (equal steps, default), quantile, threshold, diverging or category.
selectable selectable 'none' | 'single' | 'multiple' 'none' Regions the reader can select: none (default), single or multiple.
selected property only string[] [] The selected regions' ids. Changes write back here.
src src string — A URL to read shapes from (GeoJSON or TopoJSON), on the page's own server.
steps steps number 5 How many classes, for quantize, quantile and diverging.
strings strings Partial<BmxMapStrings> | string — Replacements for the map's wording, as a property or JSON.
tableView table-view boolean true Offer the table view.
thresholds thresholds number[] | string — The breaks of a threshold scale: [10, 50, 100] or "10,50,100".
topologyObject topology-object string — The TopoJSON object to draw, when there are several.
unit unit string — Written after every value: t, %.
valueField value-field string 'value' The field of a record holding its value.
zoomable zoomable boolean true The reader can pan and zoom.

Events

EventDetailDescription
bmxMapItemClick BmxMapItemDetail A region, marker or group was clicked or chosen with the keyboard.
bmxMapSelect BmxMapSelectDetail Regions were selected or unselected.
bmxMapViewChange BmxMapViewDetail The view was panned or zoomed.

Methods

MethodSignatureDescription
centerOn centerOn(center: BmxMapPoint, zoom?: number) => Promise<void> Centres the view on a place, at a zoom (the current one by default).
clearSelection clearSelection() => Promise<void> Unselects every region.
getView getView() => Promise<BmxMapViewDetail> The view: how far in, and the middle as [longitude, latitude].
toSvg toSvg() => Promise<string> The map as a standalone SVG document, its colours written in.
zoomTo zoomTo(target: string[] | "data" | null) => Promise<void> Shows regions (ids), the regions with data and the markers (data), or the whole map (null).

CSS shadow parts

PartDescription
controls the zoom and table buttons.
legend the legend.
map the drawing.
marker a marker.
region a region's shape.
table the table view.
tooltip the name and value under the pointer.

CSS custom properties

PropertyDescription
--bmx-map-border The lines between regions.
--bmx-map-color-0 The first category. Colours 0 to 9 are used in turn.
--bmx-map-color-1 Category 2.
--bmx-map-color-2 Category 3.
--bmx-map-color-3 Category 4.
--bmx-map-color-4 Category 5.
--bmx-map-color-5 Category 6.
--bmx-map-color-6 Category 7.
--bmx-map-color-7 Category 8.
--bmx-map-color-8 Category 9.
--bmx-map-color-9 Category 10.
--bmx-map-height How tall the map is. Default: as tall as the projection needs at the width.
--bmx-map-high The highest class.
--bmx-map-land A region with no value.
--bmx-map-low The lowest class of a choropleth.
--bmx-map-marker A marker.
--bmx-map-mid The middle of a diverging scale.
--bmx-map-negative The low end of a diverging scale.
--bmx-map-ocean Behind the land (with graticule, inside the map's outline).
--bmx-map-route A route.
--bmx-map-selected A selected region's outline.