score:40

Accepted answer

All the answers above misunderstood Øystein Amundsen's question.

The only way to stabilize the force upon it starts is to set node.x and node.y a proper value. Please note that the node is the data of d3.js, not the represented DOM type.

For example, if you load

nodes = [{"id": a}, {"id": b}, {"id": c}]

into

d3.layout.force().nodes(nodes)

you have to set all .x and .y of all elements in array of nodes it will be like this ( in coffeescript )

nodes = [{"id": a}, {"id": b}, {"id": c}]
for i in [0..2]
  nodes[i].x = 500 #the initial x position of node
  nodes[i].y = 300 #the initial y position of node
d3.layout.force().nodes(nodes).links(links)

then the nodes will start at the position when force.start(). this would avoid the chaos.

score:0

	var width = 960,
	  height = 500;

	var fill = d3.scale.category20();

	var force = d3.layout.force()
	  .size([width, height])
	  .nodes([{}]) // initialize with a single node
	  .linkDistance(30)
	  .charge(-60)
	  .on("tick", tick);

	var svg = d3.select("body").append("svg")
	  .attr("width", width)
	  .attr("height", height)
	  .on("mousedown", mousedown);

	svg.append("rect")
	  .attr("width", width)
	  .attr("height", height);

	var nodes = force.nodes(),
	  links = force.links(),
	  node = svg.selectAll(".node"),
	  link = svg.selectAll(".link");

	 // var cursor = svg.append("circle")
	 //     .attr("r", 30)
	 //     .attr("transform", "translate(-100,-100)")
	 //     .attr("class", "cursor");

	restart();

	function mousedown() {
	  var point = d3.mouse(this),
	    node = {
	      x: width / 2,
	      y: height / 2,
	      "number": Math.floor(Math.random() * 100)
	    },
	    n = nodes.push(node);

	  // add links to any nearby nodes
	  /*  nodes.forEach(function(target) {
		    var x = target.x - node.x,
		        y = target.y - node.y;
		    if (Math.sqrt(x * x + y * y) < 30) {
		      links.push({source: node, target: target});
		    }
		  });
		*/
	  restart();
	}

	function tick() {
	  link.attr("x1", function(d) {
	      return d.source.x;
	    })
	    .attr("y1", function(d) {
	      return d.source.y;
	    })
	    .attr("x2", function(d) {
	      return d.target.x;
	    })
	    .attr("y2", function(d) {
	      return d.target.y;
	    });

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

	function restart() {
	  link = link.data(links);

	  link.enter().insert("line", ".node")
	    .attr("class", "link");

	  node = node.data(nodes);

	  // node.enter().insert("circle", ".cursor")
	  //     .attr("class", "node")
	  //     .attr("r", 5)
	  //     .call(force.drag);

	  var nodeEnter = node.enter().insert("svg:g", ".cursor")
	    .attr("class", "node")
	    .call(force.drag);

	  nodeEnter.append("svg:circle")
	    .attr("r", 5)

	  nodeEnter.append("svg:text")
	    .attr("class", "textClass")
	    .attr("x", 14)
	    .attr("y", ".31em")
	    .text(function(d) {
	      return d.number;
	    });

	  force.start();
	}
	rect {
	  fill: none;
	  pointer-events: all;
	}
	.node {
	  fill: #000;
	}
	.cursor {
	  fill: none;
	  stroke: brown;
	  pointer-events: none;
	}
	.link {
	  stroke: #999;
	}
	.textClass {
	  stroke: #323232;
	  font-family: "Lucida Grande", "Droid Sans", Arial, Helvetica, sans-serif;
	  font-weight: normal;
	  stroke-width: .5;
	  font-size: 14px;
	}
	
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>

An example of what you might be looking for. It sets the x & y attributes of new nodes before inserting them into the layout. The desired location is the centre of the svg element.

score:4

Maybe force.friction(0.5), or some other number lower than the default 0.9, would help? At least it gives a less chaotic impression upon page load.

score:8

Based on other answers I have made this method:

function forwardAlpha(layout, alpha, max) {
  alpha = alpha || 0;
  max = max || 1000;
  var i = 0;
  while(layout.alpha() > alpha && i++ < max) layout.tick();
}

score:11

I dealt with something a little like this a while ago. There are a few things to consider.

1) The iterating ticks are simulating a system that comes to equilibrium. So there is no way to avoid calling tick as many times as needed before the system settles and you have your auto-layout. That said, you do not need to update you visualization every tick for the simulation to work! The iterations will go a lot faster, in fact, if you don't. The relevant part of my code goes:

var iters = 600; // You can get decent results from 300 if you are pressed for time
var thresh = 0.001;
if(!hasCachedLayout || optionsChanged || minorOptionsChanged) {
    force.start(); // Defaults to alpha = 0.1
    if(hasCachedLayout) {
        force.alpha(optionsChanged ? 0.1 : 0.01);
    }
    for (var i = iters; i > 0; --i) {
        force.tick();
        if(force.alpha() < thresh) {
            //console.log("Reached " + force.alpha() + " for " + data.nodes.length + " node chart after " + (iters - i) + " ticks.");
            break;
        }
    }
    force.stop();
}

This runs synchronously and after it has run I create the dom elements for all the nodes and links. For small graphs this runs quite fast, but you will find there is a delay for larger graphs (100+ nodes) - they are simply much more computationally expensive.

2) You can cache / seed layouts. The force layout will uniformly distribute the nodes upon initialization if no position is set! So if you make sure your nodes have set x and y attribute these will be used. In particular when I am updating an existing graph I will re-use the x and y positions from a previous layout.

You will find with a good initial layout you will need a lot fewer iterations to reach a stable configuration. (This is what hasCachedLayout tracks in the code above). NB: If you are re-using the same nodes form the same layout then you will also have to make sure to set px and py to NaN or you will get weird results.

score:31

Internally, under "normal" usage, the force layout repeatedly calls its own tick() method asynchronously (via a setInterval or requestAnimationFrame), until the layout settles on a solution. At that point its alpha() value equals or approaches 0.

So, if you want to "fast forward" through this solution process, you can synchronously call that tick() method over and over until the layout's alpha reaches a value that, for your specific requirements, constitutes a "close enough" solution. Like so:

var force = d3.layout.force(),
    safety = 0;
while(force.alpha() > 0.05) { // You'll want to try out different, "small" values for this
    force.tick();
    if(safety++ > 500) {
      break;// Avoids infinite looping in case this solution was a bad idea
    }
}

if(safety < 500) {
  console.log('success??');
}

After this code runs, you can draw your layout based on the state of the nodes. Or, if you're drawing your layout by binding to the tick event (ie force.on('tick', drawMyLayout)), you'll want to do the binding after this code runs, because otherwise you'll needlessly render the layout hundreds of times synchronously during the while loop.

JohnS has boiled down this approach to a single concise function. See his answer somewhere on this page.


Related Query