Building a treemap

Treemaps were introduced by Ben Shneiderman in 1991. A treemap displays hierarchical tree-structured data as a set of recursively subdivided rectangles. In other words, it displays each branch of the tree as a large rectangle which is then tiled with smaller rectangles representing sub-branches. This process continuously repeats itself till it reaches the leaves of the tree.

Note

For more information on treemaps, see this paper by Ben Shneiderman at http://www.cs.umd.edu/hcil/treemap-history/

Before we dive into the code example, let's first define what we mean by hierarchical data .

So far we have learned many types of visualizations capable of representing flat data structure usually stored in one or two dimensional arrays. In the rest of this chapter, we will switch our focus onto another common type of data structure in data visualization—the hierarchical data structure. Instead of using arrays, as in the case of flat data structures, hierarchical data are usually structured as a rooted tree. The following JSON file shows a typical hierarchical data you would expect in a data visualization project:

{
  "name": "flare",
  "children": [
  {
    "name": "analytics",
    "children": [
    {
      "name": "cluster",
      "children": [
        {"name": "AgglomerativeCluster", "size": 3938},
        {"name": "CommunityStructure", "size": 3812},
        {"name": "MergeEdge", "size": 743}
      ]
    },
    {
      "name": "graph",
      "children": [
        {"name": "BetweennessCentrality", "size": 3534},
        {"name": "LinkDistance", "size": 5731}
      ]
    },
    {
      "name": "optimization",
      "children": [
        {"name": "AspectRatioBanker", "size": 7074}
      ]
    }
  ]  
  ]
}

This is a shortened version of a popular hierarchical dataset used in the D3 community for demonstration purposes. This data is extracted from a popular flash based data visualization library—Flare, created by the UC Berkeley Visualization Lab. It shows the size and hierarchical relationship amongst different packages within the library.

Note

See the official Flare site for more information on the project: http://flare.prefuse.org/.

As we can see quite easily this particular JSON feed is structured as a typical singly-linked rooted tree with each node having a single parent and multiple child nodes stored in the children array. This is the most natural way to organize your hierarchical data in order to be consumed by the D3 hierarchical layouts. For the rest of this chapter, we will use this particular dataset for exploring different hierarchical data visualization techniques D3 has to offer.

Getting ready

Open your local copy of the following file in your web browser:

https://github.com/NickQiZhu/d3-cookbook/blob/master/src/chapter9/treemap.html.

How to do it...

Now let's see how we can use the D3 treemap layout to visually represent this kind of hierarchical data.

function treemapChart() {
  var _chart = {};

  var _width = 1600, _height = 800,
    _colors = d3.scale.category20c(),
   _svg,
   _nodes,
   _x = d3.scale.linear().range([0, _width]),
   _y = d3.scale.linear().range([0, _height]),
   _valueAccessor = function (d) {
      return 1;
    },
  _bodyG;

  _chart.render = function () {
    if (!_svg) {
      _svg = d3.select("body").append("svg")
        .attr("height", _height)
        .attr("width", _width);
    }

    renderBody(_svg);
  };

  function renderBody(svg) {
    // explained in details in the 'how it works...' section
    ... 

    renderCells(cells);
  }

  function renderCells(cells){
    // explained in details in the 'how it works...' section
    ...
  }

  // accessors omitted
  ...

  return _chart;
}

d3.json("flare.json", function (nodes) {
  var chart = treemapChart();
  chart.nodes(nodes).render();
});

This recipe generates the following treemap visualization:

How to do it...

Treemap

How it works...

At this point you might be surprised how little code is needed to implement a complex data visualization like this. This is because most of the heavy lifting is done by d3.layout.treemap.

function renderBody(svg) {
  if (!_bodyG) {
    _bodyG = svg.append("g")
      .attr("class", "body");

      _treemap = d3.layout.treemap() //<-A
        .round(false)
        .size([_width, _height])
        .sticky(true);
      }

      _treemap.value(_valueAccessor); //<-B

  var nodes = _treemap.nodes(_nodes) //<-C
    .filter(function (d) {
      return !d.children; //<-D
    });

  var cells = svg.selectAll("g") //<-E
    .data(nodes);

  renderCells(cells);
    }

The treemap layout is defined on line A with some basic custom settings:

  • round(false): If rounding is on, the treemap layout will round to exact pixel boundaries. This is great when you want to avoid antialiasing artifacts in SVG.
  • size([_width, _height]): It sets the layout boundary to the size of this SVG.
  • sticky(true): In sticky mode, the treemap layout will try to preserve the relative arrangement of nodes (rectangles in our case) across the transition.
  • value(_valueAccessor): One feature this recipe offers is the ability to switch the treemap value accessor on the fly. Value accessor is used by a treemap to access value field on each node. In our case, it can be either one of the following functions:
    function(d){ return d.size; } // visualize package size
    function(d){ return 1; } // visualize package count
  • To apply a treemap layout on Flare JSON datafeed, we simply set the nodes on the treemap layout to the root node in our JSON tree (line C). Treemap nodes are then further filtered to remove parent nodes (nodes that have children) on line D since we only want to visualize the leaf nodes while using coloring to highlight the package grouping in this treemap implementation. The layout data generated by treemap layout contains the following structure:
    How it works...

    Treemap node object

As shown, the treemap layout has annotated and calculated quite a few attributes for each node using its algorithm. Many of these attributes can be useful when visualizing and in this recipe we mostly care about the following attributes:

  • x: Cell x coordinate
  • y: Cell y coordinate
  • dx: Cell width
  • dy: Cell height

On line E, a set of svg:g elements were created for the given nodes. The function renderCells is then responsible for creating rectangles and its labels:

function renderCells(cells){
  var cellEnter = cells.enter().append("g")
    .attr("class", "cell");

  cellEnter.append("rect")
  cellEnter.append("text");

  cells.transition().attr("transform", function (d) {
    return "translate("+d.x+","+d.y+")"; //<-F
  })
  .select("rect")
    .attr("width", function (d) {return d.dx - 1;})
    .attr("height", function (d) {return d.dy - 1;})
    .style("fill", function (d) {
      return _colors(d.parent.name); //<-G
    });

  cells.select("text") //<-H
    .attr("x", function (d) {return d.dx / 2;})
    .attr("y", function (d) {return d.dy / 2;})
    .attr("dy", ".35em")
    .attr("text-anchor", "middle")
    .text(function (d) {return d.name;})
    .style("opacity", function (d) {
      d.w = this.getComputedTextLength();
      return d.dx > d.w ? 1 : 0; //<-I
    );

  cells.exit().remove();
}

Each rectangle is placed at its location (x, y) determined by the layout on line F, and then its width and height are set to dx and dy. On line G, we colored every cell using its parent's names therefore making sure all children belonging to the same parent are colored the same way. From line H onward we created the label (svg:text) element for each rectangle and setting its text to the node name. One aspect worth mentioning here is that in order to avoid displaying label for the cells that are smaller than the label itself, the opacity of label is set to 0 if the label is larger than the cell width (line I).

Tip

Technique – auto-hiding label

What we have seen here on line I is a useful technique in visualization to implement auto-hiding labels. This technique can be considered generally in the following form:

.style("opacity", function (d) {
    d.w = this.getComputedTextLength();
    return d.dx > d.w ? 1 : 0;
)

See also

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset