Skip to content

Instantly share code, notes, and snippets.

@micahstubbs
Created September 11, 2017 04:45
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 micahstubbs/f4dcb0f587e54ea80a67b3efa773b995 to your computer and use it in GitHub Desktop.
Save micahstubbs/f4dcb0f587e54ea80a67b3efa773b995 to your computer and use it in GitHub Desktop.
Multiple Shape Drag Canvas with Force Simulation
license: MIT
border: no

a Canvas example that shows how to drag multiple shapes inside of another shape. notice that the red network-node 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 Canvas

inspiration from blocks graph neighbors on mouseover and Force-Directed Graph

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 }];
const radius = 10;
//
// setup force simulation
//
const graph = {
nodes: [
{ id: 0, size: 10 },
{ id: 1, size: 5 },
{ id: 2, size: 2 },
{ id: 3, size: 3 },
{ id: 4, size: 30 },
{ id: 5, size: 40 }
],
links: [
{ source: 0, target: 1 },
{ source: 0, target: 2 },
{ source: 1, target: 0 },
{ source: 3, target: 0 },
{ source: 4, target: 1 }
]
};
const simulation = d3
.forceSimulation()
.force('link', d3.forceLink().id(d => d.id))
.force('charge', d3.forceManyBody())
.force('center', d3.forceCenter(width / 2, height / 2));
simulation.nodes(graph.nodes).on('tick', ticked);
function ticked() {
render();
}
//
//
//
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 a line for each link
context.strokeStyle = '#aaa';
context.lineWidth = 1;
context.beginPath();
graph.links.forEach(link => {
context.moveTo(
graph.nodes[link.source].x + rect.x,
graph.nodes[link.source].y + rect.y
);
context.lineTo(
graph.nodes[link.target].x + rect.x,
graph.nodes[link.target].y + rect.y
);
});
context.stroke();
// draw a circle for each node
context.beginPath();
graph.nodes.forEach(node => {
context.moveTo(node.x + rect.x + radius, node.y + rect.y);
context.arc(node.x + rect.x, node.y + rect.y, radius, 0, 2 * Math.PI);
});
context.fillStyle = 'red';
context.fill();
});
}
function dragSubject() {
let i;
const n = graph.nodes.length;
let dx;
let dy;
let d2;
let s2 = radius * radius * 4;
let node;
let subject;
for (i = 0; i < n; i += 1) {
node = graph.nodes[i];
console.log('node from dragSubject', node);
dx = d3.event.x - node.x - rects[0].x;
dy = d3.event.y - node.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 = node;
s2 = d2;
} else if (typeof subject === 'undefined') {
rect = rects[0];
subject = rect;
}
}
return subject;
}
function dragStarted() {
// if (!d3.event.active) simulation.alphaTarget(0.3).restart();
// 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;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment