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.
Inspired by Mike Bostock's Force Dragging III example.
xxxxxxxxxx
<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() {
context.clearRect(0, 0, width, height);
context.beginPath();
// Draw the shape from all nodes
drawShape(graph.nodes);
context.fillStyle = "#f55";
context.fill();
if (debug) {
context.beginPath();
graph.links.forEach(drawLink);
context.strokeStyle = "#003B5C";
context.stroke();
context.beginPath();
graph.nodes.forEach(drawNode);
context.fillStyle = "#003B5C";
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>
https://d3js.org/d3.v4.0.0-alpha.44.min.js