I am trying to dynamically add nodes to a forceSimulation in d3. The code snippet below should first generate the nodes from a static object graph and then after 5 seconds a new node should be added to graph and the Force-directed graph in d3 should be updated with the new node. But there is an error in my implementation and instead all the nodes jump to the top-left corner. My current "solution" uses the merge syntax as described in another similar SO question: https://stackoverflow.com/a/40028002/14923227 and here. I believe this is the right way to go about it but I am not sure how to implement it with my contrived example below.
var graph = {
"nodes": [
{
"id": 1,
"name": "A"
},
{
"id": 2,
"name": "B"
},
{
"id": 3,
"name": "C"
}
],
"links": [
{
"source": 0,
"target": 1
},
{
"source": 1,
"target": 2
},
{
"source": 0,
"target": 2
}
]
};
var svg = d3.select("svg"),
width = +svg.attr("width"),
height = +svg.attr("height");
var simulation = d3.forceSimulation()
.force("charge", d3.forceManyBody().strength(-400))
.force("center", d3.forceCenter(width / 2, height / 2))
.nodes(graph.nodes)
.on("tick", ticked)
.force("link", d3.forceLink(graph.links))
var link = svg.selectAll(".link")
.data(graph.links)
.join("line")
.classed("link", true)
.style("stroke", "#aaa")
var node = svg.selectAll(".node")
.data(graph.nodes);
var g = node.enter()
.append('g')
.attr('class', 'node');
g.append('circle')
.attr("r", 20)
.style("fill", "#d9d9d9");
g.append('text')
.attr('class', 'text')
.text(function(d) { return d.name });
node.exit().remove()
function ticked() {
link
.attr("x1", function(d) { return d.source.x; })
.attr("y1", function(d) { return d.source.y; })
.attr("x2", function(d) { return d.target.x; })
.attr("y2", function(d) { return d.target.y; });
svg.selectAll('.node')
.attr("cx", function (d) { return d.x; })
.attr("cy", function(d) { return d.y; })
.attr('transform', function(d) {
return 'translate(' + d.x + ',' + d.y + ')';
})
}
// problem here: add new node after five seconds
setTimeout(() => {
graph.nodes.push({"id": 4, "name": "X"}); // new node 'X'
// this line gives error
//graph.links.push({"source": 2, "target": 3}); // connect 'X' with 'C'
// Update the nodes
node = node.data(graph.nodes);
// Enter any new nodes
nodeEnter = node.enter()
.append("g")
.attr('class', 'node');
nodeEnter.append('circle')
.attr("r", 20)
.style("fill", "#d9d9d9");
nodeEnter.append('text')
.attr('class', 'text')
.text(function(d) { return d.name });
node = nodeEnter.merge(node);
nodeEnter.exit().remove();
simulation.restart();
}, 5000)
<!DOCTYPE html>
<meta charset="utf-8">
<script src="https://d3js.org/d3.v7.min.js"></script>
<body>
<svg width="960" height="600"></svg>
</body>