D3 Map Visualization Example

A choropleth is a bar chart wearing country shapes. Here's a live D3 world map with geoPath, Equal Earth, and a hover tooltip.

D3 Maps Examples
← All posts
D3 Map Visualization Example

A D3 map visualization is really just a chart where the “bars” happen to be country shapes. You still have values. You still have a color scale. The one extra piece is a projection that translates longitude and latitude into x and y coordinates that fit on screen.

The live map below loads world-atlas country outlines, projects them with geoEqualEarth (which keeps area proportions honest), and colors a dummy index. The API is exactly the same one you'd use for a GeoJSON export from QGIS or any other mapping tool. For more on D3 generally, see the D3.js page or the graph examples.

What you need

  • A FeatureCollection. GeoJSON where each feature is a place — a country, a state, a postal code area. That's the geometry you're drawing.
  • A projection. geoMercator is what most people recognize from Google Maps. geoEqualEarth is a better default for a choropleth because area stays proportional — Africa doesn't shrink and Greenland doesn't balloon.
  • geoPath. You pass your projection in and it handles writing the SVG d attribute for every feature. You don't calculate those paths manually.
  • A value per feature. Population, rate, score, whatever you're encoding. Join this to the features before you draw.

I don't start from shapefiles in the browser. Convert them offline to GeoJSON or TopoJSON first, then ship the output. Shapefiles in the browser are slow and painful.

A D3 map visualization example

Hover a country for the name and a dummy index. Brighter gold is higher. The outlines are Natural Earth 110m. The numbers are fake so you can see the join, the projection, and the scale without a CSV in the way.

Preview

Equal Earth projection · world-atlas countries-110m · hover for the value

The code

fitExtent is the line I forget, then I stare at a tiny smudge in the corner. It scales the projection so the whole FeatureCollection fills the SVG. Drop Antarctica or the fit will leave a huge empty south.

Code

const world = await d3.json(
  "https://cdn.jsdelivr.net/npm/world-atlas@2/countries-110m.json"
);
const countries = topojson.feature(world, world.objects.countries);
countries.features = countries.features.filter(
  (d) => d.properties.name !== "Antarctica"
);
const scores = { "United States of America": 96, China: 88, India: 84 };
countries.features.forEach((d) => {
  d.properties.value = scores[d.properties.name] ?? 40;
});

const width = 800;
const height = 440;
const svg = d3.select("#chart").append("svg").attr("viewBox", `0 0 ${width} ${height}`);

const projection = d3.geoEqualEarth()
  .fitExtent([[18, 10], [width - 18, height - 52]], countries);
const path = d3.geoPath(projection);
const color = d3.scaleSequential(d3.interpolateYlOrBr).domain([0, 100]);

svg.append("path").datum({ type: "Sphere" }).attr("d", path).attr("fill", "#0e141c");
svg.append("path")
  .datum(d3.geoGraticule10())
  .attr("d", path)
  .attr("fill", "none")
  .attr("stroke", "rgba(255,255,255,0.08)");

svg.selectAll("path.country")
  .data(countries.features)
  .join("path")
  .attr("class", "country")
  .attr("d", path)
  .attr("fill", (d) => color(d.properties.value))
  .attr("stroke", "#0a0c10");

Join your own numbers

The preview colors a lookup by properties.name. In a real chart you load a CSV, Map it on the country name or ISO code, then color(valueByName.get(d.properties.name)). Names in Natural Earth are picky: it's United States of America, not United States.

Need a closer zoom? Use countries-50m.json or a GeoJSON export. If the “map” is really a flow between countries, a D3 sankey chart is usually clearer than drawing lines across the ocean. And if you meant a node-link diagram, that's a force layout, not a choropleth.

Questions I get

How do I make a D3 map visualization?

Start with GeoJSON (or TopoJSON that you convert with topojson.feature()). Pick a projection — geoMercator if you want something familiar, geoEqualEarth if you want area to stay honest. Pass the projection to geoPath, which turns each geographic feature into an SVG path. Then join your data to the features and run the values through a color scale. That's the whole pipeline.

Do I need TopoJSON for a D3 map?

No, GeoJSON is fine. The reason to use TopoJSON is file size — it's significantly smaller because it eliminates redundant shared borders. Libraries like world-atlas and us-atlas ship TopoJSON files; you convert them with topojson.feature() before passing them to D3.

Why does my D3 map look empty?

Most of the time it's one of two things. Either the projection isn't fitted to your data, so the features are rendering somewhere outside the viewport — fix this with fitExtent() or fitSize() on your FeatureCollection. Or the coordinates are lon/lat swapped. GeoJSON specifies [longitude, latitude], not [lat, lon], and that's easy to get backwards.

Latest posts

All posts