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 data | Layer | Why |
|---|---|---|
| One value per region, comparable across regions | choropleth | Shading is read as intensity. Requires a rate, not a count: see below. |
| A magnitude at a place, where size is the message | bubble | Circle area does not inherit the size of the polygon under it, so a small dense country still reads large. |
| Individual locations, hundreds to thousands | marker | One mark per row. Add cluster when they overlap into a blob. |
| So many points that individual ones stop meaning anything | hexbin | Bins density into equal-area cells, which is the honest rendering of overlapping points. |
| Movement or a relationship between two places | arc or line | A flow between origin and destination, optionally with directional beads. |
| Regions compared without their real areas distorting it | Hex tile layout | Equal-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 showing | Reach for |
|---|---|
| A value that varies by place, and place is the question | A map. This is the case. |
| Ranking a dozen regions | A 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 regions | A line chart, with a map only if the spatial pattern is itself the finding. |
| Data about countries where only a handful have values | A 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 place | A bar chart, unless the territories are contiguous and drawn correctly. |
| Precise values people need to read off | A 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, always | Licensed |
|---|---|
choropleth, bubble and marker series, and the automatic basemap | Point clustering (cluster) and density (hexbin) |
| All 13 built-in projections | Projections you register yourself |
| The geometry registry, all 26 packs, provenance and attribution | Drilldown and its breadcrumb |
| Tooltips, legends, labels, data labels, states, themes | Hex tile layouts |
| Zoom, pan, pinch, hover, click and box selection, the camera API | Editorial annotations |
Joins, fuzzyJoin, and the join diagnostics | arc and line route series, including flow beads |
| Scales, palettes, size legends, responsive rules | Linked selection across maps (link: { group }) |
| Globe rotation and on-screen zoom controls | Pattern fills and image fills |
| PNG and SVG export | Story 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-filteredSee 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.