Learn

GeoJSON with D3.js

D3 renders GeoJSON by combining a path generator with a map projection. The path generator converts geographic coordinates to SVG path strings; the projection handles the coordinate transformation.

Load and render

d3.json() fetches and parses the file. d3.geoPath() takes the projection and returns a function that converts each GeoJSON feature to an SVG d attribute string. fitSize() scales and centers the projection to fill the SVG element exactly.

import * as d3 from 'd3'

const width = 960
const height = 500
const svg = d3.select('#map').append('svg').attr('width', width).attr('height', height)

const geojson = await d3.json('/world-countries.geojson')

const projection = d3.geoNaturalEarth1().fitSize([width, height], geojson)
const path = d3.geoPath(projection)

svg.selectAll('path')
  .data(geojson.features)
  .join('path')
  .attr('d', path)
  .attr('fill', '#ccc')
  .attr('stroke', '#fff')

Each feature in geojson.features becomes one SVG <path> element. The .join('path') call handles enter, update, and exit — adding new elements when features are added and removing them when they are removed.

Projections

The projection you choose determines how geographic coordinates map to screen pixels. D3 ships with over 15 projections; these five cover the most common cases.

ProjectionBest for
geoNaturalEarth1World maps — visually balanced, area distortion is moderate
geoMercatorWeb mapping standard — shapes are familiar, area near poles is inflated
geoAlbersUsaUSA maps — Alaska inset is built in, scales Hawaii automatically
geoOrthographicGlobe view — one hemisphere at a time, configurable rotation
geoConicEqualAreaChoropleth maps — area is preserved, which matters when shading by density

Switch projections by replacing geoNaturalEarth1 with any other name; the rest of the pipeline stays the same.

Choropleth: colour by property

d3.scaleSequential maps a numeric domain to a continuous colour interpolator. d3.extent() returns [min, max] from the feature array, which becomes the scale domain. Features with a null or undefined value fall through to the '#eee' default.

const colorScale = d3.scaleSequential()
  .domain(d3.extent(geojson.features, d => d.properties.value))
  .interpolator(d3.interpolateBlues)

svg.selectAll('path')
  .data(geojson.features)
  .join('path')
  .attr('d', path)
  .attr('fill', d => d.properties.value != null
    ? colorScale(d.properties.value)
    : '#eee'
  )

d3.interpolateBlues is one of D3's built-in sequential colour schemes. Alternatives: d3.interpolateViridis, d3.interpolateRdYlGn, d3.interpolateOranges. All accept a value in [0, 1] and return a CSS colour string.

To add a legend, build a d3.scaleSequential with the same domain and interpolator, render it as a gradient <rect>, and add axis ticks using d3.axisBottom.

Tooltips

Attach mouseover and mouseleave handlers to show feature data on hover:

const tooltip = d3.select('body').append('div')
  .style('position', 'absolute')
  .style('pointer-events', 'none')
  .style('display', 'none')

svg.selectAll('path')
  .data(geojson.features)
  .join('path')
  .attr('d', path)
  .attr('fill', '#ccc')
  .on('mouseover', (event, d) => {
    tooltip
      .style('display', 'block')
      .style('left', `${event.pageX + 10}px`)
      .style('top', `${event.pageY - 20}px`)
      .text(d.properties.name ?? d.properties.NAME ?? '')
  })
  .on('mouseleave', () => tooltip.style('display', 'none'))

event.pageX and event.pageY are the raw mouse coordinates relative to the document. Adding 10px horizontal offset keeps the tooltip from sitting directly under the cursor.

Watch out: D3 projections transform [longitude, latitude] → SVG [x, y] internally. Do not pre-transform coordinates before passing them to D3 — geoPath applies the projection itself. Pre-transforming produces a double-projection and places features in the wrong location.

For world country GeoJSON data sources, see World countries GeoJSON. For GeoJSON format reference, see What is GeoJSON?.

D3 renders GeoJSON; it doesn't let users edit it. For drawing and editing GeoJSON interactively, open the Element Editor playground.

Continue reading

Mike Acler

Mike Acler

Design engineer and creator of Element Editor, an open-source map drawing and GeoJSON editing library for React. Writes about web cartography and GIS tooling.