I'm trying to add a new node to an existing graph in d3.
I can't find what's the problem with my code here:
This is the code for the add node:
function handleNodeClick(node) {
// Create a node connected to the clicked node
const newNode1 = {
id: "NewNode1",
label: "New Node 1",
group: "New Nodes"
};
// Create a new edge connecting the clicked node to newNode1
const newLink1 = {
source: node.id,
target: newNode1.id,
label: "New link 1"
};
// Update the graph data
graphData.nodes.push(newNode1);
graphData.links.push(newLink1);
// Update the simulation nodes and edges
simulation.nodes(graphData.nodes);
simulation.force("link").links(graphData.links);
// Restart the simulation
simulation.alpha(1).restart();
}
The issue is hard to identify at first, but after you find it it's quite obvious: you are indeed adding the new node to the simulation... but you are never drawing the node on the screen, that is, the simulation is running and calculating the position of an element you never painted in the SVG.
Have a look at your nodes
selection:
const nodes = svg.selectAll("circle")
.data(graphData.nodes)
.enter()
.append("circle")
.attr("r", 10)
.attr("fill", "blue")
That's just an "enter" selection, the new node will not be added. The same goes for the links.
An easy solution is creating a function you can pass the data (here named drawElements
with a proper D3 set of selections, which include:
Here is the demo:
// Define the graph data
const graphData = {
nodes: [{
id: "Node1"
},
{
id: "Node2"
}
],
links: [{
source: "Node1",
target: "Node2"
}]
};
// Set up the SVG container
const width = 400;
const height = 400;
const svg = d3.select("#graph")
.attr("width", width)
.attr("height", height);
// Create the simulation
const simulation = d3.forceSimulation(graphData.nodes)
.force("charge", d3.forceManyBody().strength(-100))
.force("link", d3.forceLink(graphData.links).id(d => d.id).distance(100))
.force("center", d3.forceCenter(width / 2, height / 2));
drawElements(graphData.nodes, graphData.links);
function drawElements(nodesData, linksData) {
let links = svg.selectAll("line")
.data(linksData);
links.exit().remove();
links = links.enter()
.append("line")
.attr("stroke", "black")
.attr("stroke-width", 2)
.merge(links);
// Create the nodes
let nodes = svg.selectAll("circle")
.data(nodesData);
nodes.exit().remove();
nodes = nodes.enter()
.append("circle")
.attr("r", 10)
.attr("fill", "blue")
.merge(nodes)
.on("click", handleNodeClick);
simulation.on("tick", () => {
links
.attr("x1", d => d.source.x)
.attr("y1", d => d.source.y)
.attr("x2", d => d.target.x)
.attr("y2", d => d.target.y);
nodes
.attr("cx", d => d.x)
.attr("cy", d => d.y);
});
};
function handleNodeClick(node) {
// Create A new node connected to the clicked node
const newNode1 = {
id: "NewNode1",
label: "New Node 1",
group: "New Nodes"
};
// Create a new edge connecting the clicked node to newNode1
const newLink1 = {
source: node.id,
target: newNode1.id,
label: "New link 1"
};
// Update the graph data
graphData.nodes.push(newNode1);
graphData.links.push(newLink1);
// Update the simulation nodes and edges
simulation.nodes(graphData.nodes);
simulation.force("link").links(graphData.links);
drawElements(graphData.nodes, graphData.links);
// Restart the simulation
simulation.alpha(1).restart();
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>
<svg id="graph"></svg>
This is just to get you started, there are other issues to fix (for instance, try to click a leaf node).