Skip to content

Instantly share code, notes, and snippets.

@steve-todorov
Forked from mbostock/.block
Created December 10, 2019 17:13
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 steve-todorov/b97ba60cec6c19ed3d90d445b436d709 to your computer and use it in GitHub Desktop.
Save steve-todorov/b97ba60cec6c19ed3d90d445b436d709 to your computer and use it in GitHub Desktop.
Drag & Zoom
license: gpl-3.0

This example shows how to combine d3-drag and d3-zoom to allow dragging of individual circles within a zoomable canvas. If you click and drag on the background, the view pans; if you click and drag on a circle, it moves.

The tricky part of this example is the need to distinguish between two coordinate spaces: the world coordinates used to position the circles, and the pointer coordinates representing the mouse or touches. The drag behavior doesn’t know the view is being transformed by the zoom behavior, so you must convert between the two coordinate spaces.

Compare this to the simpler SVG implementation.

<!DOCTYPE html>
<meta charset="utf-8">
<canvas width="960" height="500"></canvas>
<script src="https://d3js.org/d3.v4.min.js"></script>
<script>
var canvas = d3.select("canvas"),
context = canvas.node().getContext("2d"),
width = canvas.property("width"),
height = canvas.property("height"),
radius = 2.5,
transform = d3.zoomIdentity;
var points = d3.range(2000).map(phyllotaxis(10));
canvas
.call(d3.drag().subject(dragsubject).on("drag", dragged))
.call(d3.zoom().scaleExtent([1 / 2, 8]).on("zoom", zoomed))
.call(render);
function zoomed() {
transform = d3.event.transform;
render();
}
function dragsubject() {
var i,
x = transform.invertX(d3.event.x),
y = transform.invertY(d3.event.y),
dx,
dy;
for (i = points.length - 1; i >= 0; --i) {
point = points[i];
dx = x - point[0];
dy = y - point[1];
if (dx * dx + dy * dy < radius * radius) {
point.x = transform.applyX(point[0]);
point.y = transform.applyY(point[1]);
return point;
}
}
}
function dragged() {
d3.event.subject[0] = transform.invertX(d3.event.x);
d3.event.subject[1] = transform.invertY(d3.event.y);
render();
}
function render() {
context.save();
context.clearRect(0, 0, width, height);
context.beginPath();
context.translate(transform.x, transform.y);
context.scale(transform.k, transform.k);
points.forEach(drawPoint);
context.fill();
context.restore();
}
function drawPoint(point) {
context.moveTo(point[0] + radius, point[1]);
context.arc(point[0], point[1], radius, 0, 2 * Math.PI);
}
function phyllotaxis(radius) {
var theta = Math.PI * (3 - Math.sqrt(5));
return function(i) {
var r = radius * Math.sqrt(i), a = theta * i;
return [
width / 2 + r * Math.cos(a),
height / 2 + r * Math.sin(a)
];
};
}
</script>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment