Fix only one dimension of a node in force layout? - d3.js

This question - Fix Node Position in D3 Force-Directed Layout - covers how to fix the position of a node in a force-layout.
My question is how to fix one dimension, X or Y, of a node and let the other respond to the forces in the layout.

This isn't directly supported in D3, but you can do it manually by resetting the coordinate you want to remain constant in the tick handler function.
force.on("tick", function() {
nodes.each(function(d) {
d.x = d.px = d.savedX; // similar for y
});
// do other stuff
});
This requires you to store the desired value with the data bound to the nodes, in the example in an attribute savedX (although you can obviously use any other name as long as it's not used by anything else).

Related

Force-directed graph reusability failure?

first of all:
d3-version: "version": "3.5.17"
i have two differnet kinds of thematic maps, a proportional symbol map and a pseudo-demers cartogram with symbols (circles, same as in proportional symbol) as close to its origin as possible with collision detection and gravity.
However, i want to animate the circles from the proportional symbol map to the cartogram. In particular, i just want to trigger my force-directed graph for collision detection and gravity; this works fine for the first time. I provide an animation back from a cartogram to a proportional symbol map where each symbol just moves back to its centroid. Now, if i want to go back to the cartogram, the code fails and it says Uncaught TypeError: Cannot read property '1' of undefined, even though i trigger it he same time again as the first time. Is this some kind of bug in reusability of the force? or some kind of error on my side?
The problem can be tested here: https://particles-masterthesis.github.io/aggregation/; on the top left, you can switch between proportional symbol and cartogram.
the appropriate code i am using is the following:
case 'psm_cartogram':
let psm = this.currentViz;
information = {
data: psm.nodes,
symbols: psm.symbols
};
upcomingViz.obj = this.canvas.drawCartogram(
null,
this.currentViz.constructor.name === "Cartogram",
information,
() => {
this.currentViz.hide(false, true);
this.fadeOutParticles();
}
);
upcomingViz.type = 'cartogram';
resolve(upcomingViz);
break;
The key part is in the information object, where i share the same objects between the cartogram and the proportional symbol map
in the cartogram, i have the following important code:
```
this.force = this.baseMap._d3.layout.force()
.charge(0)
.gravity(0)
.size([this.width - this.symbolPadding, this.height - this.symbolPadding]);
this.nodes = keepInformation.data;
this.node = keepInformation.symbols;
this.force
.nodes(this.nodes)
.on("tick", this.tick.bind(this, 0.0099))
.start();
...
tick(gravity) {
this.node
.each(this.gravity(gravity))
.each(this.collide(0.25))
.attr("cx", d => { return d.x; })
.attr("cy", d => { return d.y; });
}
gravity(k) {
return d => {
d.x += (d.x0 - d.x) * k;
d.y += (d.y0 - d.y) * k;
};
}
//the collide function is not shown as it is a simple quadtree
```
if it helps in any way, the code is also available at https://github.com/particles-masterthesis/aggregation/src/js/visualization/map The main code is the transition-manager and the two types of maps.
i am thankful for any suggestions and support i can get, even if its a simple hint that i could check out.
PS:
These are two screenshots; the first one is important for the different logs of cartogram:132 which was a console.log(this.node) in the tick-function before doing any gravity etc. and the second one mentions the error.
for more understanding of the first log:
it starts by logging this.node in the tick function; afterwards a visualization change to psm got triggered (cartogram_psm) with a change back to cartogram later on and then the error appeared.
okay so i could figure out my problem:
transition chaining was incorrect in my way and so, the mysterious attributes on the object appeared;
rewriting all transitions with this method helped (https://stackoverflow.com/a/17101823/1472902)

How to create slow simultaneous transitions of multiple attributes in force directed graphs?

In a previous post called "D3: How to create slow transition of Circles for nodes in Force Directed Graphs FDG?", I got a great answer for how to transition a single element (e.g. the radius for "just circles") in D3.
My followup question is now about how to transition "multiple D3 attributes" at the same time...
As a reminder, I'm using D3 generated Radio Buttons to toggle the size of Nodes in a FDG Layout (on mouse click) from a default size to a scaled magnitude. You can find the Radio Buttons in the upper left hand of the Node Cluster Diagram (http://nounz.if4it.com/Nouns/Applications/A__Application_1.NodeCluster.html)
The code that toggles the node circles between a default number and a scaled magnitude (now using transitions) looks as follows...
var densityControlClick = function() {
var thisObject = d3.select(this);
var typeValue = thisObject.attr("density_type");
var oppositeTypeValue = (function() {
if(typeValue=="On") {
return "Off";
} else {
return "On";
}
})();
var densityBulletSelector = "." + "densityControlBullet-" + typeValue;
var selectedBullet = d3.selectAll(densityBulletSelector);
selectedBullet.style("fill", "Black")
var oppositeDensityBulletSelector = "." + "densityControlBullet-" + oppositeTypeValue;
var selectedOppositeBullet = d3.selectAll(oppositeDensityBulletSelector);
selectedOppositeBullet.style("fill", "White")
if(typeValue=="On") {
var selectedNodeCircles = d3.selectAll("#NODE");
selectedNodeCircles.transition().duration(500).attr("r", function(d){ return rRange(d.rSize); });
}
else {
var selectedNodeCircles = d3.selectAll("#NODE"); selectedNodeCircles.transition().duration(500).attr("r", function(d) { if (d.id==focalNodeID) { return centerNodeSize; } else { return defaultNodeSize; } } );
}
}
Everything works great and you can see the slower node transitions when you select the radio buttons. However, I'd now like to learn how to transition multiple elements, such as the the radius and the edge lengths simultaneously, along with the theory behind doing so, in order to show off D3's dynamic nature.
My question is: Given that I already can successfully transition the radius of circles, how would I also transition other elements like the edge lengths based on attributes like "alpha", "friction", etc., and... what's the theory behind transitioning multiple elements (in other words, what does the code mean, in English)? The D3 API doesn't appear to clearly get into the theory behind transitioning multiple attributes, simultaneously.
So transitioning multiple attributes is the simple part of this question. Just like a regular selection you can set multiple attributes at a time on your transition:
selectedNodeCircles.transition().duration(500)
.attr("r", function(d){ return rRange(d.rSize); })
.attr("stroke", 'red');
This will transition your radius and your line colour. The transition is a property of the DOM element (in this case the circle) and it will transition as many DOM attributes as you like. The thing to remember is that there is only only one transition object on each DOM element. So if you create another you will overwrite the old one.
// This will NOT work
circles.transition().duration(1000).attr('r', 50);
// The radius transition will be overridden by the fill
// transition and so will not complete
circles.transition().duration(1000).attr('fill', 'red');
This can actually be quite useful because you don't have to worry about interrupting animations that are in progress and figure out how far along they are and then starting a new animation - this will generally be handled automatically.
In your case you want to transition edge lengths in your graph. These are determined by the positional attributes of the nodes. Judging by your finished product, these attributes are already being animated because you are updating the DOM on every iteration of the layout algorithm (not through transitions) probably in the tick() callback.
So you could use transitions inside your tick callback, which might look odd and may be a hassle to keep in synch with the radius transitions (you will have to set both attributes in the transition). But it might be just what you need.
Alternatively, if you can wait, don't update the DOM in the tick callback. Let the layout complete - it runs a lot faster when it is not rendering on each tick - and once it is complete you can animate the radius and x and y attributes to their final positions. Of course this means you'll want good starting positions.

change initial zoom in D3.js node graph

I try to change the default scale of the NodeGraph here so it fits in the screen on first load. It is written with D3.js. Is there an initial variable, that defines the zoom?
You can set a suitable transform on the top-level g element, e.g. g.attr("transform", "scale(0.8)").
I managed to change the initial gravity, which makes the zoom not needed anymore:
// Gravity at pageload:
initial_gravity=0.07; // to apply a change here, enter `make` in you terminal
var force = d3.layout.force()
.charge( function (d) {
if (d.flags.client)
return -30 * chargeScale
return -100 * chargeScale
})
.gravity(initial_gravity)
....
afterwards you need to call make at the console to apply the changes

How do I control the bounce entry of a Force Directed Graph in D3?

I've been able to build a Force Directed Graph using a Force Layout. Most features work great but the one big issue I'm having is that, on starting the layout, it bounces all over the page (in and out of the canvas boundary) before settling to its location on the canvas.
I've tried using alpha to control it but it doesn't seem to work:
// Create a force layout and bind Nodes and Links
var force = d3.layout.force()
.charge(-1000)
.nodes(nodeSet)
.links(linkSet)
.size([width/8, height/10])
.linkDistance( function(d) { if (width < height) { return width*1/3; } else { return height*1/3 } } ) // Controls edge length
.on("tick", tick)
.alpha(-5) // <---------------- HERE
.start();
Does anyone know how to properly control the entry of a Force Layout into its SVG canvas?
I wouldn't mind the graph floating in and settling slowly but the insane bounce of the entire graph isn't appealing, at all.
BTW, the Force Directed Graph example can be found at: http://bl.ocks.org/Guerino1/2879486enter link description here
Thanks for any help you can offer!
The nodes are initialized with a random position. From the documentation: "If you do not initialize the positions manually, the force layout will initialize them randomly, resulting in somewhat unpredictable behavior." You can see it in the source code:
// initialize node position based on first neighbor
function position(dimension, size) {
...
return Math.random() * size;
They will be inside the canvas boundary, but they can be pushed outside by the force. You have many solutions:
The nodes can be constrained inside the canvas: http://bl.ocks.org/mbostock/1129492
Try more charge strength and shorter links, or more friction, so the nodes will tend to bounce less
You can run the simulation without animating the nodes, only showing the end result http://bl.ocks.org/mbostock/1667139
You can initialize the nodes position https://github.com/mbostock/d3/wiki/Force-Layout#wiki-nodes (but if you place them all on the center, the repulsion will be huge and the graph will explode still more):
.
var n = nodes.length; nodes.forEach(function(d, i) {
d.x = d.y = width / n * i; });
I have been thinking about this problem too and this is the solution I came up with. I used nodejs to run the force layout tick offline and save the resulting nodes data to a json file.
I used that as the new json file for the layout. I'm not really sure it works better to be honest. I would like hear about any solutions you find.

Is it possible to create pie charts with object consistency?

the pie chart update example on the bl.ocks site doesn't update the elements 'in place':
http://bl.ocks.org/j0hnsmith/5591116
function change() {
clearTimeout(timeout);
path = path.data(pie(dataset[this.value])); // update the data
// set the start and end angles to Math.PI * 2 so we can transition
// anticlockwise to the actual values later
path.enter().append("path")
.attr("fill", function (d, i) {
return color(i);
})
.attr("d", arc(enterAntiClockwise))
.each(function (d) {
this._current = {
data: d.data,
value: d.value,
startAngle: enterAntiClockwise.startAngle,
endAngle: enterAntiClockwise.endAngle
};
}); // store the initial values
path.exit()
.transition()
.duration(750)
.attrTween('d', arcTweenOut)
.remove() // now remove the exiting arcs
path.transition().duration(750).attrTween("d", arcTween); // redraw the arcs
}
Instead, it just treats the new array of value as brand new data and resizes the chart accordingly.
I've created a fiddle demonstrating the issue very simply:
http://jsfiddle.net/u9GBq/23/
If you press 'add', it add a random int to the array: this works as intended.
If you press 'remove', the only element getting transitioned out is always the last element to have entered the pie. In short, it behaves like a LIFO stack.
The expected behaviour is for the relevant pie arc to get transitioned out instead.
Is it possible to apply object consistency to pies? I've also tried adding a key function (not demonstrated on the fiddle) but that just breaks (oddly enough it works fine with my stacked graphs).
Thank you.
The easiest solution to this problem is to set missing values to zero, rather than removing them entirely, as in Part III of the Pie Chart Update series of examples. Then you get object constancy for free: you have the same number of elements, in the same order, across updates.
Alternatively, if you want a data join as in Part IV, you have to tell D3 where the entering arcs should enter from, and where the exiting arcs should exit to. A reasonable strategy is to find the closest neighboring arc from the opposite data: for a given entering arc, find the closest neighboring arc in the old data (pre-transition); likewise for a given exiting arc, find the closest neighboring arc in the new data (post-transition).
To continue the example, say you’re showing sales of apples in different regions, and want to switch to show oranges. You could use the following key function to maintain object constancy:
function key(d) {
return d.data.region;
}
(This assumes you’re using d3.layout.pie, which wraps your original data and exposes it as d.data.)
Now say when you transition to oranges, you have the following old data and new data:
var data0 = path.data(), // retrieve the old data
data1 = pie(region.values); // compute the new data
For each entering arc at index i (where d is data1[i]), you can step sequentially through preceding data in data1, and see if you can find a match in data0:
var m = data0.length;
while (--i >= 0) {
var k = key(data1[i]);
for (var j = 0; j < m; ++j) {
if (key(data0[j]) === k) return data0[j]; // a match!
}
}
If you find a match, your entering arcs can start from the matching arc’s end angle. If you don’t find a preceding match, you can then look for a following matching arc instead. If there are no matches, then there’s no overlap between the two datasets, so you might enter the arcs from angle 0°, or do a crossfade. You can likewise apply this technique to exiting arcs.
Putting it all together, here’s Part V:
Ok, found the solution.
The trick was to pass the key this way:
path = path.data(pie(dataset), function (d) {return d.data}); // this is good
as opposed to not passing it, or passing it the wrong way:
path = path.data(pie(dataset, function (d) {return d.data})); // this is bad
And here's an updated fiddle with a working transition on the right arc! :)
http://jsfiddle.net/StephanTual/PA7WD/1/

Resources