d3js Tree Layout vertical distance between text of circle overlaps - javascript

I created this fiddle for a simple tree visualisation with d3js. It works fine. However, when the tree gets really big I have the problem than texts next to the nodes tend to overlap. So I need to somehow set a vertical distance. How can I achieve this? The following image shows what I mean:
I tried to add it with the separation function, but this is I guess only changes it horizontally.
var tree = d3.layout.tree().nodeSize([1, nodeHeight])
.separation(function(a, b) {
var height = a.height + b.width,
distance = height / 2 + 50;
return distance;
}),
nodes = tree.nodes(data),
links = tree.links(nodes);

I think the sibling nodes are not overlapping but the cousins. To handle your problem, you need to see how tree.separation() works.
In one of my projects. I did this.
var tree = d3.layout.tree();
tree.nodeSize(/*some stuff here*/)
.separation(function(a, b) {
return (a.parent == b.parent ? 1 : 1.5);
});
return (a.parent == b.parent ? 1 : 1.5) basically is saying that if
nodes have same parent or are siblings, then separation between them is none and if they don't have the same parents, they are considered cousins, and therefore computed distance between them is 50% the height of your node(which you defined in nodeSize).
I'm not good at explaining stuff like professionals do but definitely check out separation method and keep in mind it handles distance between cousins nodes.

I had a similar issue, and none of the answers to related questions that suggested using nodeSize() or separation() appeared to change the layout much (or, in ways that I was expecting).
In the end, I made the following simple scaling change in the update() function, and it fixed the issues with vertically overlapping nodes. It's not terribly elegant, but has the virtue of being simple:
nodes.forEach((d) => {
// spread out the vertical axis (if this isn't here, lines tend to overlap on denser graphs)
d.x = d.x * 2;
});

Related

D3 Sankey diagram from CSV rendering incorrectly

