Skip to content

Instantly share code, notes, and snippets.

@micahstubbs
Created September 11, 2017 04:43
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
Star You must be signed in to star a gist
Embed
What would you like to do?
Multiple Shape Drag Canvas
license: MIT
border: no

a Canvas example that shows how to drag multiple shapes inside of another shape. notice that the red circles can be dragged independently. notice that the black rectangle can also be dragged, keeping it's red-circle children in the same relative positions.

you could also think about this problem as "how to create a hierarchy of draggable shapes?". the short answer is: do it in the dragSubject() function d3-drag docs on drag subjects

a fork of Multiple Shape Drag SVG

an iteration on this very helpful stackoverflow answer

this collection of d3-drag experiments also exist in github repo form at micahstubbs/d3-drag-experiments

<!DOCTYPE html>
<html>
<head>
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/4.10.2/d3.min.js"></script>
<meta charset="utf-8">
<title>d3-drag-experiments</title>
</head>
<body>
<script src="vis.js"></script>
</body>
</html>
/* prettier-ignore */
d3.select('body')
.append('canvas')
.attr('width', 960)
.attr('height', 500);
const canvas = d3.select('canvas');
const context = canvas.node().getContext('2d');
const width = canvas.property('width');
const height = canvas.property('height');
const circles = [{ x: 50, y: 60, radius: 10 }, { x: 100, y: 80, radius: 10 }];
const rects = [{ x: 10, y: 10, x2: 210, y2: 210 }];
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);
// draw the black rectangle
rects.forEach(rect => {
context.fillStyle = 'black';
context.clearRect(0, 0, width, height);
context.fillRect(rect.x, rect.y, rect.x2, rect.y2);
// draw the red circles
circles.forEach(circle => {
context.beginPath();
context.moveTo(circle.x + rect.x + circle.radius, circle.y + rect.y);
context.arc(
circle.x + rect.x,
circle.y + rect.y,
circle.radius,
0,
2 * Math.PI
);
context.fillStyle = 'red';
context.fill();
});
});
}
function dragSubject() {
let i;
const n = circles.length;
let dx;
let dy;
let d2;
const radius = 10;
let s2 = radius * radius * 4; // Double the radius
let circle;
let subject;
for (i = 0; i < n; i += 1) {
circle = circles[i];
console.log('circle from dragSubject', circle);
dx = d3.event.x - circle.x - rects[0].x;
dy = d3.event.y - circle.y - rects[0].y;
d2 = dx * dx + dy * dy;
console.log('dx', dx);
console.log('dy', dy);
console.log('d2', d2);
console.log('s2', s2);
if (d2 < s2) {
subject = circle;
s2 = d2;
} else if (typeof subject === 'undefined') {
rect = rects[0];
subject = rect;
}
}
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;
}
// const group = container.append('g');
// const rect = group
// .append('rect')
// .attr('width', w)
// .attr('height', h)
// .attr('x', 10)
// .attr('yx', 10);
// const circle1 = group
// .append('circle')
// .attr('cx', 50)
// .attr('cy', 50)
// .attr('r', 10)
// .style('fill', 'red');
// const circle2 = group
// .append('circle')
// .attr('cx', 100)
// .attr('cy', 70)
// .attr('r', 10)
// .style('fill', 'red');
// circle1.call(drag);
// circle2.call(drag);
// group.call(drag);
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment