Pepe G-a
Pepe G-a

Reputation: 75

D3-Force updating parameters after initializing graph

In D3 v4 with the force module, how do you update the parameters of the simulation once you have initialized the graph?

More precisely, I am trying to change the .forceLink and .forceManyBody of the force directed graph when the user clicks one of its nodes.

 var node = svg
    .append("g")
    .attr("class", "gnodes")
    .selectAll(".node")
    .data(graph.nodes)
    .enter()
    .append("g")
    .attr("class", "node")
    .on('dblclick', connectedNodes); //calls for change in simulation parameters

So far I have been able to update it by duplicating the simulation under the connectedNodes function:

function connectedNodes() {

//new parameters
linkDistance = 5;
fCharge = -10;

//replicates the initial simulation code
simulation = d3.forceSimulation()
    .force("link", d3.forceLink()
        .id(function(d) {return d.id;})
        .distance(linkDistance)
        )
    .force("collide", d3.forceCollide()
        .radius(function(d){return d.r + 10})
        .strength(1)
        )
    .force("charge", d3.forceManyBody()
        .strength(fCharge)
        )
    .force("center", d3.forceCenter(width / 2, height / 2));

simulation.nodes(graph.nodes).on("tick", ticked);

simulation.force("link").links(graph.links);

Although this works it is very redundant. Is there a way in which the simulation can be refreshed with the new parameters? I have tried the following but it does not work

function connectedNodes() { 

//new parameters
linkDistance = 5;
fCharge = -10;

//restart simulation with new parameters
simulation.restart();
}

Upvotes: 3

Views: 3086

Answers (1)

altocumulus
altocumulus

Reputation: 21578

You need a reference to the forces you would like to update. This can be done using either of two ways:

  1. As pointed out by Cool Blue in their comment, you can easily get a reference to the force by calling simulation.force() passing in just the name of the force it was registered with in the first place. If we had, supposedly, created our simulation while passing in an anonymous, in-place force like so:

    var simulation = d3.forceSimulation()
      .force("link", d3.forceLink()            // "link" is the name to register the force
        .id(function(d) { return d.id; })
        .distance(linkDistance)
      );
    

    Later on the force can be obtained by its name whenever needed:

    var forceLink = simulation.force("link");  // Get the force by its name
    

    Personally, I like this approach and would prefer it over the second one, whenever possible, because I do not like having to many references / variables around.

  2. Keep a reference to the force when creating it.

    var forceLink = d3.forceLink()      // Keep a reference to the force
      .id(function(d) { return d.id; })
      .distance(linkDistance);
    
    var simulation = d3.forceSimulation()
      .force("link", forceLink )        // Pass the reference when creating the simulation
    

No matter, which way you chose, you may then update your force by doing something like

linkDistance += 10;
forceLink.distance(linkDistance);

This will take the new value into account once the next tick is calculated. If the simulation has already come to a stop or you just want to heat it up again you may call

simulation.alpha(1).restart();

I have set up an example which shows these live updates when you hover over the SVG. This will update the linkDistance and restart the force layout.

Upvotes: 5

Related Questions