Skip to content

Instantly share code, notes, and snippets.

@mbostock
Last active October 2, 2023 22:14
Show Gist options
  • Save mbostock/4699541 to your computer and use it in GitHub Desktop.
Save mbostock/4699541 to your computer and use it in GitHub Desktop.
Zoom to Bounding Box
license: gpl-3.0
redirect: https://observablehq.com/@d3/zoom-to-bounding-box

This example demonstrates how to compute a suitable translate and scale to zoom to the bounding box of a particular feature. Click on any state to zoom in; click on the focused state or the background to zoom out.

This variant uses transform transitions to change the viewport. A slightly better approach, and one that can also allow free panning and zooming with the mouse if desired, is to use zoom transitions.

<!DOCTYPE html>
<meta charset="utf-8">
<style>
.background {
fill: none;
pointer-events: all;
}
.feature {
fill: #ccc;
cursor: pointer;
}
.feature.active {
fill: orange;
}
.mesh {
fill: none;
stroke: #fff;
stroke-linecap: round;
stroke-linejoin: round;
}
</style>
<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,
active = d3.select(null);
var projection = d3.geo.albersUsa()
.scale(1000)
.translate([width / 2, height / 2]);
var path = d3.geo.path()
.projection(projection);
var svg = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height);
svg.append("rect")
.attr("class", "background")
.attr("width", width)
.attr("height", height)
.on("click", reset);
var g = svg.append("g")
.style("stroke-width", "1.5px");
d3.json("/mbostock/raw/4090846/us.json", function(error, us) {
if (error) throw error;
g.selectAll("path")
.data(topojson.feature(us, us.objects.states).features)
.enter().append("path")
.attr("d", path)
.attr("class", "feature")
.on("click", clicked);
g.append("path")
.datum(topojson.mesh(us, us.objects.states, function(a, b) { return a !== b; }))
.attr("class", "mesh")
.attr("d", path);
});
function clicked(d) {
if (active.node() === this) return reset();
active.classed("active", false);
active = d3.select(this).classed("active", true);
var bounds = path.bounds(d),
dx = bounds[1][0] - bounds[0][0],
dy = bounds[1][1] - bounds[0][1],
x = (bounds[0][0] + bounds[1][0]) / 2,
y = (bounds[0][1] + bounds[1][1]) / 2,
scale = .9 / Math.max(dx / width, dy / height),
translate = [width / 2 - scale * x, height / 2 - scale * y];
g.transition()
.duration(750)
.style("stroke-width", 1.5 / scale + "px")
.attr("transform", "translate(" + translate + ")scale(" + scale + ")");
}
function reset() {
active.classed("active", false);
active = d3.select(null);
g.transition()
.duration(750)
.style("stroke-width", "1.5px")
.attr("transform", "");
}
</script>
@rbelew
Copy link

rbelew commented Oct 28, 2013

(first i tried IRC, nobody listening. then i tried stackOverflow complained about me not meeting their quality standards?! sigh, a comment via github)

I've replicated this example, except mine does NOT zoom when i click a region? I've made a pastie of my version: http://pastie.org/8436223

the regions render fine, i can click on each and get its name echoed to console, and the second echo of the 'b' var also looks correct?

anyone see a bug? i can also send along the opd_beats.json file if it'd help?

@calitosdog
Copy link

I would like to know , it there is a way to add opacity to what is not selected,
For example when i select i state , i would like to have the selected state highlighted just like the example and make al other states covered with a transparent background.

@philiprhoades
Copy link

philiprhoades commented Jun 18, 2019

I can't get this to work - I get a blank screen - I inserted your code after this:

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
<HTML>
<HEAD>
    <META HTTP-EQUIV="CONTENT-TYPE" CONTENT="text/html; charset=iso-8859-1">
    <TITLE>Pricom</TITLE>
</HEAD>
<BODY LANG="en-US">

and before this:

</HTML>

but no display and no errors - what am I missing?

Thanks,
Phil.

@philiprhoades
Copy link

It's OK - I worked it what I was doing wrong . . now to do something like it for Australia . .

@Cyrus-Kiprop
Copy link

Hi Mike,
I have a question for you concerning how you derive your scale .9 / Math.max(dx / width, dy / height). I understand that .9 value makes sure that we have a margin between the edges and the element we want to zoom to. I was wondering if there is a way to get a zero edge-to-edge margin. Furthermore, could you please help me understand how that 0.9 is arrived at? Is it a ratio between the SVG height & width?. Thanks
Regards Cyrus.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment