I'm working on a graph to show relations between different nodes. The closer related the nodes are (according to business logic), the closer together the nodes should be.
I noticed that some links with the linkStrength of .1 are shorter (that is what I wanted to achieve) and some others with the same strength are longer than the ones with the linkStength of 1. From a documentation about the force layout parameters I now found this quote:
The default linkStrength value is 1.0, which maintains the full effect of linkDistance. By setting the value of linkStrength less than 1, though, the distance constraint can be relaxed.
Does that mean that if I was to set linkDistance to 150, the links with linkStrength(1.0) will be closer to 150 than the ones with linkStrength(.1)? And if yes, do they need to be shorter, longer or doesn't that matter at all? Because I was kind of surprised about the layout.
To cut a long story short: when using D3's force layout there is no built-in way to enforce a fixed length of the links. The force layout is inherently dynamic and setting values for force.linkDistance() and force.linkStrength() introduces just another force to the set of calculations carried out on each iteration, i.e. each tick, while the force layout is running.
There are three forces calculated on each tick:
1. Link length. The first force to be calculated is the adjustment for link lengths set via the above mentioned methods. This is done in a loop for each link and, looking at the source, this comes down to essentially one line of code:
l = alpha * strengths[i] * ((l = Math.sqrt(l)) - distances[i]) / l;
With l calculated to be the Euclidean distance between the link's source node and target node, the desired link distance distances[i] and the link's strengths[i] this line determines how to pull both nodes together or push them apart to approximate the link distance set via force.linkDistance(). It's easy to see, that the link strength has a linear effect on the resulting force. Contrary to the API documentation, though, which defines the valid range for the strength to be in the interval [0,1], the source code does not impose any restrictions on the value set by force.linkStrength().
2. Gravity. The second force to be calculated will take into account gravitational forces on each node.
3. Charge. Lastly, the mutual forces of the nodes' charges are calculated.
Because the effects of all the calculated forces are superimposed and will add up to the resulting movement of each node for a given tick, it becomes clear, that the link length is only one part of the entire computation. The other two forces may weaken or even reverse its effect.
As for your question
Does that mean that if I was to set linkDistance to 150, the links with linkStrength(1.0) will be closer to 150 than the ones with linkStrength(.1)?
The outcome depends largely on the setup of the force layout's parameters and the distribution of the nodes, and still no guarantee is made regarding the final lengths of the links.
The link strength sets the rigidity of the links, not the distance between the nodes (force layout doc). The distance of the nodes from each other is controlled by their charge. You can make the charge of a node dynamic like so:
var force = d3.layout.force()
.nodes(nodes)
.theta(0.1)
.charge(function (d) {
return -(d.radius * d.radius * 0.125);
})
.gravity(0.1)
.size([width, height])
.on("tick", tick)
.start();
A working example that uses this technique: vizz.ly
Related
I want two instances of d3.forceCollide(). In one, every node is pushed away from one another to prevent overlap. In the second, only a subset of nodes are pushed away from one another, with a much bigger radius.
To accomplish the second force, I tweak the initialize method to filter the incoming nodes, like so:
function selective(force,filter){
var init = force.initialize;
force.initialize = function(_){return init(_.filter(filter));};
return force;
}
var dpi = 90; // approximate pixels per inch in SVG
var size = dpi * (1/4); // quarter-inch unit size
var universally_applied =
d3.forceCollide()
.radius(size)
.strength(1);
var selectively_applied =
selective(
d3.forceCollide(),
function(d){return d.id === color;}
)
.radius(size*5)
.strength(1);
}
Now, this ALMOST works. I created a fiddle to see it in action: https://jsfiddle.net/jarrowwx/0dax43ue/38/ - every colored circle is supposed to repel every other circle of the same color, from a distance. Every other color, it just bumps into and pushes it out of the way.
If I do not change the order in which things are defined, then the selectively applied force is ONLY applied to the first color (red). If I shuffle the data array before applying forces, it is difficult to define exactly what happens, but the force is applied to some circles and not most of the others, even among the same color.
Any ideas what is going on here, or how to fix it?
The D3 team decided that this behavior was a bug (Isolating forces to a subset of nodes? #72), and fixed it. The fix was included in version 1.0.4 of d3-force, which is available as part of the full D3 build as of version 4.4.0.
The problem is resolved using the solution suggested by "Partial forces on nodes in D3.js", and the code works as intended now.
I'm facing an issue right now where I'm trying to make a force-directed graph that's smart about how it clusters. Currently I'm getting the following, which as you can see, has a very broken layout in regards to readability. I would much prefer to have each child group cluster with itself, and repel sibling groups, so it's a bit easier to follow. Preferably, I'd also like these clusters to be equally distributed in a circle around the parent node so when there are a lot of nodes, they're at least more readable than they would otherwise be if they all clustered on one side of the parent.
I did a bit of research, and I would like something similar to this, but I'm not sure how I can apply that to my site. For reference, my site layout is based off of this force layout.
I ended up playing with the d3 force options a bit, and I came across this as a viable solution:
var force = d3.layout.force()
.linkDistance(function(d) {
return d.target._children ? d.target._children.length * 30 :
d.target.children ? d.target.children.length * 30 :
60;
})
.charge(-400)
.gravity(0.05)
.friction(0.45)
.linkStrength(0.6)
.size([width, height])
.on("tick", tick);
gravity / friction / linkStrength are not necessary but make transitions overall smoother; having a very negative charge was the key component to solving my original problem.
Much thanks to #AmeliaBR for pointing me in the right direction!
I am trying to make the Bilevel Partition in D3 work with log scale.
However, it doesn't seem to be working properly.
I have specified a log scale for angles:
var angle1 = d3.scale.log()
.base(2.0)
.domain([0, 2 * Math.PI])
.range([root.x, root.x + root.dx]);
However, this applies only when the angles are recalculated on zoom.
I have tried to modify the original partition scale, but with no success.
Any hints appreciated. See example code at the link below.
http://bl.ocks.org/mbostock/5944371
The partition layout in D3 sums up the values of the leaf nodes in order to calculate layout of the the elements with children. By definition of layout it should word like that. Therefore, only leaf nodes values are taken into account when calling layout.nodes(). Hence, only leaf nodes can be scaled (for example as log(count + 1)). However, non-leaf nodes will be represented sums of underlying values. The only option for all nodes to scale logarithmically would be to write a new layout, that would take into account a value at each node, and not sum up the values of the children.
I'm learning D3 and my toy application is a visualizer for a 2-dimensional gravity simulation using Symplectic Velocity Verlet Integration.
I have had quite some success animating and drawing a path using bound structured data, but it seems to me that D3 isn't really designed for attaching data to more than one element. It sort of assumes that for any given data that there is a clear and simple SVG element that should own it, and this is evident in the direct storage of data within the __data__ property inside the DOM.
It's not clear, though, the proper way to represent a datum with more than one SVG element. For instance, I'd really prefer to draw a path and a circle for each planet, the path traces its past position (and can have a bunch of clever line-length and color interpolation applied), and the circle plots its current position.
I can even come up with a few more elements I might want to draw: A vector-arrow for velocity... A vector-arrow for acceleration...
In my case, my master data structure is constructed like this, and is dynamically maintained in this structure:
var data = [];
function makeParticle(x, y, vx, vy) {
// state vector plus goodies
return [
x, y,
vx, vy,
0, 0,
[] // path
];
}
data.push(makeParticle(400, 100, -0.5, 1));
data.push(makeParticle(300, -100, 0.5, 2)); // and so on
Each element of data is a planet, which contains its current state vector (position, velocity, and cached acceleration (needed for the integrator)) as well as its path history which is an array of positions which is kept truncated to some reasonably large length.
Currently I am updating the path history like this:
var paths = d3.select('svg').selectAll("path").data(data);
paths.enter().append('path'); // put cool transitions here
paths.exit().remove();
paths.attr("stroke", "red")
.attr("d", function(d){
return lineDrawer(d[6]);
})
This works fine, each path tracks its own copy of its own planet's path.
It's not immediately clear to me how to extend this elegantly to include my circle at the head of each path. I certainly do not want to duplicate the entire datum into the circle's DOM element (as the path data is simply not necessary for it).
Edit in light of my self-answer:
I am hoping that someone can help to elucidate a way to use "groups" to make data-driven the set of things to draw for each datum. e.g. [position, velocity, force, path] as data where they are visualized using, respectively, a circle, an arrow closed path, an arrow closed path, and an open path. It is also possible that this is completely overthinking it because these properties are sort of fixed.
I guess in the process of thinking the problem through, it's become clear to me that all I have to do is filter out the data, so that I only attach the position state data to selectAll('circle.planet') rather than the full datum value which includes the massive path history array. This way it gets exactly the data it is responsible for displaying.
It seems like I have to do some more reading about subselections (I'm mostly puzzled by why (or if) the subselections are limited to two dimensions of hierarchy), but this seems completely reasonable. It seems logical, if I want to draw 4 items for each datum, I just have to somehow "assign" the correct subsets of my datum's structure to each SVG visualizer element.
How are the LinkDistance and LinkStrength related in a force directed layout in D3.js? I am assuming that they are, correct me if i am wrong.
I understand that the linkDistance defines the length between any pair of nodes and essentially serves as constraint in a force layout. But what role does linkStrength play? The API documentation for D3.js defines it as the "strength (rigidity) of links to the specified value in the range [0,1]" What does "rigidity" mean here exactly?
You can see the link distance as the expected distance and the strength as the speed at which you want to reach this target distance on each iteration.
If you have a look at the source code of the force directed layout, you will find the following line:
l = alpha * strengths[i] * ((l = Math.sqrt(l)) - distances[i]) / l;
This algorithm is an optimization algorithm, thus, on each iteration you modify l. Now the thing is that you have to specify by how much you modify this.
On a basic algorithm you would have the following in order to optimize the distances:
l = ((l = Math.sqrt(l)) - distances[i]) / l;
However you might want to have more control on every links and also on each individual link. Hence, you can consider the alpha attribute as the fixed parameter and the strength attribute as the parameter that varies for each link.
If you want to know more about the optimization method used, I recommend you to have a look at the Gauss-Seidel wikipedia page.