简体   繁体   中英

Scaling geographic shapes to a similar size in D3

I'm using D3's world-countries.json file to create a mercator map of world countries, which I'll then bind to some data for a non-contiguous cartogram. Alas, the much larger sizes of Canada, the US, Australia, etc. mean that one unit for those countries is the spatial equivalent of several units for, say, Malta.

What I think I need to do is normalize the geojson shapes, such that Canada and Malta are the same size when starting out.

Any idea how I'd do that?

Thanks!

Update: I've tried explicitly setting the width and height of all the paths to a small integer, but that seems to just get overridden by the transform later. Code follows:

// Our projection.
var xy = d3.geo.mercator(),
    path = d3.geo.path().projection(xy);

var states = d3.select("body")
  .append("svg")
  .append("g")
    .attr("id", "states");

function by_number() {

 function compute_by_number(collection, countries) {

    //update
     var shapes = states
            .selectAll("path")
              .data(collection.features, function(d){ return d.properties.name; });
    //enter       
         shapes.enter().append("path")
              .attr("d", path)
              .attr("width", 5) //Trying to set width here; seems to have no effect.
              .attr("height", 5) //Trying to set height here; seems to have no effect.
              .attr("transform", function(d) { //This works.
                var centroid = path.centroid(d),
                x = centroid[0],
                y = centroid[1];
                return "translate(" + x + "," + y + ")"
                + "scale(" + Math.sqrt(countries[d.properties.name] || 0) + ")"
                + "translate(" + -x + "," + -y + ")";
              })
            .append("title")
              .text(function(d) { return d.properties.name; });
    //exit
 }

 d3.text("../data/country_totals.csv", function(csvtext){
    var data = d3.csv.parse(csvtext);
    var countries = [];
    for (var i = 0; i < data.length; i++) {
        var countryName = data[i].country.charAt(0).toUpperCase() + data[i].country.slice(1).toLowerCase();
        countries[countryName] = data[i].total;
    }

    if (typeof window.country_json === "undefined") {
        d3.json("../data/world-countries.json", function(collection) {          
            window.country_json = collection;
            compute_by_number(collection, countries);           


        });
    } else {
            collection = window.country_json;
            compute_by_number(collection, countries);
    }

  });
} //end by_number

by_number();

You might be able to use the helper function I posted here: https://gist.github.com/1756257

This scales a projection to fit a given GeoJSON object into a given bounding box. One advantage of scaling the projection, rather than using a transform to scale the whole path, is that strokes can be consistent across maps.

Another, simpler option might be to:

  1. Project the paths;
  2. Use path.getBBox() to get the bounding box for each ( .getBBox() is a native SVG function, not a D3 method)
  3. Set a transform on the path, similar to how you do it now, to scale and translate the path to fit your bounding box.

This is a bit simpler, as it doesn't involve projections, but you'll need to scale the stroke by the inverse ( 1/scale ) to keep them consistent (and therefore you won't be able to set stroke values with CSS). It also requires actually rendering the path first, then scaling it - this might affect performance for complex geometries.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM