Skip to content

Instantly share code, notes, and snippets.

@alex-ketch
Forked from mbostock/.block
Last active June 18, 2018 20:19
Show Gist options
  • Save alex-ketch/5e78724d23b99bda5c46efc27baf0d8c to your computer and use it in GitHub Desktop.
Save alex-ketch/5e78724d23b99bda5c46efc27baf0d8c to your computer and use it in GitHub Desktop.
Drag + Zoom
license: gpl-3.0

An example of how to combine d3.behavior.drag and d3.behavior.zoom, using stopPropagation to allow the drag behavior to take precedence over panning. Use the mouse to pan by clicking on the background, or drag by clicking on individual dots; you may also use the mousewheel to zoom.

Note: combining these two behaviors means that gesture interpretation is ambiguous and highly sensitive to position. A click on a circle is interpreted as dragging that circle, whereas a click one pixel away could be interpreted as panning the background. A more robust method of combining these behaviors is to employ modality. For example, if the user holds down the SPACE key, clicking and dragging is interpreted as panning, rather than dragging, regardless of the click location. This approach is commonly employed in commercial software such as Adobe Photoshop.

This example was created in response to a Stack Overflow question.

100 80
80 69
130 75
90 88
110 83
140 99
60 72
40 42
120 108
70 48
50 56
<!DOCTYPE html>
<meta charset="utf-8">
<style>
.dot circle {
fill: lightsteelblue;
stroke: steelblue;
stroke-width: 1.5px;
}
.dot circle.dragging {
fill: red;
stroke: brown;
}
.axis line {
fill: none;
stroke: #ddd;
shape-rendering: crispEdges;
}
</style>
<body>
<script src="https://d3js.org/d3.v5.min.js"></script>
<script>
var margin = {top: -5, right: -5, bottom: -5, left: -5},
width = 960 - margin.left - margin.right,
height = 500 - margin.top - margin.bottom;
var zoom = d3.zoom()
.scaleExtent([1, 10])
.on("zoom", zoomed);
var drag = d3.drag()
.on("start", dragstarted)
.on("drag", dragged)
.on("end", dragended);
var svg = d3.select("body").append("svg")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.right + ")")
.call(zoom);
var rect = svg.append("rect")
.attr("width", width)
.attr("height", height)
.style("fill", "none")
.style("pointer-events", "all");
var container = svg.append("g");
container.append("g")
.attr("class", "x axis")
.selectAll("line")
.data(d3.range(0, width, 10))
.enter().append("line")
.attr("x1", function(d) { return d; })
.attr("y1", 0)
.attr("x2", function(d) { return d; })
.attr("y2", height);
container.append("g")
.attr("class", "y axis")
.selectAll("line")
.data(d3.range(0, height, 10))
.enter().append("line")
.attr("x1", 0)
.attr("y1", function(d) { return d; })
.attr("x2", width)
.attr("y2", function(d) { return d; });
function dottype(d) {
d.x = +d.x;
d.y = +d.y;
return d;
}
var dotData = `100 80
80 69
130 75
90 88
110 83
140 99
60 72
40 42
120 108
70 48
50 56
`;
// var parsedDots = d3.tsvParse("dots.tsv", dottype);
var parsedDots = d3.tsvParseRows(dotData, dottype);
container
.append("g")
.attr("class", "dot")
.selectAll("circle")
.data(parsedDots)
.enter()
.append("circle")
.attr("r", 5)
.attr("cx", function(d) {
return d[0];
})
.attr("cy", function(d) {
return d[1];
})
.call(drag);
function zoomed() {
container.attr("transform", "translate(" + d3.event.translate + ")scale(" + d3.event.scale + ")");
}
function dragstarted(d) {
d3.event.sourceEvent.stopPropagation();
d3.select(this).classed("dragging", true);
}
function dragged(d) {
d3.select(this).attr("cx", d.x = d3.event.x).attr("cy", d.y = d3.event.y);
}
function dragended(d) {
d3.select(this).classed("dragging", false);
}
</script>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment