How to create pure react SVG maps with topojson and d3-geo

Since d3 has been split into smaller components in v4, pairing it with react has become more streamlined. Making declarative SVG maps with react, topojson, and d3-geo has never been easier.

This article will show you how to create pure react SVG maps with topojson-client and d3-geo. The techniques outlined here will help you make your own reusable SVG mapping components.

What this article covers

  1. The anatomy of an SVG map independent of react
  2. Structure of example app and npm dependencies
  3. Loading topojson and rendering a basic SVG map with react and d3-geo
  4. Adding events
  5. Example data-driven world map with markers

Code

The code for this tutorial is available in the react-svg-maps-tutorial repo on github.

Note: This tutorial covers the creation of a simple SVG map. If you need more functionalities, such as zooming, panning and annotations, I put together a library of reusable components you can install via npm called react-simple-maps.

1. The anatomy of an SVG map

In order to create a map with react and d3-geo let’s first take a look at what the output of such a map could look like:

<svg>
<g class="wrapper">
<g class="countries">
<path d="..." class="country" />
<path d="..." class="country" />
</g>
<g class="markers">
<circle class="marker" r=10 />
<circle class="marker" r=10 />
</g>
</g>
</svg>

This structure is completely independent from whether you are using react or any other framework/tool to render a map.

Instead of using d3 to manipulate the DOM and manage the above elements, we can use react. In JSX the above structure virtually stays the same (except for class, which becomes className).

<svg>
<g className="wrapper">
<g className="countries">
<path d="..." className="country" />
<path d="..." className="country" />
</g>
<g className="markers">
<circle className="marker" r=10 />
<circle className="marker" r=10 />
</g>
</g>
</svg>

The only tricky part is generating the d-path for each country. In order to do that you will first need a geo-file (topojson) containing some json describing the country paths. Then you will need a library that can translate such geographical data into coordinates. Topojson-client and d3-geo are ideally suited for these tasks. The former allows you to use topojson files, which are significantly smaller than geojson, and the latter allows you to easily manage geo-projections and translate geo data into a coordinate system. Using d3-geo also allows you to take advantage of the extensive collection of projections created by Mike Bostock.

2. Dependencies and structure

In order to create a map with react you will need a basic react project. For my own example, I used the most simple boilerplate possible, to not distract from the map-specific code. See the tutorial github repo for more information.

For the map I will be using d3-geo and topojson-client (assuming you have already installed react and react-dom), so let’s start by installing these libraries:

npm install d3-geo topojson-client --save

The code structure for this example will look like this:

┬ app
├─┬ src
│ ├─┬ components
│ │ └── WorldMap.js
│ └── index.js
├─┬ public
│ ├── app.js => code compiled by webpack
│ └── world-110m.json
├── index.js
├── package.json
├── webpack.config.js
└── node-modules

3. The WorldMap component

The WorldMap component will render a map based on the world-110m map from the topojson-worldatlas. Since I want to customise the projection and reuse it for markers, I created a separate method that returns a mercator projection with some scale and offset customisations. I also take advantage of the SVG viewBox property to prepare the map for variable screen sizes.

Start by defining worldData as part of the WorldMap component state (line 12). This will be an empty array in the beginning, and the data will be loaded asynchronously, once the component mounts (see componentDidMount on line 20). The empty array avoids throwing an error when iterating over worldData before the map is loaded (see line 39).

To fetch world-110m.json I use Chrome’s built-in fetch function, but you can use any AJAX library of your choice (e.g. axios, superagent).

The projection is used to render the d-path of the country paths, as well as the cx and cy property of the marker circles. Since the projection is customised, it makes more sense to write a method for it (see line 15).

In order to not make all countries the same colour, I use the index of the country within the worldData array to set the opacity of the country path fill. If you have data, you can use the same strategy to create a choropleth map.

4. Events

Adding events to your SVG paths works the same way as adding events to any other element in JSX. The below code shows how to add an onClick handler to the country paths.

The same approach can be used for other events (e.g. mouseEnter, mouseLeave, mouseMove) for countries, but also for markers.

5. Data example: Most populous cities of the world

Let’s try to add some data to the mix and output the world’s most populous cities on the world map. The marker size will be determined by the population size of the city.

Note: I have also added an onClick event to the markers, outputting the currently clicked city to the console (see lines 57 and 103).

Your map should look like this.

The output of WorldMap.js should look like this. The countries are coloured based on the index of the country within the worldData array. The circle sizes are determined by the population size of each city displayed.

You can now update the marker dataset using setState, and the markers will be updated accordingly.

If you want to add tooltips to your map, you can use the technique outlined above for event handling. To render the actual tooltip, you can use a library like redux-tooltip.

Next steps

For simple maps, the techniques outlined in this article should be enough to get you started, but once you want to add more complex functionality, such as panning, zooming, etc. it gets a little bit more complicated. Performance is a particularly tricky issue with react SVG maps.

As mentioned before, I have put together a set of reusable components in the react-simple-maps library, which help make more performant SVG maps with react and d3-geo. The library handles the more complex logic related to resizing, panning, and performance optimisation, so that you can focus on making beautiful and engaging visualisations.

Inspiration

If you are interested in reading more about using react and d3 together, check out “Interactive Applications with React and D3” by Elijah Meeks, or “How and why to use d3 with react” by Dan Scanlon.

If you have any questions or thoughts about the code in this article, please leave a message below. If you found this article helpful, feel free to share and recommend. Happy mapping!