Problem:
I have a simple network drawn with force network, and I would like the user to individually drag nodes around once it’s drawn. This is currently only working after a user drags each node. On first drag, dragging one node will cause the others to move around. I would like all nodes to remain in place when another node is being dragged. I tried adding the dragging functions after the simulation.stop()
, but it’s not working.
Example of this below:
const networkData = {
"nodes": [{
"id": 1,
"name": "A",
isSource: true,
"sourceX": 100,
"sourceY": 100
},
{
"id": 2,
"name": "B",
isSource: false
},
{
"id": 3,
"name": "C",
isSource: true,
"sourceX": 150,
"sourceY": 150
},
{
"id": 4,
"name": "D",
isSource: false
},
{
"id": 5,
"name": "E",
isSource: true,
"sourceX": 200,
"sourceY": 200
},
{
"id": 6,
"name": "F",
isSource: false
}
],
"links": [{
"source": 1,
"target": 2
},
{
"source": 3,
"target": 4
},
{
"source": 5,
"target": 6
}
]
}
const svg = d3.select("#my_dataviz");
const sourceNodes = networkData.nodes.filter(node => {
return node.isSource === true;
});
const targetNodes = networkData.nodes.filter(node => {
return !node.isSource;
});
const link = svg
.selectAll(".link")
.data(networkData.links)
.enter()
.append("line")
.attr("class", "link")
.attr("stroke-width", 2)
.attr("stroke", 'grey');
const targetNodeSelection = svg
.selectAll(".node")
.data(targetNodes)
.enter()
.append("circle")
.attr("class", "node")
.attr("r", 10)
.attr("fill", 'blue');
// Separate selections for source nodes
const sourceNodeSelection = svg
.selectAll(".source-node")
.data(sourceNodes)
.enter()
.append("circle")
.attr("class", "source-node")
.attr("r", 1)
.attr("fill", 'white')
.attr("cx", d => d.sourceX) // Set fixed X position for source nodes
.attr("cy", d => d.sourceY); // Set fixed Y position for source nodes
const maxIterations = 10;
let iterationCount = 0; // Initialize the iteration counter
// Create a simulation for the target nodes only
const simulation = d3.forceSimulation(networkData.nodes)
.force("link", d3.forceLink(networkData.links)
.id(function(d) {
return d.id;
})
.links(networkData.links)
)
.force("center", d3.forceCenter(400 / 2, 400 / 2))
.force('collision', d3.forceCollide().radius(20))
.on("tick", function() {
tick();
iterationCount++;
if (iterationCount >= maxIterations) {
simulation.stop();
}
});
function tick() {
link
.attr("x1", function(d) {
return d.source.sourceX;
})
.attr("y1", function(d) {
return d.source.sourceY;
})
.attr("x2", function(d) {
return d.target.x;
})
.attr("y2", function(d) {
return d.target.y;
});
targetNodeSelection
.attr("cx", function(d) {
return d.x;
})
.attr("cy", function(d) {
return d.y;
});
sourceNodeSelection
.attr("cx", function(d) {
return d.sourceX;
})
.attr("cy", function(d) {
return d.sourceY;
});
}
function clamp(x, lo, hi) {
return x < lo ? lo : x > hi ? hi : x;
}
function dragged(event, d) {
d.fx = clamp(event.x, 0, 400);
d.fy = clamp(event.y, 0, 400);
simulation.alpha(0).restart();
}
const drag = d3
.drag()
.on("drag", dragged);
targetNodeSelection.call(drag);
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/6.7.0/d3.min.js" integrity="sha512-cd6CHE+XWDQ33ElJqsi0MdNte3S+bQY819f7p3NUHgwQQLXSKjE4cPZTeGNI+vaxZynk1wVU3hoHmow3m089wA==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
<svg id="my_dataviz" height='400' width='400'></svg>
Solution:
On drag your simulation is still honoring your center
and collision
forces. You can simply null them out after the initial simulation is complete.