This is an example of adding simple interface for single axis zoom and pan capability using the mouse. When the mouse is over the x
or y
axes, the panning and zooming with the mouse will execute on only those axes. When the mouse is over the main plotting canvas, pan and zoom with the mouse works on both axes. Feel free to comment on the gist.
Last active
February 25, 2018 20:23
-
-
Save jgbos/9752277 to your computer and use it in GitHub Desktop.
Simple Single Axis Zoom
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<!DOCTYPE html> | |
<meta charset="utf-8"> | |
<title>Simpe Single Axis Zoom</title> | |
<script src="http://d3js.org/d3.v3.min.js"></script> | |
<style> | |
.axis path, .axis line { | |
fill: none; | |
stroke: #000; | |
shape-rendering: crispEdges; | |
} | |
</style> | |
<body> | |
<div id="chart"></div> | |
<script> | |
var ex_chart = example().zoom(true); | |
var data = []; | |
for (var i = 0; i < 100; i++) { | |
data.push([Math.random(), Math.random()]); | |
} | |
d3.select('#chart') | |
.append("svg").attr("width", window.innerWidth).attr("height",window.innerHeight) | |
.datum(data).call(ex_chart); | |
function example() { | |
var svg; | |
var margin = { | |
top: 60, | |
bottom: 80, | |
left: 60, | |
right: 0 | |
}; | |
var width = 500; | |
var height = 400; | |
var xaxis = d3.svg.axis(); | |
var yaxis = d3.svg.axis(); | |
var xscale = d3.scale.linear(); | |
var yscale = d3.scale.linear(); | |
var zoomable = true; | |
var xyzoom = d3.behavior.zoom() | |
.x(xscale) | |
.y(yscale) | |
.on("zoom", zoomable ? draw : null); | |
var xzoom = d3.behavior.zoom() | |
.x(xscale) | |
.on("zoom", zoomable ? draw : null); | |
var yzoom = d3.behavior.zoom() | |
.y(yscale) | |
.on("zoom", zoomable ? draw : null); | |
function chart(selection) { | |
selection.each(function(data) { | |
svg = d3.select(this).selectAll('svg').data([data]); | |
svg.enter().append('svg'); | |
var g = svg.append('g') | |
.attr("transform", "translate(" + margin.left + "," + margin.top + ")"); | |
g.append("defs").append("clipPath") | |
.attr("id", "clip") | |
.append("rect") | |
.attr("width", width - margin.left - margin.right) | |
.attr("height", height - margin.top - margin.bottom); | |
g.append("svg:rect") | |
.attr("class", "border") | |
.attr("width", width - margin.left - margin.right) | |
.attr("height", height - margin.top - margin.bottom) | |
.style("stroke", "black") | |
.style("fill", "none"); | |
g.append("g").attr("class", "x axis") | |
.attr("transform", "translate(" + 0 + "," + (height - margin.top - margin.bottom) + ")"); | |
g.append("g").attr("class", "y axis"); | |
g.append("g") | |
.attr("class", "scatter") | |
.attr("clip-path", "url(#clip)"); | |
g | |
.append("svg:rect") | |
.attr("class", "zoom xy box") | |
.attr("width", width - margin.left - margin.right) | |
.attr("height", height - margin.top - margin.bottom) | |
.style("visibility", "hidden") | |
.attr("pointer-events", "all") | |
.call(xyzoom); | |
g | |
.append("svg:rect") | |
.attr("class", "zoom x box") | |
.attr("width", width - margin.left - margin.right) | |
.attr("height", height - margin.top - margin.bottom) | |
.attr("transform", "translate(" + 0 + "," + (height - margin.top - margin.bottom) + ")") | |
.style("visibility", "hidden") | |
.attr("pointer-events", "all") | |
.call(xzoom); | |
g | |
.append("svg:rect") | |
.attr("class", "zoom y box") | |
.attr("width", margin.left) | |
.attr("height", height - margin.top - margin.bottom) | |
.attr("transform", "translate(" + -margin.left + "," + 0 + ")") | |
.style("visibility", "hidden") | |
.attr("pointer-events", "all") | |
.call(yzoom); | |
// Update the x-axis | |
xscale.domain(d3.extent(data, function(d) { | |
return d[0]; | |
})) | |
.range([0, width - margin.left - margin.right]); | |
xaxis.scale(xscale) | |
.orient('bottom') | |
.tickPadding(10); | |
svg.select('g.x.axis').call(xaxis); | |
// Update the y-scale. | |
yscale.domain(d3.extent(data, function(d) { | |
return d[1]; | |
})) | |
.range([height - margin.top - margin.bottom, 0]); | |
yaxis.scale(yscale) | |
.orient('left') | |
.tickPadding(10); | |
svg.select('g.y.axis').call(yaxis); | |
draw(); | |
}); | |
return chart; | |
} | |
function update() { | |
var gs = svg.select("g.scatter"); | |
var circle = gs.selectAll("circle") | |
.data(function(d) { | |
return d; | |
}); | |
circle.enter().append("svg:circle") | |
.attr("class", "points") | |
.style("fill", "steelblue") | |
.attr("cx", function(d) { | |
return X(d); | |
}) | |
.attr("cy", function(d) { | |
return Y(d); | |
}) | |
.attr("r", 4); | |
circle.attr("cx", function(d) { | |
return X(d); | |
}) | |
.attr("cy", function(d) { | |
return Y(d); | |
}); | |
circle.exit().remove(); | |
} | |
function zoom_update() { | |
xyzoom = d3.behavior.zoom() | |
.x(xscale) | |
.y(yscale) | |
.on("zoom", zoomable ? draw : null); | |
xzoom = d3.behavior.zoom() | |
.x(xscale) | |
.on("zoom", zoomable ? draw : null); | |
yzoom = d3.behavior.zoom() | |
.y(yscale) | |
.on("zoom", zoomable ? draw : null); | |
svg.select('rect.zoom.xy.box').call(xyzoom); | |
svg.select('rect.zoom.x.box').call(xzoom); | |
svg.select('rect.zoom.y.box').call(yzoom); | |
} | |
function draw() { | |
svg.select('g.x.axis').call(xaxis); | |
svg.select('g.y.axis').call(yaxis); | |
update(); | |
zoom_update(); | |
}; | |
// X value to scale | |
function X(d) { | |
return xscale(d[0]); | |
} | |
// Y value to scale | |
function Y(d) { | |
return yscale(d[1]); | |
} | |
chart.zoom = function (_){ | |
if (!arguments.length) return zoomable; | |
zoomable = _; | |
return chart; | |
} | |
return chart; | |
} | |
</script> |
Thank you for the example.
How is this achievable in D3 4.0?
@vertighel, have a look at this v4 zooming example, but instead of
view.attr("transform", d3.event.transform);
try something like
view.attr('transform', `scale(${d3.event.transform.k}, 1)`);
The trick is that the transform uses a single scale k for both x and y, so you need to force the y scale to stay 1 with a custom transform. Note that the transform I added doesn't include translation, which you'd need to add for panning.
Great exemple : can you make the same in v4 ?
Great example! I wanna make it so that the x-axis is not scalable but only scrollable (i.e one should be able to click and drag it left and right but not change the x-scale of it), how would I go about doing that? I have tried almost everything so far.
Great example, many thanks!
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Thanks for this. It really helped me with a project I am working on.
(Note also, a small typo in the html
<title>
tag:Simpe
should beSimple
)