Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Centering of d3 tree changes when specifying nodeSize

Tags:

d3.js

I'm using d3.v4 for this. When I have

var treemap = d3.tree().size([height, width])

the tree is nicely centred

enter image description here

but (because I want to specify the vertical spacing between nodes) when I change this to

var treemap = d3.tree().size([height, width]).nodeSize([40]);

the tree moves to the top left:

enter image description here

Does anyone know why this might be happening?

like image 799
AlexG Avatar asked May 17 '17 00:05

AlexG


1 Answers

I believe that d3.tree().size() and d3.tree().nodeSize() are exclusive in relation to one another. Setting one nullifies the other. The v3 documentation (here) is explicit in this regard, while the v4 documentation (here) might suggest that this is still true. Quick testing suggests v4 is the same as v3 in regards to this. (see also: this answer, or this one.)

More importantly, "When a node size is specified, the root node is always positioned at ⟨0, 0⟩." (API documentation).

When using size, the positioning of each node takes advantage of available space - positioning in relation to the specified width/height with no consideration for node density. When using nodeSize, positioning is relative to other nodes, not the available area for the diagram (though it will be anchored at [0,0]). By initially using .size, you didn't have to worry about positioning. Using .nodeSize, you'll have to set a transform for your nodes so that they are centered properly.

To position it properly, you'll need to set a transform on your container so that [0,0], and the root node, is more appropriately placed (as in the margin-less example below, at [0,height/2]):

var data = { "name": "Parent", "children": [ { "name": "Child A", "children": [ { "name": "Grandchild" } ] }, { "name": "Child B", } ] };

var width = 500;
var height = 500;
var svg = d3.select("body").append("svg")
      .attr("width", width)
      .attr("height", height);
      
var g = svg.append("g").attr('transform','translate(0,'+ (height/2) +')');
var root = d3.hierarchy(data);
      
var tree = d3.tree()
    .nodeSize([width/2,height/2]);

 var link = g.selectAll(".link")
    .data(tree(root).links())
    .enter().append("path")
      .attr("class", "link")
      .attr("d", d3.linkHorizontal()
          .x(function(d) { return d.y; })
          .y(function(d) { return d.x; }));

  var node = g.selectAll(".node")
    .data(root.descendants())
    .enter().append("g")
      .attr("class", function(d) { return "node" + (d.children ? " node--internal" : " node--leaf"); })
      .attr("transform", function(d) { return "translate(" + d.y + "," + d.x + ")"; })

  node.append("circle")
      .attr("r", 2.5);
.node circle {
  fill: #fff;
  stroke: steelblue;
  stroke-width: 3px;
}

.link {
  fill: none;
  stroke: #ccc;
  stroke-width: 2px;
}
svg {
    background: #eee;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/4.9.1/d3.min.js"></script>

As compared with the automatic spacing of d3.tree().size():

var data = { "name": "Parent", "children": [ { "name": "Child A", "children": [ { "name": "Grandchild" } ] }, { "name": "Child B", } ] };

var width = 500;
var height = 500;
var svg = d3.select("body").append("svg")
      .attr("width", width)
      .attr("height", height);
      
var g = svg.append("g");
var root = d3.hierarchy(data);
      
var tree = d3.tree()
    .size([width,height]);

 var link = g.selectAll(".link")
    .data(tree(root).links())
    .enter().append("path")
      .attr("class", "link")
      .attr("d", d3.linkHorizontal()
          .x(function(d) { return d.y; })
          .y(function(d) { return d.x; }));

  var node = g.selectAll(".node")
    .data(root.descendants())
    .enter().append("g")
      .attr("class", function(d) { return "node" + (d.children ? " node--internal" : " node--leaf"); })
      .attr("transform", function(d) { return "translate(" + d.y + "," + d.x + ")"; })

  node.append("circle")
      .attr("r", 2.5);
.node circle {
  fill: #fff;
  stroke: steelblue;
  stroke-width: 3px;
}

.link {
  fill: none;
  stroke: #ccc;
  stroke-width: 2px;
}
svg {
    background: #eee;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/4.9.1/d3.min.js"></script>
like image 111
Andrew Reid Avatar answered Oct 04 '22 20:10

Andrew Reid