Skip to content

Instantly share code, notes, and snippets.

@mbostock
Last active February 9, 2016 02:02
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save mbostock/6262722 to your computer and use it in GitHub Desktop.
Save mbostock/6262722 to your computer and use it in GitHub Desktop.
OPHZ Zooming
license: gpl-3.0

First, preproject and simplify using topojson --projection:

topojson \
	-o ophz.json \
	-q 1e5 \
	-s 0.25 \
	--projection 'd3.geo.albersUsa()' \
	-- b=ophz-b-wgs84.shp

See the Command Line Reference for details.

The simplification threshold baked into the TopoJSON file is based on the maximum amount of zoom we’ll use, or conversely, the minimum area threshold for client-side simplification. Since at maximum zoom scale = 4, and the simplification threshold is set dynamically at 4 / scale / scale, the minimum simplification threshold is 0.25. (The two fours are unrelated; the latter indicates the area threshold in square pixels.) The smaller the baked-in simplification threshold, the more maximum detail, and the larger the file. But since the client simplifies when rendering, it should still perform well when zoomed out.

Then, update the example code to reflect the new projection, here the composite U.S.A. Albers projection (probably not the best projection for this; you might want to rotate so that the parallels are flat when you zoom in):

var projection = d3.geo.albersUsa();

And that’s pretty much it.

Well, this example is slightly different from the original because it combines viewport clipping (via d3.geo.clipExtent) rather than using rough simplification outside the viewport. The geometry here is much more complex, so the rough simplification was causing artifacts on the edges of the viewport that go away when you use proper clipping.

<!DOCTYPE html>
<meta charset="utf-8">
<body>
<script src="//d3js.org/d3.v3.min.js"></script>
<script src="//d3js.org/topojson.v1.min.js"></script>
<script>
var width = 960,
height = 500;
// This projection is baked into the TopoJSON file,
// but is used here to compute the desired zoom translate.
var projection = d3.geo.albersUsa();
var sf = projection([-122.417, 37.775]),
center = projection.translate();
var scale,
translate,
area; // minimum area threshold
var clip = d3.geo.clipExtent()
.extent([[0, 0], [width, height]]);
var simplify = d3.geo.transform({
point: function(x, y, z) {
if (z >= area) this.stream.point(x * scale + translate[0], y * scale + translate[1]);
}
});
var zoom = d3.behavior.zoom()
.size([width, height])
.on("zoom", zoomed);
var canvas = d3.select("body").append("canvas")
.attr("width", width)
.attr("height", height);
var context = canvas.node().getContext("2d");
var path = d3.geo.path()
.projection({stream: function(s) { return simplify.stream(clip.stream(s)); }})
.context(context);
d3.json("ophz.json", function(error, ophz) {
if (error) throw error;
canvas
.datum(topojson.mesh(topojson.presimplify(ophz)))
.call(zoomTo(center, 1).event)
.transition()
.duration(2500)
.each(jump);
});
function zoomTo(point, scale) {
return zoom
.translate([width / 2 - point[0] * scale, height / 2 - point[1] * scale])
.scale(scale);
}
function zoomed(d) {
translate = zoom.translate();
scale = zoom.scale();
area = 4 / scale / scale;
context.clearRect(0, 0, width, height);
context.beginPath();
path(d);
context.stroke();
}
function jump() {
var t = d3.select(this);
(function repeat() {
t = t.transition()
.call(zoomTo(sf, 4).event)
.transition()
.call(zoomTo(center, 1).event)
.each("end", repeat);
})();
}
</script>
Display the source blob
Display the rendered blob
Raw
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