Skip to content

Instantly share code, notes, and snippets.

@shawnbot
Last active December 11, 2018 09:09
Show Gist options
  • Star 8 You must be signed in to star a gist
  • Fork 2 You must be signed in to fork a gist
  • Save shawnbot/6518285 to your computer and use it in GitHub Desktop.
Save shawnbot/6518285 to your computer and use it in GitHub Desktop.
d3 bounded zoom behavior

This gist shows how to restrict d3's zoom behavior so that users can't pan outside of a rectangular bounding box. Use your scroll wheel to zoom in and out of the field of circles, and click and drag to move when zoomed in. Note how when you zoom back out (by scrolling up) the view snaps to the original extent at zoom 1.

<!DOCTYPE html>
<html>
<head>
<title>Restricted zoom behavior in d3</title>
<meta charset="utf-8">
<script src="http://d3js.org/d3.v3.min.js"></script>
<style>
</style>
</head>
<body>
<script>
// first, define your viewport dimensions
var width = 960,
height = 500;
// then, create your svg element and a <g> container
// for all of the transformed content
var svg = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height)
.style("background-color", randomColor),
g = svg.append("g");
// then, create the zoom behvavior
var zoom = d3.behavior.zoom()
// only scale up, e.g. between 1x and 50x
.scaleExtent([1, 50])
.on("zoom", function() {
// the "zoom" event populates d3.event with an object that has
// a "translate" property (a 2-element Array in the form [x, y])
// and a numeric "scale" property
var e = d3.event,
// now, constrain the x and y components of the translation by the
// dimensions of the viewport
tx = Math.min(0, Math.max(e.translate[0], width - width * e.scale)),
ty = Math.min(0, Math.max(e.translate[1], height - height * e.scale));
// then, update the zoom behavior's internal translation, so that
// it knows how to properly manipulate it on the next movement
zoom.translate([tx, ty]);
// and finally, update the <g> element's transform attribute with the
// correct translation and scale (in reverse order)
g.attr("transform", [
"translate(" + [tx, ty] + ")",
"scale(" + e.scale + ")"
].join(" "));
});
// then, call the zoom behavior on the svg element, which will add
// all of the necessary mouse and touch event handlers.
// remember that if you call this on the <g> element, the even handlers
// will only trigger when the mouse or touch cursor intersects with the
// <g> elements' children!
svg.call(zoom);
// then, let's add some circles
var circle = g.selectAll("circle")
.data(d3.range(300).map(function(i) {
return {
x: Math.random() * width,
y: Math.random() * height,
r: .01 + Math.random() * 50,
color: randomColor()
};
}).sort(function(a, b) {
return d3.descending(a.r, b.r);
}))
.enter()
.append("circle")
.attr("fill", function(d) { return d.color; })
.attr("cx", function(d) { return d.x; })
.attr("cy", function(d) { return d.y; })
.attr("r", function(d) { return d.r; });
function randomColor() {
return "hsl(" + ~~(60 + Math.random() * 180) + ",80%,60%)";
}
</script>
</body>
</html>
@ghiden
Copy link

ghiden commented Jan 4, 2014

Thank you. This gist saved my day.
I made a plunker with your name.
http://embed.plnkr.co/T0g2lK/preview

@campreb
Copy link

campreb commented Aug 5, 2014

Likewise, this saved me from a lot of frustration, thank you 😃

@sergzak022
Copy link

Thank you!

@johnwhelchel
Copy link

johnwhelchel commented Feb 9, 2017

Nice stuff. If anyone like me wanted this to be bounded only in one dimension, i.e. zooming out until the max height is reached, but allowing that to be at any x-value (i.e. any width), you can just replace the tx line with
tx = e.translate[0] to not constrain it. This allows you to use zoom with very wide charts. Also, if you're using the newest version of d3 (V4), you'll need to change a few things:

var e = d3.event,
    tx = Math.min(0, Math.max(e.transform.x, width - width * e.transform.k)),
    ty = Math.min(0, Math.max(e.transform.y, height - height * e.transform.k));
    d3.selectAll('.toScale').attr('transform', [
        "translate(" + [tx, ty] + ")",
        "scale(" + e.transform.k + ")"
   ].join(" "));

@guanw
Copy link

guanw commented Jul 20, 2017

Is there an example for scaling down?

@mehulumistry
Copy link

Thank a lot, you saved my life

@adriantdf
Copy link

Thank you!

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