Skip to content

Instantly share code, notes, and snippets.

@mbostock mbostock/.block
Last active Dec 27, 2016

Embed
What would you like to do?
Circle Dragging III
license: gpl-3.0

This is a variant of dragging Canvas circles that uses better hit-testing. Rather than choose the top circle that contains the active pointer, this custom drag subject chooses the circle closest to the active pointer. Even better, this allows increasing the search radius for the closest circle, such that the pointer doesn’t need to be inside a circle. This is equivalent to using an SVG Voronoi overlay, and is similar to Tovi Grossman’s bubble cursor.

This implementation uses a linear scan. For force-directed layouts, see simulation.find. See also quadtree.find, though note that a quadtree will only be faster than a linear scan if you can amortize the cost of building the quadtree, such as by using quadtree.remove and quadtree.add to update the circle that is being dragged.

<!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() {
var i = 0,
n = circles.length,
dx,
dy,
d2,
s2 = radius * radius * 4, // Double the radius.
circle,
subject;
for (i = 0; i < n; ++i) {
circle = circles[i];
dx = d3.event.x - circle.x;
dy = d3.event.y - circle.y;
d2 = dx * dx + dy * dy;
if (d2 < s2) subject = circle, s2 = d2;
}
return subject;
}
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
You can’t perform that action at this time.