Getting states borders from counties using TopoJSON's merge function

September 27, 2015 |

I use the immortal Mike Bostock's TopoJSON for every map I make at TIME. Most of the time, we're looking at geographies more granular that states—mainly counties, congressional districts and PUMAs. In these cases, we like to have state boundaries drawn over the map to help orient the viewer.

To do this without loading separate state boundaries (with never align perfectly at high zoom levels), I've using TopoJSON's .mesh function to impute the boundaries. But this doesn't include international boundaries, which doesn't always look great:

svg_meshed.append("path")
    .datum(topojson.mesh(topology, topology.objects.counties, function(a, b) { return a.id - a.id % 1000 !== b.id - b.id % 1000; }))
    .attr("class", "state")
    .attr("d", path);

It's not very difficult to add international boundaries as well, but at this point the amount of code is accumulating. More importantly, the states themselves are not discrete <path> objects that one can manipulate with mouseovers and so forth.

I recently came across Mike's example of merging geographies, and decided it might be a better way to make states out of counties. Here's what that looks like:

// let's get an organic list of fips
var fips = {};
counties.forEach(function(county) { fips[county.id - county.id % 1000] = 1; });

var states = [];
// and merge by fips
Object.keys(fips).forEach(function(fip) {
    var state = topojson.merge(topology, topology.objects.counties.geometries.filter(function(d) { return d.id - d.id % 1000 == fip; }));
    states.push(state);
});

svg_merged.append("g").attr("id", "states").selectAll(".state").append("path")
    .data(states)
    .enter()
    .append("path")
    .attr("class", "state")
    .attr("d", function(d) {
        return path(d);
    });

Better, right? Now you have an object for every state plus international borders. Here's the gist and Bl.ocks.