user3510164
user3510164

Reputation: 121

how to add nodes and links do d3-force without enter

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:

  1. adding links makes the network grow
  2. the links don't seem to have any effect on the graph layout, i.e. they don't seem to be added to the force.simulation
  3. first adding nodes seems to work, but when adding nodes after adding a link they don't seem to excert forces on the other nodes.

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

Answers (1)

Andrew Reid
Andrew Reid

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:

  1. 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()

  2. 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.

  3. 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

Related Questions