Guide

JavaScript Map Chart

A thematic map, not a street map. Which layer suits your data, the normalisation step most count maps skip, and the join failure behind almost every grey region.

Choropleth DefaultsOpen in new tab

Built with ApexMaps

A map chart plots data onto geography: regions shaded by a value, circles sized by a quantity, points placed at coordinates, lines drawn between places. It is a thematic map rather than a street map, and it answers "where" for numbers you already have, which is a different job from showing someone the way to the airport.

ApexMaps is the one in this family. It ships boundary data for 26 geometry packs, so a working world map needs no GeoJSON hunting:

npm install apexmaps

import ApexMaps from 'apexmaps'

const map = new ApexMaps(document.getElementById('map'), {
  geo: { map: 'world/countries' },
  series: [
    {
      type: 'choropleth',
      joinBy: ['iso_a3', 'code'],
      data: [
        { code: 'USA', value: 3410 },
        { code: 'BRA', value: 1180 },
        { code: 'IND', value: 2260 },
      ],
    },
  ],
})

map.render()

Which layer does your question need?

This is the decision the docs cannot make for you, because each layer has its own page and none of them compares. Pick by the shape of what you are showing, not by what looks best in a screenshot.

Your dataLayerWhy
One value per region, comparable across regionschoroplethShading is read as intensity. Requires a rate, not a count: see below.
A magnitude at a place, where size is the messagebubbleCircle area does not inherit the size of the polygon under it, so a small dense country still reads large.
Individual locations, hundreds to thousandsmarkerOne mark per row. Add cluster when they overlap into a blob.
So many points that individual ones stop meaning anythinghexbinBins density into equal-area cells, which is the honest rendering of overlapping points.
Movement or a relationship between two placesarc or lineA flow between origin and destination, optionally with directional beads.
Regions compared without their real areas distorting itHex tile layoutEqual-size tiles, so Rhode Island and Texas carry equal visual weight.

The first two rows are the choice people get wrong most often, and the fix has a name.

A choropleth of counts is a population map

Shade regions by a raw count and you mostly redraw where people already live: whichever region has the most people wins nearly every category, and the map stops saying anything else. normalizeBy divides the value by another field on the same row before classifying:

{
  type: 'choropleth',
  joinBy: ['iso_a3', 'code'],
  normalizeBy: 'population',      // the legend retitles to "Cases (per population)"
  data: [
    { code: 'USA', value: 3410, population: 331_900_000 },
    { code: 'BRA', value: 1180, population: 214_300_000 },
  ],
}

The legend retitles itself whenever normalizeBy is set, so a rate map and a count map cannot be confused at a glance. ApexMaps also notices the shape: a series of large integers with no normalizeBy gets a development-mode diagnostic, on the grounds that data shaped like that is a raw count nine times out of ten. It is a note rather than an error, because a count map is sometimes exactly right.

When magnitude itself is the finding rather than a rate, use a bubble series instead. That is the row above, and it is the same insight from the other direction: a circle's area is independent of the polygon beneath it.

Why are half my regions grey?

Because the join failed, which is the dominant failure mode of every map chart and has nothing to do with the chart library. Your data says "Ivory Coast" and the geometry says "Côte d'Ivoire". A FIPS code lost its leading zero in a spreadsheet. You have ISO-2 and the pack keys on ISO-3.

What is unusual here is that you get told. Most implementations render the unmatched features in no-data grey and leave the map to explain itself. ApexMaps reports what happened, through a development-mode diagnostic and map.diagnoseJoin(), including which field it picked when you did not say:

joinBy: 'name'                          // same field name on both sides
joinBy: ['iso_a3', 'code']              // [geometryProperty, dataKey]
joinBy: { geo: 'iso_a3', data: 'code' } // the same, spelled out

Leave joinBy off and both sides are guessed: each geometry pack states its own recommended key, and the data side is scanned for id, key, code, iso, iso_a3, fips, geoid, hc-key, region, state, country, name and similar, first match winning. Treat that as a convenience for a first render, not a guarantee, and read back what it chose.

Matching is exact by default, deliberately: silently guessing turns an obvious failure into a plausible wrong answer, and nobody double-checks a map that looks fine. fuzzyJoin: true opts into normalized and alias matching, and every substitution it makes is still reported so the convenience stays auditable. The joins guide has the full behaviour.

When is a map the wrong chart?

Geography is seductive and often the least informative axis available.

