Reputation: 121
I am trying to update nodes and links but would like to not use d3's enter pattern. The reason is that I want the svelte framework to do this instead as well as handle all the rendering, I just want to use d3-force for calculations.
I get the initial render to work just fine, but adding links and nodes has the following issues:
Here's my functions for adding nodes and links:
function addNode(){
force.forceSimulation(data.nodes.push({"id": "116", "group": 5, "index":data.nodes.length, "x":0, "y":0, "vx":0, "vy":0}))
data=data
graph.alpha(1.0).update()
graph.restart()
}
function addLink(){
let n=getRandomInt(0, data.nodes.length-1)
let tn=getRandomInt(0, n)
let sn=getRandomInt(n, data.nodes.length-1)
graph.force("link", force.forceLink(data.links.push({'source':data.nodes[sn], 'target':data.nodes[tn],'index':data.links.length, 'value':getRandomInt(1, 7)})))
data=data
graph.alpha(1.0).update()
graph.restart()
}
I found this answer but they use merge and tie it to DOM elements. I don't understand how I can do this without relating to the DOM, just updating the array of nodes and links in javascript to have d3-force include it in the simulation.
Here I have the current simulation in a svelte REPL, you can fork and edit it.
Upvotes: 2
Views: 1545
Reputation: 38211
Luckily disassociating a d3-force layout from the DOM is fairly easy: the force layout itself has no interaction with the DOM, it simply is a physics calculation based on some data's properties. Adding and removing data points (nodes/links) can be a bit tricky though, but is the same regardless of whether D3 renders the DOM, something else does, or you don't render the force at all.
Here's where you add links and nodes:
function addNode(){
force.forceSimulation(data.nodes.push({"id": "116", "group": 5, "index":data.nodes.length, "x":0, "y":0, "vx":0, "vy":0}))
data=data
graph.alpha(1.0).update()
graph.restart()
}
function addLink(){
let n=getRandomInt(0, data.nodes.length-1)
let tn=getRandomInt(0, n)
let sn=getRandomInt(n, data.nodes.length-1)
graph.force("link", force.forceLink(data.links.push({'source':data.nodes[sn], 'target':data.nodes[tn],'index':data.links.length, 'value':getRandomInt(1, 7)})))
data=data
graph.alpha(1.0).update()
graph.restart()
}
There are a few issues here:
Array.push() does not return an array. It modifies an array in place, returning the length of the array after pushing an item. This means you aren't actually adding nodes to the force layout. This will cause issues as the force layout requires objects rather than primitives to represent nodes. Instead just push the node/link, then pass the node/link array to .nodes() or .links()
force.forceSimulation() will create a new force layout generator, this is not what you want. You want to add nodes to the existing nodes, so we can use graph.nodes()
instead.
There is no force.update()
, this causes an error and is why you are unable to restart the simulation once it is done cooling down. We can just drop this part.
Let's see what these two functions look like correcting for this:
function addNode(){
data.nodes.push({"id": "116", "group": 5, "index":data.nodes.length, "x":0, "y":0, "vx":0, "vy":0})
graph.nodes(data.nodes)
data=data
graph.alpha(1.0).restart()
}
function addLink(){
let n=getRandomInt(0, data.nodes.length-1)
let tn=getRandomInt(0, n)
let sn=getRandomInt(n, data.nodes.length-1)
data.links.push({'source':data.nodes[sn], 'target':data.nodes[tn],'index':data.links.length, 'value':getRandomInt(1, 7)})
graph.force("link", force.forceLink(data.links))
data=data
graph.alpha(1.0).restart()
}
I'm not sure why you have data=data
, I see no difference without it, I'll quietly assume it's a quirk of the framework
A small alternative for updating links:
You can access the force you've named 'link' and assign it new links with:
graph.force("link").links(data.links)
Rather than:
graph.force("link", force.forceLink(data.links))
The latter recreates a force, where as the first simply modifies it.
Upvotes: 2