Mapping in React and D3

A lot of folks today are talking about React, the Javascript framework that lets you build performant apps and UIs quickly and efficiently. I use React almost everyday at my job and in my own open source projects, often pairing it with Redux for state management.

But as a cartographer, my ultimate assessment of any framework comes down to its ability to help me make beautiful, engaging maps and visualizations. I've worked quite a bit with the ArcGIS Javascript API in React to make slippy maps, but I haven't fallen in love with it. D3, on the other hand (which ranks among my favorite libraries out there), is a lot more fun to play with in React. It's also a good bit more difficult to work with because of how differently it interacts with the DOM. While React uses a virtual DOM model to parse UI updates and render the proper HTML as a function of props and state, D3 just operates directly on the DOM, adding and removing elements as you please. This makes D3 quite a bit more opinionated than React. In this piece, I'll go over the two main strategies I've found for working with, and mapping with, D3 and React.

Before we get started, please take a read of one of my favorite pieces on this subject, which will be particularly useful for those of you who are new to React. Done? No really, go read it. It'll help you understand what is to follow. Sweet, on to the good stuff!

When mapping with D3 and React, I like to say that you can either write React code the D3 way or write D3 code the React way. By this I mean to say that you need to let one of these frameworks drive how you handle the DOM in any given situation. Of course, just as nothing in life is an either / or (more often, it's an &), this doesn't preclude you from using both techniques in a single app – on the contrary, I think these two methods are suited to solving different problems. Let's look at two examples, starting with writing React code the D3 way. Our task will be rendering a simple legend for a choropleth map. Let's take a look.

// import React
import * as React from 'react';
// import d3 - run npm install --save d3 to add
// d3 to your node_modules
import * as d3 from 'd3';

class MapLegend extends React.Component {
  constructor() {
    // props, bound methods of your React component
    // etc. can go in here
  componentDidMount() {
    // time to make a legend here
    // start by storing all color values in an array
    let colorLegend = ["rgb(247,251,255)", "rgb(222,235,247)", "rgb(198,219,239)", "rgb(158,202,225)", "rgb(107,174,214)", "rgb(66,146,198)", "rgb(33,113,181)", "rgb(8,81,156)"];

    // get the svg that just mounted - this is componentDidMount()
    // so this function gets fired just after render()
    let svgLegend ='#legend');

    // now just inject standard D3 code
        .attr("transform", "translate(500, 525)")
        .attr("fill", function(d, i){ return colorLegend[i]; })
        .attr("x", function(d, i){ return (i*30); })
        .attr("y", 30)
        .attr("width", 30)
        .attr("height", 20);

    // add a title
        .attr("transform", "translate(500, 525)")
        .attr("font-size", "12px")
        .attr("font-family", "HelveticaNeue-Bold, Helvetica, sans-serif")
        .attr("y", 20)
        .text("Shootings Per Million");

    // add numbers as labels
    let labelsLegend = ["0-1","1-3","3-5","5-7","7-10","10-12","12-15",">15"];
        .attr("transform", "translate(500, 525)")
        .attr("font-size", "10px")
        .attr("font-family", "HelveticaNeue-Light, Helvetica, sans-serif")
        .attr("x", function(d, i){ return (i*30); })
        .attr("y", 60)
        .text(function(d){ return d; })
  render() {
    return <svg id='legend'></svg>;

export default MapLegend;

So let's discuss what's going on here. Essentially, we are just using React to render a blank SVG container for us, rather than leaving that to D3. This is useful because it turns this SVG into a component, which – using ES6 classes – has lifecycle methods we can hijack to do anything we want. In this case, using componentDidMount() is the proper choice because it guarantees that the empty SVG is in the DOM before we execute any D3 code to act on it. The contents of the componentDidMount() method should look familiar to anyone who has worked with D3. Here we simply obtain a reference to our SVG in the DOM, append a <g> tag to house the <rect>'s that will make up our legend, add the proper color and text label to each rectangle, and add a title to the legend. Pretty simple, but pretty powerful. I call this writing React code the D3 way because we're really letting D3 drive the way this component renders. React is essentially just providing us an SVG shell into which D3 can put anything we want. Of course, since this legend is now a component, we can generalize it – say by turning the colorLegend or labelsLegend arrays into props that can render anything we hand off to the component.

The other way of doing things is what I like to call writing D3 code the React way. Taking this approach, React takes the reins of explicitly rendering things, to the point where <path> or <circle> elements become components. Let's take a look at how to render a simple map of the United States using this approach.

// import react
import * as React from 'react';
// be sure to import d3 - run npm install --save d3 first!
import * as d3 from 'd3';
// also import topojson so we work with topologically clean geographic files
// this will improve performance and reduce load time
// npm install --save topojson
import * as topojson from 'topojson'
// also import lodash
import * as _ from 'lodash';
// and import a custom component called State
// we'll get to this a bit later
import State from './State';

class Map extends React.Component {

  constructor() {
    this.state = { us: [] };
    this.generateStatePath = this.generateStatePath.bind(this);
  componentDidMount() {
    // send off a request for the topojson data
    d3.json("", (error, us) => {
      if (error) throw error;
      // store the data in state
      // I prefer to use Redux for state management - if you're taking
      // that approach this would be a good place to dispatch an action, i.e.
      this.props.dispatch(actions.sendAPIDataToReducer({ us }));
  // let's define a method for rendering paths for each state
  generateStatePath(geoPath, data) {
    const generate = () => {
      let states =, (feature, i) => {
        // generate the SVG path from the geometry
        let path = geoPath(feature);
        return <State path={path} key={i} />;
      return states;
    let statePaths = generate();
    return statePaths;
  render() {
    // create a geographic path renderer
    let geoPath = d3.geoPath();
    let data = // or, the reference to the data in the reducer, whichever you are using
    // call the generateStatePaths method
    let statePaths = this.generateStatePaths(geoPath, data);
    return (
        <svg id='map-container'>
            <g id='states-container'>

Alright, so let's dig into this piece of code. Here we are actually generating the JSX for our states inside of a separate method on our component called generateStatePath. You'll notice generateStatePath takes two arguments - geoPath and data. geoPath is just D3's geographic path generator. It what allows us to work dynamically with things like projected coordinate systems in D3 (which is pretty darn sweet for those of us who've spent many a day in hell reprojecting static maps in ArcGIS). The second argument is our data - in this case, our data is a 10m resolution topojson file of every state in the United States, which we fetch using the d3.json method and store in the component's state. Notice I make a comment about using Redux to store the topojson data rather than the component's state. The case for this is that your topojson is available to any component in your application, which becomes very useful for doing analysis or making different maps with the same root data.

The generateStatePath function then simply maps over every feature in the dataset, returning a State component for each. The result is an array of State components, which get rendered inside our <g></g> tag. Let's check out what this State component looks like.

import * as React from 'react';

const State = (props) => {
  return <path className='states' d={props.path} fill="#6C7680"
  stroke="#FFFFFF" strokeWidth={0.25} />;

export default State;

Our State component is really just an SVG path that gets rendered using the path prop handed down by our parent Map component. That path gets generated in the call to d3.geoPath(feature), which takes the input state feature as a GeoJSON object and outputs an SVG path that can be rendered on our page. You'll notice here we're applying the Stateless Functional Component paradigm to our component. Rather than using an ES6 class, we define our component as a function, which simply returns some JSX based on the props we provide it as an argument.

Here I suggest we're righting D3 code the React way because we're applying React's component hierarchy philosophy to rendering our D3 elements. What I appreciate about this approach is the ability to control the rendering of HTML elements at a very precise level – down to the point where we can write custom functions to handle all sorts of UI scenarios. These elements are now also subject to React's diffing algorithm, meaning that updates to data stored in Redux or in the component's state will trigger updates to these elements on the UI. With something like react-transition-group, you can begin to implement all of the beautiful magic behind D3 transitions using React!

If you're curious to see some of this code in action, check out this Github repo. This is an app I've been developing to help make sense of crowdsourced police shootings data gathered by the Guardian. An older, pure D3 / JS version of the app can be seen on my portfolio or here. I wanted to test myself to see if I could develop the same (or better) experience in React, using other cool technologies like Sass and redux-sagas. In my next post, I'll talk a bit about techniques for responsive typography in map focused layouts using Sass maps. More to come as well on async data loading using redux-sagas and axios. Thanks as always for reading.

Senior Research in Geography – GEOG 0700: Programming for GIS

"How can we make GIS do something different?"

Professor Sarah Elwood from the University of Washington posed this question to me one November afternoon towards the end of the fall semester, as we discussed her research on feminist GIS practice over Skype. At the time, I was working on a podcast for my senior seminar, Feminist Geography, that asked the question, "What does it mean to make maps in a feminist / critical way?" After a healthy literature review and two other interviews with experts in the field, I was still struggling to answer this question. I knew that feminist GIS had something to do with questioning how cartographic knowledge is made, with challenging the reductive technique of representing human experiences of space as points, lines, and polygons, and with breaking the boundary between cartographers and the subjects (objects?) of their maps. But it wasn't until I spoke with Professor Elwood that I received the insight I craved. The goal of all this was related to one simple idea: that we could be doing GIS differently.

Fast forward to the start of the spring semester, and here I am trying to figure out what, exactly, that might look like. What would it mean to do GIS differently? What tools would we use? Hadn't all of the tools been invented already? Where would I start?

Well, the answer to that last question was somewhat obvious: the internet. As I perused the methods sections of different papers by feminist and critical geographers, and spent hours trying to figure out how certain maps and visualizations were made, I began to realize that these cartographers were using more than your standard geoprocessing toolkit – they were using code. I had known for a long time that GIS people and computer scientists had a lot to talk to each other about, but I had never realized the full extent of the connection. Particularly when I discovered the open source world led by CartoDB, Mapbox, the QGIS Project, Development Seed, Leaflet, the PostGIS folks, and all of the other hundreds (perhaps thousands) of developers out there working on this same problem, my mind began to churn in excitement. I could see a new bridge forming, a connection whereby code, GIS, feminist epistemologies, and smart design could come together to make maps that truly did something different. My next task was to figure out how I could do that myself.

Here, in my last semester at Middlebury, I have decided to dedicate my capstone senior research to this exploration. With the excellent guidance of my faculty advisor, Professor Joseph Holler, I will be pursuing an extensive and ambitious self-designed syllabus to learn the coding techniques necessary to make new kinds of maps. This will, of course, involve a lot of making "old kinds" of maps, of experimenting with traditional techniques to see where I might be able to do things differently, to deviate from the master plan. The ultimate goal of this research is to produce a framework for how we might go about learning (and teaching) GIS differently, using a code-based approach that emphasizes creativity, novelty, universality, and smart design alongside a critical epistemology of self-reflexivity, positionality, accountability, and social justice. In this pursuit, I hope to cultivate both technical and philosophical skill as a young cartographer and developer. I am a self-taught programmer, and have become adept at coding largely through determination, commitment, and sheer stubbornness (and let's of course be sure to mention the amazing pooled community knowledge on Github and Stack Exchange). I am passionate about making maps that tell impactful stories and empower marginalized voices. And I want to dedicate my life to changing how we think about cartographic knowledge and the process of making it. I hope, in this senior research, I can begin to do just that.

To view a preliminary syllabus of the research, click here.