What you are actually showingReach for
A value that varies by place, and place is the questionA map. This is the case.
Ranking a dozen regionsA bar chart. A map makes the reader estimate and compare shaded areas to recover an order a bar chart just states.
Change over time in a few regionsA line chart, with a map only if the spatial pattern is itself the finding.
Data about countries where only a handful have valuesA bar chart or a table. A world map with six shaded countries is mostly empty ocean.
Sales by "region" meaning a business territory, not a placeA bar chart, unless the territories are contiguous and drawn correctly.
Precise values people need to read offA table, or a map plus a table. Nobody reads a number off a fill colour.

What is free and what is licensed

ApexMaps draws the line at one rule: a map that answers a question needs no licence, and a map that becomes an application does. Community is free for individuals, non-profits, educators and organizations under $2M USD in annual revenue; at or above that a Commercial licence applies. Nothing here is open source, and source published on GitHub is not an open licence.

Free, alwaysLicensed
choropleth, bubble and marker series, and the automatic basemapPoint clustering (cluster) and density (hexbin)
All 13 built-in projectionsProjections you register yourself
The geometry registry, all 26 packs, provenance and attributionDrilldown and its breadcrumb
Tooltips, legends, labels, data labels, states, themesHex tile layouts
Zoom, pan, pinch, hover, click and box selection, the camera APIEditorial annotations
Joins, fuzzyJoin, and the join diagnosticsarc and line route series, including flow beads
Scales, palettes, size legends, responsive rulesLinked selection across maps (link: { group })
Globe rotation and on-screen zoom controlsPattern fills and image fills
PNG and SVG exportStory mode
The accessibility layer

Two of those are free by decision rather than omission. fuzzyJoin is free because cleaning up someone else's data is the cost of using real data, not a premium experience. Accessibility is never gated in any tier.

Every licensed feature works in full without a key, watermarked, so you can evaluate it in your own application with your own data. One key covers the whole Apex family, though each library needs its own setLicense() call. The licensing guide and the pricing page have the rest.

Note that the licence covers the software, not the geography. Boundary data ships from third parties under its own terms, readable at runtime through ApexMaps.mapMeta(id), and required attribution renders on the map automatically.

Putting a map next to the rest of a dashboard

Clicking a region to filter the tables and charts around it is the interaction that makes a map useful rather than decorative, and box selection is free. The flagship showcase wires exactly that, across three products:

Sales analytics dashboard: map, grid and charts cross-filtered

See the pieces running

Reference documentation

Frequently Asked Questions

When should I use a choropleth instead of a bubble map?

A choropleth when the value is comparable across regions and expressed as a rate; a bubble map when the magnitude itself is the message. Shading is read as intensity and inherits the size of the polygon underneath it, so a large sparse region looks more significant than a small dense one. A circle's area does not, which is why bubbles suit counts and choropleths suit rates.

Why does my choropleth just look like a population map?

Because it is one. Shading by a raw count means the region with the most people wins nearly every category. Set `normalizeBy` to a field on the same row, usually population, so the value is classified as a rate; the legend retitles itself to say so. ApexMaps also flags large-integer series with no `normalizeBy` in development, since that data shape is a raw count nine times out of ten.

Why are some regions on my map grey?

The data did not join to the geometry. Your rows say "Ivory Coast" and the boundary file says "Côte d'Ivoire", or a FIPS code lost its leading zero, or you have ISO-2 codes against a pack keyed on ISO-3. Set `joinBy` explicitly and read `map.diagnoseJoin()`, which reports what matched, what did not, and which field was auto-detected. `fuzzyJoin: true` opts into alias matching and still reports every substitution.

Is ApexMaps free to use?

Community is free for individuals, non-profits, educators and organizations under $2M USD in annual revenue, and it includes the choropleth, bubble and marker series, all 13 projections, all 26 geometry packs, joins, scales, export and the accessibility layer. Clustering, hexbin, drilldown, arcs and routes, linked selection, pattern and image fills are licensed. Nothing is open source: published source is not an open licence.

Do I need to supply my own GeoJSON?

No. ApexMaps ships 26 geometry packs covering world countries and coastline plus admin-1 regions, each with its own recommended join key and its own provenance and attribution, which renders automatically. You can register your own geometry as well, and the licence covers the software rather than the boundary data, which travels under its publishers' terms.

Related

Start with ApexMaps

26 geometry packs included, so a working world map needs no GeoJSON hunting.

Get started