I created this fork to focus strictly on how to drag multiple nodes in a graph. I intentionally removed functionality that the original version had.
Click and drag the background area to make a rectangular selection (brushing). Once you’ve selected some nodes, drag them around to reposition the network. You might use this technique to hand-tweak a force-directed layout for better appearance, saving the manually-adjusted node positions back to a file.
Forked from mbostock's block: Draggable Network II
xxxxxxxxxx
<meta charset="utf-8">
<link href='style.css' />
<script src="//d3js.org/d3.v3.min.js"></script>
<svg id='root'></svg>
<script>
var width = 960,
height = 500,
shiftKey;
var svg = d3.select('svg#root')
.attr("width", width)
.attr("height", height);
var link = svg.append("g")
.attr("class", "link")
.selectAll("line");
var brush = svg.append("g")
.datum(() => ({ selected: false }))
.attr("class", "brush");
var node = svg.append("g")
.attr("class", "node")
.selectAll("circle");
d3.json("graph.json", (error, graph) => {
graph.links.forEach(d => {
d.source = graph.nodes[d.source];
d.target = graph.nodes[d.target];
});
link = link.data(graph.links).enter().append("line")
.attr("x1", d => d.source.x)
.attr("y1", d => d.source.y)
.attr("x2", d => d.target.x)
.attr("y2", d => d.target.y);
brush.call(d3.svg.brush()
.x(d3.scale.identity().domain([0, width]))
.y(d3.scale.identity().domain([0, height]))
.on("brush", () => {
var extent = d3.event.target.extent();
node.classed("selected", d => {
return d.selected = containedWithinExtent(d.x, d.y, extent);
});
})
.on("brushend", () => {
// Not yet clear what event.target is here
d3.event.target.clear();
d3.select(brush.node()).call(d3.event.target);
}));
node = node.data(graph.nodes).enter().append("circle")
.attr("r", 4)
.attr("cx", d => d.x)
.attr("cy", (d) => d.y)
.call(d3.behavior.drag()
.on("drag", (d) => { moveSelectedNodes(d3.event.dx, d3.event.dy); }));
});
/**
* A helper to determine when a point (x,y) is within a two-dimensional extent.
*/
function containedWithinExtent(x, y, extent) {
return (extent[0][0] <= x && x < extent[1][0]) &&
(extent[0][1] <= y && y < extent[1][1]);
}
function moveSelectedNodes(dx, dy) {
// Move selected nodes
node.filter(d => d.selected)
.attr("cx", d => d.x += dx) // NOTE: This mutates `d` (sets dx) AND sets `cx`
.attr("cy", d => d.y += dy) // NOTE: This mutates `d` (sets dy) AND sets `cy`
// Move links to selected nodes
link.filter(d => d.source.selected || d.target.selected)
.attr("x1", d => d.source.x)
.attr("y1", d => d.source.y)
.attr("x2", d => d.target.x)
.attr("y2", d => d.target.y);
}
</script>
https://d3js.org/d3.v3.min.js