Skip to content

Instantly share code, notes, and snippets.

@shawnbot
Last active Aug 29, 2015
Embed
What would you like to do?
Uniformly scaled geo thumbnails

This example shows how to use d3 to create uniformly scaled geographic feature thumbnails. There are two interesting features of SVG at play here:

  1. The viewBox attribute is used to "zoom in" to the individual states.
  2. The use element is used to redraw a common background states path into each thumbnail, reducing DOM overhead.

The other nice thing here is that we didn't have to define the SVG elements' size in JavaScript, so they can be sized exclusively in CSS. The viewBox attribute tells the browser to fit the given rectangle within the SVG element's bounds. You can also use the preserveAspectRatio attribute to change how the viewBox fits within its bounds.

<!DOCTYPE html>
<html>
<head>
<title>d3 geo thumbnails</title>
<script src="http://d3js.org/d3.v3.min.js"></script>
<script src="http://d3js.org/topojson.v1.min.js"></script>
<style>
svg.thumb {
width: 90px;
height: 94px;
display: block;
float: left;
margin: 5px 0 0 5px;
}
path {
vector-effect: non-scaling-stroke;
stroke-width: 1;
}
.thumb .bg,
.thumb use {
fill: #ddd;
stroke: #ccc;
}
.thumb .fg {
fill: #8cba3d;
stroke: none;
}
</style>
</head>
<body>
<div id="thumbnails"></div>
<svg id="shared">
</svg>
<script>
var root = d3.select("#thumbnails"),
proj = d3.geo.albersUsa(),
path = d3.geo.path()
.projection(proj),
// how much space to give the features from
// the edge of their container
margin = 10;
d3.json("us-states.json", function(error, topology) {
var collection = topojson.feature(topology, topology.objects["states"]);
// draw the whole collection once to a path in the shared <defs>
d3.select("#shared")
.append("defs")
.append("path")
.datum(collection)
.attr("id", "states-bg")
.attr("d", path);
// sort the states by name
collection.features.sort(function(a, b) {
return d3.ascending(a.properties.name, b.properties.name);
});
// filter out the territories & DC
var states = collection.features.filter(function(d) {
return +d.id <= 70 && d.id != 11;
});
var svg = root.selectAll(".thumb")
.data(states)
.enter()
.append("svg")
.attr("id", function(d) {
return d.properties.name;
})
.attr("class", "thumb")
.attr("viewBox", function(d) {
// get the projected bounds
var bounds = path.bounds(d),
width = bounds[1][0] - bounds[0][0],
height = bounds[1][1] - bounds[0][1],
// get the proportion of the bounds' longest side
// to the container's shortest side
scale = Math.max(width, height) / Math.min(this.offsetWidth, this.offsetHeight),
// and multiply the desired margin by this
m = margin * scale;
return [
bounds[0][0] - m,
bounds[0][1] - m,
width + m * 2,
height + m * 2
].join(" ");
});
// place the shared states path here
svg.append("use")
.attr("xlink:href", "#states-bg");
// and draw the individual states on top
svg.append("path")
.attr("class", "fg")
.attr("d", path);
});
</script>
</body>
</html>
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment