43

Right now I am trying to separate my rectangle nodes because they overlap as shown in the picture below:

enter image description here

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 + ")");
aug
  • 11,138
  • 9
  • 72
  • 93
  • It looks like your `nodeSize` specification gives the height as width and the width as height. – Lars Kotthoff Jul 10 '13 at 10:47
  • @LarsKotthoff I did the reverse but the problem is there seems to be no difference when I change it either way. I was expecting the nodes to space themselves out properly when I inputted the size but maybe I'm misinterpreting how it works? – aug Jul 10 '13 at 16:39
  • That's my understanding from the documentation as well. Did you play around with the values to see if they made any difference? – Lars Kotthoff Jul 10 '13 at 18:18
  • @LarsKotthoff Yeah I even set them to ridiculous amounts such as (1000, 500) and nothing changed... – aug Jul 10 '13 at 23:10
  • @LarsKotthoff I figured it out... you need to specify `size` first... Posted an answer. Thanks for trying to help though! – aug Jul 11 '13 at 22:36
  • @aug: I saw the codepen demo you created and its awesome. Its just that I found that when you open a node, the child nodes get hidden outside the viewport. Can anything be done about it. I want to have a scroll at least. – invincibleDudess Apr 20 '16 at 10:38
  • 1
    @invincibleDudess I think there are a few ways you could go about it but there will always be an issue depending on how big your tree is since viewports will only be so big. My example allows you to drag the tree around and zoom in or out to handle this. You can also [consider repositioning where your tree is located so every time you click on a node, it centers the tree](http://stackoverflow.com/questions/18924778/get-coordinates-of-clicked-on-node-in-d3-tree-and-centre). Hope that helps! – aug Apr 20 '16 at 21:46

4 Answers4

53

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.

Picture where nodeSize is set at width of 100 and height of 30

aug
  • 11,138
  • 9
  • 72
  • 93
20

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

Danny Harding
  • 1,666
  • 19
  • 25
2

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 + ")");
Kevin Gunn
  • 21
  • 2
-2

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.

Cybernetic
  • 12,628
  • 16
  • 93
  • 132