The real magic for multi-foci force is done here;
function tick(e) {
var k = .1 * e.alpha;
// Push nodes toward their designated focus.
nodes.forEach(function(o, i) {
o.y += (foci[o.id].y - o.y) * k;
o.x += (foci[o.id].x - o.x) * k;
});
node.attr("transform", function(d) { return "translate(" + d.x + "," + d.y + ")"; });
}
but I'd appreciate some clarification about what's going on.
alpha, I believe, is a force method that controls the rate at which the force comes to rest, accepting values in the range [0, 1] - higher values cause force to slow to a halt slower, lower values faster.
We then iterate through the original array and increment the x and y locations (which don't exist initially, so these are assigned for the first time on the first iteration of this forEach loop) by k * the x and y components of the focus point.
Ultimately then they'll always be moving towards the designated x and y positions, but how do we guarantee we get them there based on this k value which is itself based on alpha? Is the extent to which the nodes move along the x and y axes controlled by the .1 constant? Setting this higher/lower implies more/less drift towards the focus points?
Finally why do we then transform the nodes? I would understand
node.attr("cx", function(d) { return d.x}) and the same for y. Why the transform?
Thanks in adv.
jfiddle - https://jsfiddle.net/hiwilson1/dL9r22ny/
UPDATE: I suspect the last part of my question, why we transform the nodes, is because we're moving g elements rather than circle elements and we can't use cx and cy on a g element. Still unsure about why we translate them d.x and d.y though, wouldn't this move them from the arbitrarily assigned d.x and d.y values to effectively double these locations? (if we start at [10, 10] and translate another [10, 10] we end up at [20, 20]?)
The animation is driven by alpha
. It's a geometric series which is always the same: set to 0.1 in .start()
and multiplied by 0.99 at each tick, the animation stops when it is less than 0.005
alpha: 0.0990
alpha: 0.0980
alpha: 0.0970
alpha: 0.0961
alpha: 0.0951
alpha: 0.0941
...etc.
force.tick = function() {
if ((alpha *= .99) < .005) {
event.end({
type: "end",
alpha: alpha = 0
});
return true;
}
//other code...
};
It represents the "heat" in the layout because it is used to determine the velocity of the nodes. This in analogous to temperature in a gas, which is proportional to the average kinetic energy of it's molecules. The "cooling" is pre-programmed to be be always -1% of the current "temperature".
The initial positions of the elements is also set in the .start()
function as Math.random() * size
for x and y, where size is width and height respectively. This is done before the first forEach
in the tick
function.
function tick(e) {
//var k = .1 * e.alpha;
var k = .1 * e.alpha;
log.text('alpha: ' + d3.format(".4f")(e.alpha * 1000))
// Push nodes toward their designated focus.
nodes.forEach(function (o, i) {
o.y += (foci[o.id].y - o.y) * k;
o.x += (foci[o.id].x - o.x) * k;
});
In the above, forEach
statement, if the element y position is greater than the focus y position, then it will be given a smaller y, similar for the x positions. That means they will move towards their foci at a speed proportional to their distance from it. The proportionality constant k
is 0.1*alpha
which is decreasing geometrically from k = 0.1*0.1
to k = 0.1*0.005
, as the animation proceeds. The final positions are a function of their initial positions and k
and the other forces of gravity, charge and friction.
The nodes are g
elements which have no positioning other than a reference (positioning context) for their child elements. This is the origin (top left corner) of the containing svg
element and it's position is a result of the page flow and CSS positioning. The positioning context of the g
elements can be altered by their transform property and this is inherited by all of their child elements. Without the g elements, the circles and text elements would both have to be positioned separately so the work is halved this way. Without the transforms, all of the circles and text would be positioned, centered on the top, left corner of the svg
element.
The new positions calculated each tick are absolute values, not changes in value.
The change in node position is (foci[o.id].y - o.y) * k
and this will move them towards their foci. This is "added" to the existing value (although it could be negative) and stored on the node datum (o.x
and o.y
), this statement
node.attr("transform", function (d) { return "translate(" + d.x + "," + d.y + ")"; });
uses the new datum (d
) to update the translation, which is still relative to the svg
origin. It is a transform, not a move, so it doesn't translate relative to the current position, it changes the translation relative to the svg
element origin (which is the positioning context for the g
). So if we start at [10,10] and the new calculation is [10,10] then the position will remain at [10,10] relative to the svg
positioning context.
来源:https://stackoverflow.com/questions/29632287/d3-multi-foci-force-key-code-component-understanding