3

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();
}
Pepe G-a
  • 75
  • 1
  • 6
  • any one interested in this should see this awesome demo that accomplishes it for all the graph force variables: https://bl.ocks.org/steveharoz/8c3e2524079a8c440df60c1ab72b5d03 – Frazer Kirkman May 30 '17 at 10:57

1 Answers1

5

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.

Community
  • 1
  • 1
altocumulus
  • 21,179
  • 13
  • 61
  • 84
  • I am having trouble updating the forcecollide function. The graph is first initialized and then changes when the user clicks. During this change the size of some nodes is modified simulation forces are recalled following your suggestion. All of the forces work except for forceCollide which is not updating to account for the new radius. I am editing the question to include some code. – Pepe G-a Aug 19 '16 at 15:30
  • 1
    @PepeG-a That is a question of its own. Please don't edit this in your question as it will only confuse readers. Rather post a new one and put a reference back to this one in it. I am going to look into it once you posted it. – altocumulus Aug 19 '16 at 15:41
  • You can easily get a reference using `simulation.force("link")` that's why they are named. – Cool Blue Mar 25 '17 at 23:49
  • @CoolBlue Thank you so much for your comment! You are absolutely right! I am not sure, how that could have possibly escaped me. I guess, sometimes you are just too much into your own solution to notice the other, more obvious one. Yet, my core statement was *you need a reference*, and I am glad, I can stand by that ;-) – altocumulus Mar 26 '17 at 19:46
  • @altocumulus, maybe I'm getting old but I get that all the time when learning new stuff ;) there are a lot of changes in v4 and especially the force simulation; a lot to take on board. Another thing I would suggest is using `forceLink.initialize(simulation.nodes())` to update a force that has a function accessor. This will recalculate the internal array of values also. – Cool Blue Mar 26 '17 at 20:55
  • @CoolBlue I'll give you the first one, but your second suggestion regarding initialization was covered by my [answer](http://stackoverflow.com/a/39043475/4235784) to OP's follow-up question [*"d3-force update radius of forceCollide after initializing graph"*](/q/39043121) ;-) – altocumulus Mar 26 '17 at 21:17
  • Ha! That's probably where I got it from! :q – Cool Blue Mar 26 '17 at 23:40