Screenshot from The Wall Street Journal’s 2016 Super Tuesday election results page.

Building a U.S. Election Basemap with D3.js and TopoJSON

Hey there. So you want to make an election map using TopoJSON and D3.js!? Well good news, friend. I’m here to outline a relatively simple approach I’ve worked out after making maps through a couple of national election cycles and a long 2016 primary season.

First, the basics. National election results in the U.S. are reported generally at three levels: by state, by county, and by congressional district. During the primaries and general election, the Associated Press provides live results to news organizations by subscription. This tutorial will outline the process for building a bare-bones front-facing election map without getting into the AP’s feed and how one loads it. (But if you’re interested, I’d suggest checking out the excellent Elex project.) This tutorial is broken out in two parts:

  1. Create a TopoJSON file with states, counties, and congressional districts using Census Bureau shapefiles and the TopoJSON command line tool.
  2. Build a responsive, zoomable, USA Albers-projected map using JavaScript and the D3.js and TopoJSON.js libraries. (The accompanying bl.ock can be found here.)

Creating the TopoJSON

The U.S. Census Bureau provides current shapefiles for states, counties, and congressional districts, as well as populated areas, zip codes and, of course, census tracts. To build our election map TopoJSON file, we’ll start by downloading shapefiles for states, counties and districts. (The shapefiles are labeled by file size as “500K”, “5M” and “20M”. Counterintuitively, the 500K file is actually the largest.)

To combine the three shapefiles into one TopoJSON file, we’ll use the TopoJSON command-line tool (install instructions here). TopoJSON is a tool created by Mike Bostock for converting shapefiles and GeoJSON into much smaller files. The format is similar to GeoJSON but eliminates redundancy by combining line segments and shared borders.

Start by putting all three of our shapefiles in one directory.

Using the command line, we’ll create a new TopoJSON file that contains topology for states, counties, and districts as separate objects. In this example, I’ve written a complete conversion as a shell script, which we’ll call “makemap.sh”.

topojson > us2016.topo.json \
-q 5000 \
-s 0.00000008 \
states=cb_2015_us_state_500k/cb_2015_us_state_500k.shp \
--id-property GEOID \
districts=cb_2015_us_cd114_500k/cb_2015_us_cd114_500k.shp \
--id-property GEOID \
counties=cb_2015_us_county_500k/cb_2015_us_county_500k.shp \
--id-property GEOID

To execute the script, save it to the same directory as your shapefiles and run the script through the command line:

$ bash makemap.sh

In the script above, “-q” and “-s” stand for quantization and simplification respectfully. A higher quantization value yields greater coordinate precision and a smaller simplification value leaves more nodes to represent arcs. These are the controls for file size and level of detail in the output file. Each are documented in the command line reference, but the best explanation I’ve read is here in this article.

Our TopoJSON output file is roughly 1.5MB in size (again you may want to play with the quantization and simplification settings). Each of the features nested in the states, counties and districts objects is keyed by it’s GEOID property, which is the same as the Federal Information Processing Standard, or FIPS ID. This is important because the AP reports results using the FIPS ID (2 digits for states, 5 digits for counties and 4 digits for congressional districts).

If you have the ogr2ogr library installed, you can view the field names of your shapefile through the command line, like so:

$ ogrinfo -so -al cb_2015_us_state_500k

If you’re like me, you might like to actually see the geography when looking at a new shapefile. In which case, you could open it up in a mapping application such as QGIS. Here are the properties included in the state shapefile:

Once you’ve run the script, you should have a brand new TopoJSON file ready to be rendered on the page via JavaScript and D3.js. The easiest way to see what we’ve created is to call the newly created file in our script so can view it in our browser inspector, like so:

d3.json("us2016.topo.json", function(error, us) {
if (error) throw error;
console.log(us)
})

The inspector view gives us a clean window into the file’s structure:

As you can see, we have three nested objects: counties, districts, and states. Each of those contains topographical features as objects, like so:

Here we’ve got Barbour County, AL represented as “01005”, where the first two digits represent the state (01 = Alabama) and the last three represent the county. Cool, right? Yep, pretty cool.

Now the hard part.

Building the Application in JavaScript

Important: Please refer to the accompanying bl.ocks example for a a concise rendering of documented code.

You can find many bl.ocks examples that demonstrate how to render a map from TopoJSON. What I’ve tried to do here is tie together a few disparate concepts into one unified and fairly universal use case. This example addresses three basic requirements for a U.S. election map. Our map should:

  1. Convey results by coloring features and displaying data in tooltips.
  2. Switch between State, County and District views.
  3. Be zoomable and responsive.

With those in mind, I’ve tried to build the simplest scaffolding for each of those functions. Rather than describe this code line by line, I’ll just link to the code and make a few notes about structure.

Requirements:
To render the map, we’ll need to include the D3.js and topojson.js libraries.

Structure:
This script follows an object prototype structure, which keeps our variables and methods neatly organized within the `electionMap` object. I was introduced to this concept in a post by my Journal colleague Elliot Bently as a way to manage complexity and to control multiple instances of an application on a singular page. For example, a typical D3.js bl.ocks example will define width, height, and margins globally. Our object prototype model scopes those attributes within each `electionMap` instance and pins them to the dimensions of its container.

Accessing Election Data
This example does not actually load any election data. Rather, it’s meant to serve as a shell for displaying topology with each state, county, and congressional district, with FIPS and district codes bound to each feature. For example, as we iterate through each selection in `electionMap.update()`, `d` holds the id value for each selected feature, which will be the same as the FIPS per our TopoJSON set-up.

states.selectAll("path")
.attr("fill", function(d) {
var winner = null;
return _this.setColor(winner);
});
counties.selectAll("path")
.attr("fill", function(d) {
var winner = null;
return _this.setColor(winner);
});
districts.selectAll("path")
.attr("fill", function(d) {
var winner = null;
return _this.setColor(winner);
});

If this were a live map loading actual election data, we’d pass winner’s name for each state/county/district to the setColor() function and get a corresponding fill color in return. For now, I’ve simply defined `winner` as undefined in order to return a blank fill color.

Click-to-Zoom
There are several methods for zooming to features in a D3 map or graphic. Personally, I find the `d3.behavior.zoom` method, which allows free zoom and pan, to be overkill. I prefer a simpler “click-to-zoom” method, which uses a binary zoom level (you’re either zoomed in or zoomed out) and the `transform` property, which sets the pan location based on the centroid of a selected feature. In this example, I’ve used CSS to show a magnifying glass to show when a click will take you in or out. I’ve also added a `.bkgd` element to the map, which zooms the user out when whitespace is selected.

Conclusion

I’m sure there are other ways to create custom TopoJSON files and to render them in D3.js, and I’m looking forward to hearing feedback and suggestions. Truly! And as I type this, I’m already thinking I should have written the example in D3.js v4.0. That may make sense as a follow-up post. For now, I’ll let it lie as a condensed tutorial with the basic concepts intact. I hope this tutorial helps someone who may be standing at the bottom of the mountain, wondering how to go about rendering a map in D3.js.

Like what you read? Give Chris Canipe a round of applause.

From a quick cheer to a standing ovation, clap to show how much you enjoyed this story.