3

Is the .append() method the only way to add a D3 object to HTML?

I am using the append() method to add a treemap to the body of my HTML file, but I would like to add the treemap inside a element. The problem is that append adds after the current element, not inside the current element. I want to resize the treemap to expand to 100% of the initial height and width of the browser, like this. Adding CSS seems like the most straight-forward way.

I have tried to 'trick' D3.js by appending the treemap to a div within a div, but that didn't work.

Perhaps I am asking the wrong question, and I should resize the treemap within the d3.js script? I have tried to change the default styles provided by bl.ock.org, but I haven't gotten the treemap to expand to the full screen.

Here is the code that I think initializes the treemap.

var margin = {top: 40, right: 10, bottom: 10, left: 10},
    width = 1000 - margin.left - margin.right,
    height = 650 - margin.top - margin.bottom;

var color = d3.scale.category20c();

var treemap = d3.layout.treemap()
    .size([width, height])
    .sticky(false)
    .value(function(d) { return d.size; });

var div = d3.select("body").append("div")
    .style("position", "relative")
    .style("width", (width + margin.left + margin.right) + "px")
    .style("height", (height + margin.top + margin.bottom) + "px")
    .style("left", margin.left + "px")
    .style("top", margin.top + "px");

Here is the code that changes the size of the node to accommodate the javascript values.

var root;

socket.on('update', function(stream) {
  console.log('tweets')

  div.datum(root).selectAll(".node").remove();

  root = stream.masterlist;

  var node = div.datum(root).selectAll(".node")
      .data(treemap.nodes)
    .enter().append("div")
      .attr("class", "node")
      .call(position)
      .style("background", function(d) { return d.children ? color(d.name) : null; })
      .text(function(d) { return d.children ? null : d.name; });

  });


});

function position() {
  this.style("left", function(d) { return d.x + "px"; })
      .style("top", function(d) { return d.y + "px"; })
      .style("width", function(d) { return Math.max(0, d.dx - 1) + "px"; })
      .style("height", function(d) { return Math.max(0, d.dy - 1) + "px"; });
}
Community
  • 1
  • 1
user2954463
  • 2,362
  • 2
  • 23
  • 37
  • 1
    There is at least [`.insert(element[, before])`](https://github.com/mbostock/d3/wiki/Selections#insert) to fine-tune where D3 adds elements. – musically_ut Mar 19 '14 at 19:15

2 Answers2

4

I would approach the problem a little differently.

I would use the viewBox attribute to keep the coordinate system of the visualization constant. Personally, I like <svg viewBox="-100 -100 200 200"> meaning that (-100, -100) is the top-left and (100, 100) is the bottom-right corner. You can choose 0 0 100 100 as well.

Then I will not set any width and height on the svg element explicitly, but instead rely on CSS to give them the correct dimensions (say display: block; width: 80%; margin: auto; height: 80%) and then control how the visualization scales using preserveAspectRatio attribute. Usually, I find that the defaults (xMidYMid and meet) are sufficient for me. However, your mileage might vary.

This is an example of using this approach to resize the visualisation when the size of the container changes without redrawing the visualisation: http://jsfiddle.net/ss47m/

Here: http://jsfiddle.net/ss47m/1/ size of the container is made to cover the whole window.


An alternate approach is binding to the resize event on window (like nvd3 does with windowResize), and redraw your chart each time the window changes size. I find that a bit excessive.

musically_ut
  • 34,028
  • 8
  • 94
  • 106
3

First, to clarify:

The problem is that append adds after the current element, not inside the current element.

That's not correct. If you do d3.select("div.parent").append("div") the new div will be inside the parent div, but after any child content already within the parent. To make the new element the first child of the parent, you would do d3.select("div").insert("div", function(){return this.children[0];}). But I don't think that's really your problem here...

For the question of how to calculate a treemap so that it automatically fills the browser window:

If you just want the map to fill the initial size of the window, and not worry about resizing, you need to (a) find out the window size; (b) set your container div's height and width to that size; and (c) use those dimensions as the input size to the treemap function.

Basically, just change these lines:

width = 1000 - margin.left - margin.right,
height = 650 - margin.top - margin.bottom;

to

width = window.innerWidth - margin.left - margin.right,
height = window.innerHeight - margin.top - margin.bottom;

and then the rest of your code is the same.

If you also want the treemap to scale with the window, you should set the position and size of the child elements using percentage values. That will be easiest if you initialize the treemap using [100,100] as the size, and then you can simply replace all the "px" in your position function with "%".

However, you'll still need some CSS to make your container div fill up the full size of the browser window and resize with it. How tricky that is depends on how much else you have going on in your page layout. You might also want to check out this answer. If you can't get the CSS working, you can always listen for window resize events and reset the container's width and height based on window size. If the children are all sized base on percentages then they will adjust to whatever size you set for the parent.

Finally, you'll probably want to set min-height and min-width properties on the container to guarantee that your treemap elements will always be a decent size even if it means scrolling.

Community
  • 1
  • 1
AmeliaBR
  • 27,344
  • 6
  • 86
  • 119