Skip to content

Instantly share code, notes, and snippets.

@mrcslws
Forked from mbostock/.block
Last active May 7, 2019 15:03
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save mrcslws/565393a63d5e017d095686b8fbf5a2f3 to your computer and use it in GitHub Desktop.
Save mrcslws/565393a63d5e017d095686b8fbf5a2f3 to your computer and use it in GitHub Desktop.
Pan & Zoom Axes
license: gpl-3.0

This demonstrates one way d3-zoom is quirky if you apply the zoom to an element within an SVG. Zoom in on this. Watch how it jerks to the side.

Now click "Fix it" to change away from d3-zoom's default behavior, specifying your own extent function. This fixes the issue. Zooming works as expected.

<!DOCTYPE html>
<meta charset="utf-8">
<style>
.axis path {
display: none;
}
.axis line {
stroke-opacity: 0.3;
shape-rendering: crispEdges;
}
.view {
fill: url(#gradient);
stroke: #000;
}
button {
position: absolute;
top: 20px;
left: 20px;
}
</style>
<button>Fix it</button>
<svg width="960" height="500">
<defs>
<linearGradient id="gradient" x1="0%" y1="0%" x2="100%" y2="100%">
<stop offset="0.0%" stop-color="#2c7bb6"></stop>
<stop offset="12.5%" stop-color="#00a6ca"></stop>
<stop offset="25.0%" stop-color="#00ccbc"></stop>
<stop offset="37.5%" stop-color="#90eb9d"></stop>
<stop offset="50.0%" stop-color="#ffff8c"></stop>
<stop offset="62.5%" stop-color="#f9d057"></stop>
<stop offset="75.0%" stop-color="#f29e2e"></stop>
<stop offset="87.5%" stop-color="#e76818"></stop>
<stop offset="100.0%" stop-color="#d7191c"></stop>
</linearGradient>
</defs>
</svg>
<script src="//d3js.org/d3.v4.min.js"></script>
<script>
// d3 uses the size of the entire <svg/> node to determine the zoom extent. This
// causes it to incorrectly handle the translateExtent if you're catching zoom
// events with a rect that's smaller than the SVG. But d3 lets you specify your
// own extent function, so there's a solution!
function svgRectExtent() {
var e = this, w, h;
return [[0, 0],
[e.width.baseVal.value,
e.height.baseVal.value]];
}
var padding = {top: 100, right: 100, bottom: 100, left: 100};
var svg = d3.select("svg"),
width = +svg.attr("width") - padding.top - padding.bottom,
height = +svg.attr("height") - padding.left - padding.right;
svg.append("defs")
.append('clipPath')
.attr('id', "myClipPath")
.append('rect')
.attr('width', width)
.attr('height', height);
var zoom = d3.zoom()
.scaleExtent([1, 40])
.translateExtent([[0, 0], [width, height]])
.on("zoom", zoomed);
var x = d3.scaleLinear()
.domain([0, width])
.range([0, width]);
var y = d3.scaleLinear()
.domain([0, height])
.range([0, height]);
var xAxis = d3.axisBottom(x)
.tickSize(height)
.tickPadding(8 - height);
var yAxis = d3.axisRight(y)
.ticks(4)
.tickSize(width)
.tickPadding(8 - width);
var view = svg
.append("g")
.attr("transform", "translate(" + padding.left + "," + padding.top + ")")
.style("clip-path", "url(#myClipPath)")
.append("rect")
.attr("class", "view")
.attr("width", width)
.attr("height", height);
var gX = svg.append("g")
.attr("class", "axis axis--x")
.attr("transform", "translate(" + padding.left + "," + padding.top + ")")
.call(xAxis);
var gY = svg.append("g")
.attr("class", "axis axis--y")
.attr("transform", "translate(" + padding.left + "," + padding.top + ")")
.call(yAxis);
d3.select("button")
.on("click", fixIt);
var zoomCatcher = svg.append("rect")
.attr("class", "zoomCatcher")
.attr("x", padding.left)
.attr("y", padding.top)
.attr("width", width)
.attr("height", height)
.attr("fill", "transparent")
.attr("stroke", "none")
.call(zoom);
function zoomed() {
view.attr("transform", d3.event.transform);
gX.call(xAxis.scale(d3.event.transform.rescaleX(x)));
gY.call(yAxis.scale(d3.event.transform.rescaleY(y)));
}
function fixIt() {
zoomCatcher
.call(zoom.transform, d3.zoomIdentity);
zoom.extent(svgRectExtent);
zoomCatcher.call(zoom);
}
</script>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment