Skip to content

Instantly share code, notes, and snippets.

@git-ashish
Forked from mrcslws/.block
Created May 7, 2019 15:03
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save git-ashish/c3222bd504ce6e37e43d6671b3759ef6 to your computer and use it in GitHub Desktop.
Save git-ashish/c3222bd504ce6e37e43d6671b3759ef6 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