Skip to content

Instantly share code, notes, and snippets.

@mbostock
Last active July 5, 2019 15:44
Show Gist options
  • Save mbostock/c206c20294258c18832ff80d8fd395c3 to your computer and use it in GitHub Desktop.
Save mbostock/c206c20294258c18832ff80d8fd395c3 to your computer and use it in GitHub Desktop.
Circle Dragging II
license: gpl-3.0
redirect: https://observablehq.com/@d3/circle-dragging-ii

This example demonstrates applying d3.drag for dragging circles in Canvas. A custom drag subject performs hit-testing to determine which circle to drag, if any. The circle’s position is updated during the drag event. The circle is raised to the foreground at the start of a drag by reordering the data, and a temporary stroke is applied during drag for visual feedback.

The render function is also registered as a listener to update the display after any drag event. The display will thus be redrawn once per active pointer, but since the number of active pointers is typically small (1, maybe 2), the cost is negligible.

The hit-testing here is simple: the topmost circle that contains the active pointer is returned. A better technique is to choose the closest circle to the active pointer, allowing accurate selection even if the pointer is outside the circle.

Compare this to dragging SVG circles.

<!DOCTYPE html>
<meta charset="utf-8">
<canvas width="960" height="500"></canvas>
<script src="//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 = 32;
var circles = d3.range(20).map(function(i) {
return {
index: i,
x: Math.round(Math.random() * (width - radius * 2) + radius),
y: Math.round(Math.random() * (height - radius * 2) + radius)
};
});
var color = d3.scaleOrdinal()
.range(d3.schemeCategory20);
render();
canvas.call(d3.drag()
.subject(dragsubject)
.on("start", dragstarted)
.on("drag", dragged)
.on("end", dragended)
.on("start.render drag.render end.render", render));
function render() {
context.clearRect(0, 0, width, height);
for (var i = 0, n = circles.length, circle; i < n; ++i) {
circle = circles[i];
context.beginPath();
context.moveTo(circle.x + radius, circle.y);
context.arc(circle.x, circle.y, radius, 0, 2 * Math.PI);
context.fillStyle = color(circle.index);
context.fill();
if (circle.active) {
context.lineWidth = 2;
context.stroke();
}
}
}
function dragsubject() {
for (var i = circles.length - 1, circle, x, y; i >= 0; --i) {
circle = circles[i];
x = circle.x - d3.event.x;
y = circle.y - d3.event.y;
if (x * x + y * y < radius * radius) return circle;
}
}
function dragstarted() {
circles.splice(circles.indexOf(d3.event.subject), 1);
circles.push(d3.event.subject);
d3.event.subject.active = true;
}
function dragged() {
d3.event.subject.x = d3.event.x;
d3.event.subject.y = d3.event.y;
}
function dragended() {
d3.event.subject.active = false;
}
</script>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment