Search code examples
javascriptd3.jsgeojson

Best way to center multiple geojson features in a small multiples chart


I am making a small multiples chart that visualizes multiple neighborhoods.

Imagine something like this, but with neighborhoods not a country:

iraq

My issue is, when I append each path in an enter() method, each neighborhood skews such that its position reflects its position in the city at large. I want each neighborhood centered.

This image displays my trouble (each neighborhood offset from the others):

nyc2

I can use Mike Bostock's instructions for centering a map, which are all well and good. But then I need to recenter every neighborhood. How can I do that such that I'm passing an instance of d into the data.bounds() method and redefining each projection?

Here's how the initial definition of centering happens:

(function run() { 
    d3.json("./data/output.geojson", function(error, map) {


    var b = path.bounds(map),
 s = .95 / Math.max((b[1][0] - b[0][0]) / width, (b[1][4] - b[0][5]) / height),
t = [(width - s * (b[1][0] + b[0][0])) / 2, (height - s * (b[1][6] + b[0][7])) / 2];

projection
    .scale(s)
    .translate(t);


  var chart = d3.select("#charts").selectAll("svg")
           .data(map.features)
           .enter()
           .append("svg")
               .attr("width", mapWidth )
               .attr("height", mapHeight )
               .append("g")

              chart
                .append("path")
                .attr("d", path)
                .style("fill","steelblue");

    });

})()

Will it be necessary to make each map projection its own instance of data entry with the .data().enter() pattern?

Edit:

Final version looks like this:

final


Solution

  • You can use .each():

    chart.append("path")
      .each(function(d) {
        var b = path.bounds(d),
            s = 1.5 / Math.max((b[1][0] - b[0][0]) / mapWidth, (b[1][1] - b[0][1]) / mapHeight),
            t = [(mapWidth - s * (b[1][0] + b[0][0])) / 2, (mapHeight - s * (b[1][1] + b[0][1])) / 2];
    
        projection.scale(s).translate(t);
        d3.select(this).attr("d", path);
      });
    

    This doesn't quite work because the bounds of a feature depend on the projection the path is using. By changing the projection, you're also changing the bounds of subsequent features. To fix that, reset the projection parameters to their previous values after setting the d attribute:

    var os = projection.scale(),
        ot = projection.translate();
    
    // ...
    
    projection.scale(s).translate(t);
    d3.select(this).attr("d", path);
    projection.scale(os).translate(ot);