Reputation: 5346
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 U.S., 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();
Upvotes: 1
Views: 472
Reputation: 55678
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:
path.getBBox()
to get the bounding box for each (.getBBox()
is a native SVG function, not a D3 method)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.
Upvotes: 3