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
Related
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;
});
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.
I am building something quite similar to this. What I would love is to make every node either their size as defined in the json file, OR, if it has no size attribute but a children attribute in json, the sum of all of its children's sizes. How would one go about doing that? I have tried various methods but short of adding things up and hardcoding it in JSON, which is a bit lame, I haven't found anything that really would have worked ;( Any suggestions, hive mind?
If your data is a tree structure, you could use a Partition Layout to initialize positions and sizes of nodes. The d.value returned by partition for parent nodes is by default the sum of values for all children nodes, assuming you've properly set the value accessor function to return the data variable that you want to use for size for leaf nodes.
Although the standard display in partition examples is to have space-filling rectangles or arcs instead of nodes and links, it still has all the basic functionality of the other hierarchy layouts. So once you've run the layout on your root to generate your array of nodes, you can run the links function to calculate the links.
If you still want a force-based layout instead of a static tree, you can just pass in your nodes and links to the force layout and start it up.
In sum:
var root = /*root object read from JSON data*/;
var w,h; /*set appropriately */
var partition = d3.layout.partition()
.size([w,h])
.value(function(d){return d.size;})
.children(function(d){return d.children;})
//optional: this is the default, change as needed
.sort(null);
//optional: turns off sorting,
//the default is to sort children by descending size
var nodes = partition(root);
var links = partition.links(nodes);
initialize();
var force = d3.layout.force()
.nodes(nodes)
.links(links)
.size([w,h])
/*and any other customization*/
.start();
svg.on("tick", update);
One thing to note. The x value created by partition layout is designed to be the corner of a rectangle instead of the centre of a circle. So if you position your nodes based on the original x value, you'll end up with parents off to the far left of their children. If you're running everything through a force-based layout afterwards, it will sort itself out eventually, but you can centre them from the beginning by setting d.x = d.x + d.dx/2 and d.y = d.y + d.dy/2 on all your nodes during initialization (e.g., using an .each() call in your enter() chain). And of course, use d.value to initialize your node size (with an appropriate scale).
Introduction
I'm building an HTML5 web application that creates a visual representation of a binary search tree from a given list of numbers.
Currently, I have an algorithm which calculates the visual spacing between nodes on each row based on the maximum depth of the tree (which is a base-0 value):
offset = 50
offset *= pow(2, maxDepth - currentDepth)
From here, the position of the node is determined using this offset and the x-position of its parent.
The algorithm works well, because it's always able to accommodate for the widest-possible tree of any depth. However, this also makes the tree unnecessarily wide at times.
Examples
Tree branching to the left (too wide):
Tree branching to the left http://f.cl.ly/items/0c0t0L0L0o411h092G2w/left.png
Tree branching to both sides (left and right sides could be closer together).
Tree branching to both sides http://f.cl.ly/items/0r3X1j0w3r1D3v1V1V3b/left-right.png
Ideally, the above tree should be shaped like a pyramid, with a smaller width and with the sides straight, as depicted below:
Balanced tree (case where the algorithm works best):
Balanced tree http://f.cl.ly/items/203m2j2i3P1F2r2T3X02/balanced.png
Implementation
Properties
I'm using Backbone.js to create nodes from a Node model. Each node has the following properties:
parent (the parent node)
left (the left child node)
right (the right child node)
x (the x-position of the node in pixels)
y (the y-position of the node in pixels)
The x and y properties above are calculated based on the direction the node branches from:
if (parent.get('left') === node) {
x = parentX - offsetX;
y = parentY + offsetY;
} else if (parent.get('right') === node) {
x = parentX + offsetX;
y = parentY + offsetY;
}
At this point, the x and y properties are the exact values used to position the nodes (each is positioned absolute within a container element).
Methods
getDepth() (returns the base-0 depth of a node)
getMaxDepth() (returns the depth of the last row in the tree)
getRow(n) (returns an array of all nodes at depth-n)
Question
Therefore, my question is simple:
What is the best algorithm to minimize the aesthetic width of my binary tree?
It could help you if you looked at the answers given to a similar question; they contain links to software doing exactly the kind of tree visualization that you want.
Aesthetics is highly subjective, so this is just my opinion. I think my guidelines (not an algorithm) would be the following. I am assuming that the order of children is important (as these are binary search trees).
Only x coordinates are interesting; y coordinates should only be determined by the node's level. (I would find it rather ugly if this was violated but, as I said, tastes differ. However, the rest is based on this assumption.)
No nodes in the same level should be closer than some fixed minimum distance (say D).
If a node has two children at x1 and x2, I would prefer it to be placed at (x1+x2)/2. In some cases, it would be preferable to select some other coordinate in [x1..x2] (possibly one of its ends). I guess there could be unusual cases where a coordinate outside [x1..x2] would be preferable.
If a node has one child at x1 and its parent is at xp, I would prefer it to be placed at (x1+xp)/2 (so that it lies on the line connecting its parent to its child). In some cases, it would be preferable to deviate from this and select some other coordinate in [xp..x1] (or even outside).
Let's call width of a level the distance between the leftmost and the rightmost node. The width of the widest level should be minimal.
These guidelines impose constraints that cannot be satisfied all at the same time. Therefore, you should prioritize them, and this is again subjective. E.g., what's more important, #4 or #5? Your sketch for the 5-node tree implies that #4 is more important; if #5 was more important you'd get a house-like picture (vertical lines); if both were important, then your current result would be fine.
One way to tackle this is by assigning weights to the guidelines and define penalties if these are not followed. E.g., in guideline #3, you could and penalize with abs(x-(x1+x2)/2) if a parent is placed at x which is not halfway between its children; you could also assign a weight that tells you how important this is, in comparison with other guidelines. You should then try to minimize the total weighted penalty of the solution. In general, this would give you a constraint optimization problem and there are several ways to solve such problems.
You can use an AVL tree. These self-balance on insertion giving you a balanced tree after every insertion.
http://en.wikipedia.org/wiki/AVL_tree
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.