Skip to content

Instantly share code, notes, and snippets.

@jstcki
Last active June 1, 2016 07:35
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 jstcki/7c648f0d401cb283d09fd37a61e1a9c7 to your computer and use it in GitHub Desktop.
Save jstcki/7c648f0d401cb283d09fd37a61e1a9c7 to your computer and use it in GitHub Desktop.
Draggable Blob II

Drag the blob to deform it. Press the shift key to show the underlying force simulation's nodes and links.

Uses d3-shape's line() and curveBasisClosed() functions to draw a blob from a graph.

This version tries to prevent the blob from collapsing using polygonHull() but this results in the removal of points and leads to jumpiness (you see the dragged point getting removed when you hold the shift key and drag it inside the blob). See the version without it for smoother dragging.

<!DOCTYPE html>
<meta charset="utf-8">
<canvas width="960" height="500"></canvas>
<script src="https://d3js.org/d3.v4.0.0-alpha.44.min.js"></script>
<script>
var canvas = document.querySelector("canvas"),
context = canvas.getContext("2d"),
width = canvas.width,
height = canvas.height
debug = false;
var numPoints = 5
var radius = 200
var nodes = d3.range(numPoints).map(function(d) {
return {
id: d,
x: width / 2 + radius * Math.cos(Math.PI * 2 / numPoints * d) + Math.random() * 30 - 15,
y: height / 2 + radius * Math.sin(Math.PI * 2 / numPoints * d) + Math.random() * 30 - 15
};
});
var links = nodes.map(function(n) {
return {
source: n.id,
target: n.id + 1 === nodes.length ? 0 : n.id + 1
}
});
var drawShape = d3.line()
//.x(function(d) { return d.x; })
//.y(function(d) { return d.y; })
.curve(d3.curveBasisClosed)
.context(context);
var simulation = d3.forceSimulation()
.force("link", d3.forceLink().id(function(d) { return d.id; }).strength(0.1))
.force("charge", d3.forceManyBody().strength(-1000))
.force("x", d3.forceX(function(d) { return d.x; }).strength(0.05))
.force("y", d3.forceY(function(d) { return d.y; }).strength(0.05));
render({
nodes: nodes,
links: links
});
function render(graph) {
simulation
.nodes(graph.nodes)
.on("tick", ticked);
simulation.force("link")
.links(graph.links);
d3.select(canvas)
.call(d3.drag()
.container(canvas)
.subject(dragsubject)
.on("start", dragstarted)
.on("drag", dragged)
.on("end", dragended));
d3.select("body")
.on("keydown", toggleDebug)
.on("keyup", toggleDebug);
function ticked() {
var hull = d3.polygonHull(graph.nodes.map(function(d) { return [d.x, d.y]; }));
context.clearRect(0, 0, width, height);
context.beginPath();
// Draw the shape from all nodes
drawShape(hull);
context.fillStyle = "#f55";
context.fill();
if (debug) {
context.beginPath();
graph.links.forEach(drawLink);
context.strokeStyle = "#003B5C";
context.stroke();
context.beginPath();
hull.forEach(function(d) { drawNode({x: d[0], y: d[1]}); });
context.fillStyle = "#003B5C";
context.fill();
context.strokeStyle = "#fff";
context.stroke();
context.beginPath();
context.fillStyle = "#03b700";
context.fill();
context.strokeStyle = "#fff";
context.stroke();
}
}
function toggleDebug() {
debug = d3.event.shiftKey;
ticked();
}
}
function dragsubject() {
return simulation.find(d3.event.x, d3.event.y);
}
function dragstarted() {
if (!d3.event.active) simulation.alphaTarget(0.5).restart()
simulation.fix(d3.event.subject);
}
function dragged() {
simulation.fix(d3.event.subject, d3.event.x, d3.event.y);
}
function dragended() {
if (!d3.event.active) simulation.alphaTarget(0);
simulation.unfix(d3.event.subject);
}
function drawLink(d) {
context.moveTo(d.source.x, d.source.y);
context.lineTo(d.target.x, d.target.y);
}
function drawNode(d) {
context.moveTo(d.x + 3, d.y);
context.arc(d.x, d.y, 3, 0, 2 * Math.PI);
}
</script>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment