I am working on collapsible force layout in d3.The problem I am facing is I need to increase the length of the link between the nodes when it is clicked keeping the link distance same among the childs.
How to increase the distance between analytics and flare when analytics is expanded keeping distance to its children small?
Yes you can do this by defining a function for the force.linkDistance
something like this:
var force = d3.layout.force()
.linkDistance(function(d){
if(d.target._children){
return 50;//target is not expanded so link distance is 50
} else {
return 200;//target is expanded so link distance is 200
}
})
Full working code here.
Related
For the following Fiddle I'd like to hide the root node. Any help is appreciated, to view the code please view the fiddle.
I imagine I would do something like the following but I'm unsure of how/where to implement it:
if (d.depth > 0) {
...node is drawn
}
Image below:
Not drawing it is not "drawing it with zero opacity" or "hidden display". It's actually not appending the element.
Therefore, the simplest option is removing it from the data array. Just filter out the first node:
nodes = nodes.filter(function(d){
return d.depth != 0;
})
As 0 is falsy, this is the same of:
nodes = nodes.filter(function(d){
return d.depth;
})
And also filter out all links from it:
links = links.filter(function(d){
return d.depth != 1;
})
Here is your updated fiddle: https://jsfiddle.net/wa21csbc/
Also, since those elements are not painted anymore, you can move the dataviz to the left, thus occupying the empty SVG space. That space is there because we're filtering out the first node after d3.tree() calculated the positions.
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).
I am using the pack layout for packing different no of equal sized circles. I have a group of clusters to be visualized. So I am calling pack function for each cluster of circles. In all the d3 examples the diameter is either calculated with the given size or fixed diameter. I would like to calculate it according to the no of circles to be packed. So how do I calculate the packing circle diameter?
is there any formula so that I can pack the circles without wasting the space.
If you truly don't care about relative sizing of the circles, then you could make your JSON file represent only the data you care about(say, names) and feed your packing function a dummy value that the 'value' accessor function is expecting.
For instance:
var circleChildren = [{
"value": 1
}, {
"value": 1
}, {
"value": 1
}, {
"value": 1
}];
would give you a JSON object that you can use as children for your packing function:
var circleInput = Object();
circleInput.children = circleChildren;
You can verify that in your console by running:
bubble.nodes(circleInput)
.filter(function (d) {
return !d.children; //we're flattening the 'parent-child' node structure
})
where bubble is your D3 packing bubble variable.
Here's a fiddle that demonstrates that. It may have some extra things but it implements what you're looking for. In addition, you can play around with the number of circles by adding more dummies in the JSON file, as well as changing the SVG container size in the diameter variable. Hope that helps!
EDIT: The size of your layout(in this case, a misnomer of the 'diameter' variable) directly determines the size and diameter of your circles within. At some point you have to assign the pack.size() or pack.radius() value in order for your circles to display within a layout(documentation ):
If size is specified, sets the available layout size to the specified two-element array of numbers representing x and y. If size is not specified, returns the current size, which defaults to 1×1.
Here you have several options:
If you want your circles to be 'dynamically' sized to your available element's width (that is, if you want them to cover up all the element width available) then I'd recommend you get your element's width beforehand, and then apply in your pack() function. The problem is then you have to think about resizing, etc.
If you want to keep the maximum sizing available, then you have to make your viz responsive. There's a really good question already in SO that deals with that.
I know this isn't the full solution but hopefully that points you in the right direction for what you're trying to do.
FURTHER EDIT:
All of a sudden, another idea came to mind. Kind of an implementation of my previous suggestion, but this would ensure you're using the maximum space available at the time for your circle drawing:
zone = d3.select("#myDiv");
myWidth = zone.style("width").substring(0, zone.style("width").length - 2);
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.
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.