I am trying to make a Sankey Diagram using d3js. I am using a csv data source and using code based on this block: http://bl.ocks.org/d3noob/c9b90689c1438f57d649
My dataset is quite large, but I think I should be under the threshold where I would be breaking the SVG renderer. I am unsure though. My code is functional when running with the 1082 line "SankeyDataSampleFunctional.csv". Here is what it looks like:
I have made some slight modifications to the original codebase to allow for my diagram to have substantially larger amounts nodes at some layers while others don't. This is my attempt at accommodating it in sankey.js:
function initializeNodeDepth() {
var ky = d3.min(nodesByBreadth, function(nodes) {
if (nodes.length > 200){
return (size[1] - (nodes.length - 1) * 1) / d3.sum(nodes, value);
}
else if (nodes.length > 1000) {
console.log((size[1] - (nodes.length - 1) * 1) / d3.sum(nodes, value))
return (size[1] - (nodes.length - 1) * 1) / d3.sum(nodes, value);
}
return (size[1] - (nodes.length - 1) * nodePadding) / d3.sum(nodes,
value);
});
I'm doing this to clamp down the space between nodes on layers with large numbers.
But when I try to add the last layer with 2102 more nodes I get all sorts of problems. This is what it looks like when I plug in "SankeyDataSample1.csv" into the same code in question:
I checked the console and its saying I'm trying to draw a bunch of stuff with negative height. My first recourse was to clip 2101 of those 2102 extra nodes off, to see if i could get just one node to render correctly on the following layer. of course not:
I tried adding some additional depth to that branch and it continues to render the branch in the wrong direction:
finally, i checked this post about someone else who was using large datasets in a d3 sankey: Large data set breaks d3 sankey diagram
I attempt the 'quick fix' of forcing my height to be nonzero seen in this post:
.attr("height", function(d) { return d.dy < 0 ? 0 : d.y; })
However this didnt do much for me. I also tried multiplying negative values of d.dy by -1 with a hope to invert them and this is all i got:
Here is my code: http://plnkr.co/edit/g2rE0z?p=info
Am I up at the limits of SVG with that last 2k nodes? Or am I just going about this wrong? Any assistance would be appreciated.
Have you tried to enlarge canvas size? Usually, in my case, if there's no space left for nodes, it renders like that.
You might want to see the answer that I've posted here: Large data set breaks d3 sankey diagram
The idea is that you can regenerate the graph with a new width and a new height until each node has a proper height.

How to dynamically reposition binary tree nodes visually

I have been working with the following code: http://bl.ocks.org/NPashaP/7683252. This is a graphical representation of a tree. I have stripped away much of the code (the graceful labels), only allowing two nodes per parent, and changed the data structure to an array.
The only problem left now is the repositioning. The original code does that perfectly. But since I want a binary tree, I have let the user choose to insert a left or right child. The original reposition code animates the first child straight down from the parent, but that would be wrong in a binary tree. I want it to either go to the left or right.
reposition = function (v) {
function repos(v) {
var lC = getLeafCount(v.v),
left = v.p.x; //parent's x-position
v.c.forEach(function (d) {
var vc = d; //saving reference of the child in parent object
d = tree.getVerticeById(d.v); //actually fetching the child object
var w = 0;
if(d.d == 'right') { w += 15 * lC }
if(d.d == 'left') { w -= 15 * lC }
d.p = {x: left + w, y: v.p.y + tree.h}; //setting the position
vc.p = d.p; //setting the child's pos in parent obj
repos(d);
});
}
repos(v[0]);
};
Some of the parts of my code are different from the original code, because I have changed the data structure as stated before. I have tried to comment the parts that may be confusing, but what is important is the math of the repositioning.
At first, this code seemed to work well (https://i.stack.imgur.com/gjzOq.png). But after some testing I discovered a huge problem with the repositioning: the nodes crash with each other (https://i.stack.imgur.com/pdQfy.png)!
Conclusion: I have tried to modify the original function to take the in mind the left and right positioning of nodes, but could not do it. I wrote this variant of the method which does, but it still has some problems as illustrated in the pictures. I would be thankful for some input in this matter.
I eventually did a quick fix for this problem.
After running the reposition function as posted in the question, i ran another function which fixed the problem. The new function goes through all levels of the tree and checks if the nodes are too close together. If they are, the algorithm finds closest common ancestor, and increases the distance between its left and right children. After this, there is no more collisions between nodes.

D3.js Tree layout canvas resize

This is a continuation of my efforts to build a collapsible tree layout using d3.js.
Generate (multilevel) flare.json data format from flat json
The layout looks like: (http://bl.ocks.org/mbostock/raw/4339083/) with around 3k nodes and depth of some nodes around 25. The current size of the canvas I need to set is 8000px width and 8000px height in order that all nodes are visible which I know is not reasonable when the number of tree levels rendered is 2 or 3.
Furthermore, I intend to make this code reusable with other trees that maybe smaller/larger in size based on what data source(json file) is selected.
So I was wondering if it is possible to resize the canvas size relative to the positions of the nodes/ number of nodes shown on screen. This way, the code would be much more systematic and adaptable.
I saw this:
Dynamically resize the d3 tree layout based on number of childnodes
but this resizes the tree, which if you can imagine in a case of tree with around 3k nodes, makes it hard to read and comprehend.
I know this might not even be related to d3.js but I tagged it to explain my issue and bring in d3 experts too who might have faced a similar condition.
I am also attempting to filter out uninformative nodes based on my criteria so as to render less number of nodes than the actual data. (I know i will run into performance issues with larger trees). Any help would be much appreciated.
NOTE: When I say canvas, I mean the area on which the tree is drawn and not the "canvas". I am not familiar with the jargon so kindly read accordingly.
Hope this helps someone.
I faced similar problems also using the flare tree code as a base for what I was building and the suggested links did not seem to account for a lot of variance in node structure? I have many trees to display with a dynamic number of nodes and structuring. This solution worked for me:
Concerning height: After observing the translate offsets per node, I learned that d.x (vs d.y, as tree is flipped) is an offset from the root node, with the nodes above root going negative and those below going positive. So, I "calculated" the max offset in both directions each time a node is appended, then with that information, adjusted the canvas height and the view translation (starting point of root).
For width: max d.depth * by the (y length normalization the code uses) + margins
let aboveBount = 0
let belowBound = 0
let maxDepth = 0
nodeEnter.each( (d) => {
if( Math.sign(d.x) === -1 && d.x < boundAbove) {
boundAbove = d.x
}
if( Math.sign(d.x) === 1 && d.x > boundBelow) {
boundBelow = d.x
}
if( d.depth > maxDepth){
maxDepth = d.depth
}
})
const newHeight = boundBelow + Math.abs(boundAbove) + nodeHeight*2 + margin.top*2
svg.style('height', newHeight)
svg.style('width'. maxDepth*180 + margin.left*2)
//180 was the amount set to normailze path length
svg.attr('transform', `translate(${margin.left}, ${Math.abs(boundAbove) + margin.top})`)
Well, best wishes and happy coding!
I was facing the similar problem and now I have find out a solution. Check on this link. D3 collapsible tree, node merging issue

Avoid collision between nodes and edges in D3 force layout

In this example: http://bl.ocks.org/mbostock/1747543:
...Mike shows us how to avoid collision among nodes so that no two nodes overlap each other.
I wonder if it is possible to avoid collision between nodes and edges so that no node 'clips' or overlaps an edge unless it is connected by that edge.
The following example using D3 force-direct shows that node L overlaps with the edge connecting I and A, and similarly, node M overlaps with the edge connecting L and D. How do we prevent such cases?
If your graph doesn't have too many nodes, you can fake it. Just insert one or more nodes for each link, and set their position along the link in the tick handler. Check out http://bl.ocks.org/couchand/7190660 for an example, but the changes to Mike Bostock's version amount to basically just:
var linkNodes = [];
graph.links.forEach(function(link) {
linkNodes.push({
source: graph.nodes[link.source],
target: graph.nodes[link.target]
});
});
and
// force.on('tick', function() {
linkNodes.forEach(function(node) {
node.x = (node.source.x + node.target.x) * 0.5;
node.y = (node.source.y + node.target.y) * 0.5;
});
This will introduce a pretty serious performance overhead if you have very many nodes and edges, but if your graph doesn't get much larger than your example it would hardly be noticed.
You may also want to fiddle with the relative force of the real nodes versus the link nodes.
Take this one step further and you get the nice curved links of http://bl.ocks.org/mbostock/4600693.

D3 Tree Layout Separation Between Nodes using NodeSize

Right now I am trying to separate my rectangle nodes because they overlap as shown in the picture below:
I took a look and found out that D3 offers a nodeSize and separation method but for some reason it did not work.
I found this blog post talking about the issue but he says
The size property doesn’t exist in nodes, so it will be whatever property you want to control the size of them.
but clearly there is a nodeSize method so I feel like I am simply using the method incorrectly and/or the blog post is out-of-date. I want to shape my nodes to be the size of the rectangle and space them out evenly so they do not overlap each other. Does anyone know how to use the methods properly? The documentation about these methods isn't explained very well and it isn't yielding any difference. I also couldn't find many examples where people changed the nodeSize of trees or needed separation for rectangular objects (there were some examples regarding circular ones but I feel that's too different...)
Here is the relevant code. I will try to prepare a JSFiddle.
var margin = {top: 20, right: 120, bottom: 20, left: 120},
height = 960 - margin.right - margin.left,
width = 800 - margin.top - margin.bottom,
rectW = 70;
rectH = 30;
//bbox = NaN,
maxTextLength = 0;
var i = 0,
duration = 750,
root;
//paths from each node drawn initially here
//changed to d.x, d.y
var diagonal = d3.svg.diagonal()
.projection(function(d) { return [d.x+rectW/2, d.y+rectH/2];
//.projection(function(d) { return [d.x+bbox.getBBox().width/2, d.y+bbox.getBBox().height/2];
});
var tree = d3.layout.tree()
.nodeSize([30,70])
.separation(function(a, b) { return (a.parent == b.parent ? 1 : 2); })
.size([width, height]);
var svg = d3.select("body")
.append("svg")
.attr("height","100%").attr("width","100%")
.call(d3.behavior.zoom().on("zoom", redraw))
.append("g")
.attr("transform", "translate(" + margin.top + "," + margin.left + ")");
UPDATE 05/04/2018: It is my understanding d3 has changed a lot (for the better) to be a lot more modular. For those who are looking towards this answer, this was using a much older version of d3 (specifically v3).
A lot of the findings are still relevant for the d3-hierarchy package under cluster.size() and cluster.nodeSize() and I am planning to potentially update my example to use that. For historical reference though, I'm leaving the bottom untouched.
Here is a jsFiddle: http://jsfiddle.net/augburto/YMa2y/
EDIT: Updated and move the example to Codepen. The example still exists on jsFiddle but Codepen seems to have a nicer editor and allows you to easily fork. I'll also try to add the example directly to this answer once I've reduced the amount of content in it.
http://codepen.io/augbog/pen/LEXZKK
Updating this answer. I talked with my friend and we looked at the source for size and nodeSize
tree.size = function(x) {
if (!arguments.length) return nodeSize ? null : size;
nodeSize = (size = x) == null;
return tree;
};
tree.nodeSize = function(x) {
if (!arguments.length) return nodeSize ? size : null;
nodeSize = (size = x) != null;
return tree;
};
When you set a size for the tree, you are setting a fixed size so that the tree has to conform to that width and height. When you set a nodeSize, the tree has to be dynamic so it resets the size of the tree.
When I specified size after nodeSize, I was pretty much overriding what I wanted haha...
Bottom line: If you want nodeSize to work, you can't have a fixed tree size. It will set the size to null. Do not declare a size if you are declaring a nodeSize.
EDIT: D3.js actually updated the documentation. Thanks to whoever did that because it is way clearer now!
The nodeSize property is exclusive with tree.size; setting
tree.nodeSize sets tree.size to null.
This is what my tree looks like now. I have also added zoom functionality as well as how to center text within the rectangle.
I didn't quite understand the accepted answer until I did some digging of my own, so I thought I'd share what I found as well...
If you are using .size() and your nodes are overlapping, use .nodeSize() instead
As explained in the accepted answer, .size() sets the tree's available size, and so depending on the spacing between cousin nodes, second cousins, etc. they may get squished together and overlap. Using .nodeSize() simply says each node should get this much space, so they will never overlap!
The code that ended up working for me was
var nodeWidth = 300;
var nodeHeight = 75;
var horizontalSeparationBetweenNodes = 16;
var verticalSeparationBetweenNodes = 128;
var tree = d3.layout.tree()
.nodeSize([nodeWidth + horizontalSeparationBetweenNodes, nodeHeight + verticalSeparationBetweenNodes])
.separation(function(a, b) {
return a.parent == b.parent ? 1 : 1.25;
});
Without horizontalSeparationBetweenNodes and verticalSeparationBetweenNodes the nodes edges were touching each other. I also added this .separation() to decrease the amount of space between cousin nodes, as my nodes are pretty wide and lots of space was getting wasted.
Note: This is for d3 v3, not v4
First, thanks to all those who posted before, pure gold.
I wanted to add to this post for those that might be struggling with the offset problem associated with a horizontally drawn tree.
The key is, if you switch from .size() to .nodeSize() on a horizontal tree, you'll notice your root node seems to jump/reorient to be located at (0,0).
And per the d3.js documentation this is actually the case (see https://github.com/d3/d3-hierarchy/blob/master/README.md#tree_nodeSize )
However, to adjust you just need to make sure to reorient your viewBox.
That is to say, when you .append your svg you need to explicitly set your viewBox. Here's my hacky little line where it worked for me...
svg = d3.select("#tree").append("svg")
.attr("width", width + margin.right + margin.left)
.attr("height", height + 0 + 0)
.attr("viewBox", "0 "+(-1*(height-margin.top-margin.bottom)/2)+" "+width+" "+height)
.append("g")
.attr("transform", "translate("
+ margin.left + "," + 0 + ")");
D3 now does things through Observable.
To set the nodeSize look for the line:
main.variable(observer("tree")).define("tree", ["d3","dx","dy"],
function(d3,dx,dy){return(
d3.tree()
And set nodeSize with added constants:
main.variable(observer("tree")).define("tree", ["d3","dx","dy"],
function(d3,dx,dy){return(
d3.tree().nodeSize([dx + 10, dy + 10])
Or use a function to set values wrt chart size as discussed in other answer using the older D3 approach.

Categories

Resources