Distribute nodes into two groups on a force diagram layout (left and right) - d3.js

I have some data that Im trying to plot in a specific way:
all the nodes have a way to identify which group it belongs, let say position:L or position:R.
I would like to take that property into account an order all the "l" (left) nodes to be on the left of a central node and all of the "R" (right) nodes to be on the right hand side of a static central node (something similar like drawn in the picture ).
Previously Ive used this:
tick = function () {
nodes.forEach(function (n) {
if (n.position == "L") {
n.x -= force.alpha() * 80;
}
if (n.position == "R") {
n.x += force.alpha() * 80;
}
});
....
};
This solution was okay for small sets but when nodes reach a certain number, say a 1000+, it becomes a very expensive one.
Ive looked at these two examples:
1st one and 2nd one and I was wondering maybe I could solve may problem with combining couple of forces on the page trying to "tear" the (static) central node apart and pulling different nodes to the sides ?
Any suggestions are welcome, thank You.

Related

how to get silbling elements in sunburst

I am new to d3.js, I am creating a visualization based on surburst. Can somebody let me know how I can get siblings elements external element in d3.js.
The main part of building the hierarchy from your data will be done by d3. Check the spec for partition.nodes(root) on how this information is put into the nodes. Basically, d3 will populate each node with a reference to its parent and its children providing all that is needed to navigate the hierarchy.
As a starting point you may have a look at this sunburst diagram. When hovering over an arc, this arc as well as its ancestors up to the root node are highlighted. The selection of nodes up the hierarchy which are to be highlighted takes place in a single function:
// Given a node in a partition layout, return an array of all of its ancestor
// nodes, highest first, but excluding the root.
function getAncestors(node) {
var path = [];
var current = node;
while (current.parent) {
path.unshift(current);
current = current.parent;
}
return path;
}
An even simpler version of this function will select the node and its siblings instead of the ancestors:
function getAncestors(node) {
return node.parent.children;
}
I adapted the above example to a plunk demonstrating how this may help solve your problem. In this plunk the hovered node and its siblings will get highlighted.

Force Network: How to set initial position for select nodes?

I have a simple force network graph of four nodes as an example, here:
https://jsfiddle.net/NovasTaylor/o1qesn6k/
I would like to set the initial position of node "A" using its accompanying data so the node is initially fixed at point (265,143). If I were to double-click that node it would return to being d.fixed=FALSE and the usual forces apply.
From my research here on Stackoverflow I know I need to set the inital .x and .y values as per Bostock's suggestion:
[Fix Node Position in D3 Force-Directed Layout
But I do not quite grasp where and how to do this. I have tried the following code, which I may not have placed in the right sequence. It appears to be the same as having .x and .y in the source data...which also did not work.
for (var i=0, tot=dataset.nodes.length; i < tot; i++) {
if (dataset.nodes[i].fix_x > 0) {
dataset.nodes[i].x = dataset.nodes[i].fix_x
dataset.nodes[i].y = dataset.nodes[i].fix_y
}
}
Your advice would be very much appreciated!
Tim

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.

D3: Highlight connected/adjacent nodes [duplicate]

I am working on a force directed graph in D3. I want to highlight the mouseover'd node, its links, and its child nodes by setting all of the other nodes and links to a lower opacity.
In this example, http://jsfiddle.net/xReHA/, I am able to fade out all of the links and nodes then fade in the connected links, but, so far, I haven't been able to elegantly fade in the connected nodes that are children of the currently mouseover'd node.
This is the key function from the code:
function fade(opacity) {
return function(d, i) {
//fade all elements
svg.selectAll("circle, line").style("opacity", opacity);
var associated_links = svg.selectAll("line").filter(function(d) {
return d.source.index == i || d.target.index == i;
}).each(function(dLink, iLink) {
//unfade links and nodes connected to the current node
d3.select(this).style("opacity", 1);
//THE FOLLOWING CAUSES: Uncaught TypeError: Cannot call method 'setProperty' of undefined
d3.select(dLink.source).style("opacity", 1);
d3.select(dLink.target).style("opacity", 1);
});
};
}
I am getting a Uncaught TypeError: Cannot call method 'setProperty' of undefined error when I try to set the opacity on an element I loaded from the source.target. I suspect this is not the right way to load that node as a d3 object, but I can't find another way to load it without iterating over all of the nodes again to find the ones that match the link's target or source. To keep the performance reasonable, I don't want to iterate over all the nodes more than necessary.
I took the example of fading the links from https://bl.ocks.org/mbostock/4062006:
However, that doesn't show how to alter the connected child nodes.
Any good suggestions on how to solve or improve this will be furiously upvoted :)
The error is because you are selecting the data objects (d.source and d.target) rather than the DOM elements associated with those data objects.
You've got the line highlighting working, but I would probably combine your code into a single iteration, like this:
link.style("opacity", function(o) {
return o.source === d || o.target === d ? 1 : opacity;
});
Highlighting the neighboring nodes is harder because what you need to know the neighbors for each node. This information isn't that easy to determine with your current data structures, since all you have as an array of nodes and an array of links. Forget the DOM for a second, and ask yourself how you would determine whether two nodes a and b are neighbors?
function neighboring(a, b) {
// ???
}
An expensive way to do that is to iterate over all of the links and see if there is a link that connects a and b:
function neighboring(a, b) {
return links.some(function(d) {
return (d.source === a && d.target === b)
|| (d.source === b && d.target === a);
});
}
(This assumes that links are undirected. If you only want to highlight forward-connected neighbors, then eliminate the second half of the OR.)
A more efficient way of computing this, if you have to do it frequently, is to have a map or a matrix which allows constant-time lookup to test whether a and b are neighbors. For example:
var linkedByIndex = {};
links.forEach(function(d) {
linkedByIndex[d.source.index + "," + d.target.index] = 1;
});
Now you can say:
function neighboring(a, b) {
return linkedByIndex[a.index + "," + b.index];
}
And thus, you can now iterate over the nodes and update their opacity correctly:
node.style("opacity", function(o) {
return neighboring(d, o) ? 1 : opacity;
});
(You may also want to special-case the mouseovered link itself, either by setting a self-link for every node in linkedByIndex, or by testing for d directly when computing the style, or by using a !important css :hover style.)
The last thing I would change in your code is to use fill-opacity and stroke-opacity rather than opacity, because these offer much better performance.

