I've just started dabbling with d3, and find the learning curve quite steep. The process is completely different than what I am used to, and the mathematics are mostly way over my head.
Anyway, my project consists of a force layout representing map of integrations between systems. This part works out awesomely well, but I do have one major concern, which is also represented in the force directed layout demo on Michael Bostocks site: When nodes are initiated, they seem to be rendered off canvas. After this, some serious physics math is taking over, simulating a gravitational pull which sends the nodes on a rather confusing path back and forth until they calm down and settle on some random coordinates. Although these movements are ripping cool the first time the demo is run, when you are trying to view the status of network interfaces from a company it admins point of view and the servers just wont stay still, it gets tiresome after a while.
I am sure I have the correct layout setup for this project, because I do want the servers to autolayout, I do want to visualize links between them. I am however ambivalent in regards to the gravitation effect.
I wonder; is it possible to set the initial position of each node manually, so that I can put them closer to the gravitational center and shorten the "bounce time" a bit?
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.
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.
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.
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();
}
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With