Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to stop a d3 force graph layout simulation?

Once you d3.layout.force()....start() a force layout simulation in D3, it keeps on going with also an active event.

I want to set a timeout of 5 seconds to let the graph force layout simulation take shape and stop ( calling .stop() or alpha(0). This works to stop, but as soon as I drag a node the simulation starts again.

The following code, also on easily testable from jsfiddle, shows at the last line that the stop() will be issued and the simulation immediately stops, but as soon as you drag any node, the simulation starts again. I think this is hardcoded in the force-drag. Is there a way to disable/unregister this ?

var svgContainer = d3.select("#svgContainer");

var element0a = svgContainer.append("g").attr("class","node").attr("transform","translate(100,100)");
var element0b = element0a.append("rect").attr("x",0).attr("y",0).attr("width",20).attr("height",10).attr("fill","red");

var element1a = svgContainer.append("g").attr("class","node").attr("transform","translate(100,200)");
var element1b = element1a.append("rect").attr("x",0).attr("y",0).attr("width",20).attr("height",10).attr("fill","green");

var element2a = svgContainer.append("g").attr("class","node").attr("transform","translate(100,300)");
var element2b = element2a.append("rect").attr("x",0).attr("y",0).attr("width",20).attr("height",10).attr("fill","blue");

var nodeArray = new Array();
nodeArray[0] = { id : "000", label : "label 000", ui : element0a };
nodeArray[1] = { id : "001", label : "label 001", ui : element1a };
nodeArray[2] = { id : "002", label : "label 002", ui : element2a };

var linkArray = new Array();

var force = self.force = d3.layout.force()
    .nodes(nodeArray)
    .links(linkArray)
    .gravity(.05)
    .distance(80)
    .charge(-100)
    .size([600, 600])
    .start();

var node = svgContainer.selectAll("g.node")
    .data(nodeArray)
    .call(force.drag);


force.on("tick", function() {
      node.attr("transform", function(d) {return "translate(" + d.x + "," + d.y + ")";});
    });

// HERE !!!! Without the stop() the simulation goes fine. With the stop() the simulation will immediately stop, but will continue as soon as you drap something. I want the simulation to never start again. 

force.stop();

This is a screenshot of the example:

enter image description here

like image 902
gextra Avatar asked Dec 26 '22 05:12

gextra


2 Answers

The simulation is restarted in the function that is called when a node is dragged internally. You can however overwrite this behaviour. The event name is "drag.force" and you can access it through force.drag. Instead of the usual behaviour, you can modify the coordinates directly without restarting the simulation like this:

var node...
  .call(force.drag().on("drag.force", function() {
    d3.select(this).attr("transform", "translate(" + d3.event.x + "," + d3.event.y + ")");
  }));

This corresponds to the normal drag behavior. Before you do this, it is advisable to stop the simulation to prevent the drag event and force ticks from interfering. Simply call force.stop() at the beginning of your modified event handler.

Finally you'll need to reset the origin for the drag events, otherwise you'll notice that dragged elements "jump" back to their original position when you drag them again. This is done in similar fashion to modifying the event handler. You simply get the current translation and return the coordinates in the expected format.

var node...
  .call(force.drag().origin(function() {
    var t = d3.transform(d3.select(this).attr("transform")).translate;
    return {x: t[0], y: t[1]};
  }));

The complete code is in the updated jsfiddle here.

like image 173
Lars Kotthoff Avatar answered Feb 14 '23 16:02

Lars Kotthoff


Setting up an appropiate "friction" should do it, though you don't control the time it will need to stop:

var force = self.force = d3.layout.force()
  .nodes(nodeArray)
  .links(linkArray)
  .gravity(.05)
  .distance(80)
  .charge(-100)
  .friction(0.5)
  .size([600, 600])
  .start();
like image 22
Marjancek Avatar answered Feb 14 '23 16:02

Marjancek