Constraining d3 force layout graphs based on node degree

I have a force layout with potentially a very large number of nodes, too large for the graph to render responsively. I was thinking that one way to improve the performance of the system was to prune the graph by eliminating nodes based on in- and out-degree when the number of nodes gets too large.
Recomputing the node and link lists is a bit of a nuisance because links are related to indexes in the node array, and so all the links would need to be re-built.
It seems more elegant to be able to mark individual nodes for exclusion (analogously to the way some nodes are fixed) and have the layout algorithm skip those nodes. This would allow me to dynamically select subsets of the graph to show, while preserving as much state for each node (e.g., position) as practical.
Has anyone implemented something like this?
UPDATE:
I tried to implement the filter suggestion, but ran into an interesting error. It appears that the filter method returns an object that does not implement enter:
qChart apply limit:2
NODES BEF: [Array[218], enter: function, exit: function, select: function, selectAll: function, attr: function…]
NODES AFT: [Array[210], select: function, selectAll: function, attr: function, classed: function, style: function…]
Uncaught TypeError: Object [object Array] has no method 'enter'
The following code is run to get from BEF to AFT:
nodeSubset = nodeSubset.filter(function(n) { return (n.sentCount() <= limit); });
UPDATE 2:
I created a jsfiddle to isolate the problem. This example implements my interpretation of ChrisJamesC's answer. When I tried to implement his suggestion directly (putting the filter after the data), the subsequent call to enter failed because the object returned by filter did not have enter defined.
The goal is to make the layout select only those nodes that have active == true, so in this example, this means that node b should be excluded.
You can use the selection.filter() option combined with the node.weight attribute.
What you would normally do is:
var node = svg.selectAll(".node")
.data(graph.nodes)
.enter().append("circle")
Here you can do:
var node = svg.selectAll(".node")
.data(graph.nodes)
.filter(function(d){return d.weight>3})
.enter();
You might also have to remove from drawing the links going to these nodes using the same method.
EDIT You should just filter the data you provide if you want to mark nodes as active directly in the data array (and do the same for links)
var node = svg.selectAll(".node")
.data(force.nodes().filter(function(d) { return d.active; }));
var link = svg.selectAll(".link")
.data(force.links().filter(function(d) {
var show = d.source.active && d.target.active;
if (show)
console.log("kept", d);
else
console.log("excluded", d);
return show;
}) );
Fiddle
If you want to do this by computing the weight of each node, I would still recommend you to do this before passing the nodes and links to the graph and mark nodes as active or not following a specific criteria, then filter the links according to active nodes. Otherwise you would have to load the whole force directed layout only to get the weight to then filter the data to re-load the force directed graph.

Resources