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.
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() {
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>
https://d3js.org/d3.v4.0.0-alpha.44.min.js