D3 tree vertical separation

2019-02-04 19:54发布

问题:

I am using the D3 tree layout, such as this one: http://mbostock.github.com/d3/talk/20111018/tree.html

I have modified it for my needs and am running into an issue. The example has the same issue too where if you have too many nodes open then they become compact and makes reading and interacting difficult. I am wanting to defined a minimum vertical space between nodes while re-sizing stage to allow for such spacing.

I tried modifying the separation algorithm to make it work:

.separation(function (a, b) {
    return (a.parent == b.parent ? 1 : 2) / a.depth;
})

That didn't work. I also tried calculating which depth had the most children then telling the height of the stage to be children * spaceBetweenNodes. That got me closer, but still was not accurate.

depthCounts = [];
nodes.forEach(function(d, i) { 
    d.y = d.depth * 180;

    if(!depthCounts[d.depth])
        depthCounts[d.depth] = 0;

    if(d.children)
    {
        depthCounts[d.depth] += d.children.length;
    }
});

tree_resize(largest_depth(depthCounts) * spaceBetweenNodes);

I also tried to change the node's x value too in the method below where it calculates the y separation, but no cigar. I would post that change too but I removed it from my code.

nodes.forEach(function(d, i) { 
    d.y = d.depth * 180;
});

If you can suggest a way or know a way that I can accomplish a minimum spacing vertically between nodes please post. I will be very grateful. I am probably missing something very simple.

回答1:

As of 2016, I was able to achieve this using just

tree.nodeSize([height, width])

https://github.com/mbostock/d3/wiki/Tree-Layout#nodeSize

The API Reference is a bit poor, but is works pretty straight forward. Be sure to use it after tree.size([height, width]) or else you will be overriding your values again.

For more reference: D3 Tree Layout Separation Between Nodes using NodeSize



回答2:

I was able to figure this out with help from a user on Google Groups. I was not able to find the post. The solution requires you to modify D3.js in one spot, which is not recommended but it was the only to get around this issue that I could find.

Starting around line 5724 or this method: d3_layout_treeVisitAfter

change:

d3_layout_treeVisitAfter(root, function(node) {
    node.x = (node.x - x0) / (x1 - x0) * size[0];
    node.y = node.depth / y1 * size[1];
    delete node._tree;
});

to:

d3_layout_treeVisitAfter(root, function(node) {
    // make sure size is null, we will make it null when we create the tree
    if(size === undefined || size == null) 
    { 
        node.x = (node.x - x0) * elementsize[0]; 
        node.y = node.depth * elementsize[1]; 
    } 
    else 
    { 
        node.x = (node.x - x0) / (x1 - x0) * size[0];
        node.y = node.depth / y1 * size[1]; 
    } 
    delete node._tree;
});

Below add a new variable called: elementsize and default it to [ 1, 1 ] to line 5731

var hierarchy = d3.layout.hierarchy().sort(null).value(null)
    , separation = d3_layout_treeSeparation
    , elementsize = [ 1, 1 ] // Right here
    , size = [ 1, 1 ];

Below that there is a method called tree.size = function(x). Add the following below that definition:

tree.elementsize = function(x) {
    if (!arguments.length) return elementsize;
    elementsize = x;
    return tree;
};

Finally when you create the tree you can change the elementsize like so

var tree = d3.layout.tree()
             .size(null)
             .elementsize(50, 240);


回答3:

I know I'm not supposed to respond to other answers, but I don't have enough reputation to add a comment.

Anyway, I just wanted to update this for people using the latest d3.v3.js file. (I assume this is because of a new version, because the line references in the accepted answer were wrong for me.)

The d3.layout.tree function that you are editing is found between lines 6236 and 6345. d3_layout_treeVisitAfter starts on line 6318. The hierarchy variable is declared on line 6237. The bit about tree.elementsize still stands - I put it on line 6343.

Lastly (I assume this was an error): when you create the tree, put the dimensions inside square brackets, like you normally do with "size". So:

var tree = d3.layout.tree()
         .size(null)
         .elementsize([50, 240]);


回答4:

The original fix you proposed will work, you just have to make sure you do it after you add everything to the canvas. d3 recalculates the layout each time you enter, exit, append, etc. Once you've done all that, then you can fiddle with the d.y to fix the depth.

nodes.forEach(function(d) { d.y = d.depth * fixdepth});