Raspberry Pi Pico Tips and Tricks

Saturday, 11 January 2014

Tree diagrams in d3.js

The following post is a portion of the D3 Tips and Tricks book which is free to download. To use this post in context, consider it with the others in the blog or just download the the book as a pdf / epub or mobi .
----------------------------------------------------------

What is a Tree Diagram?

The ‘Tree layout’ is not a distinct type of diagram per se. Instead, it’s representative of D3’s family of hierarchical layouts.
It’s designed to produce a ‘node-link’ diagram that lays out the connection between nodes in a method that displays the relationship of one node to another in a parent-child fashion.
For example, the following diagram shows a root node (the starting position) labelled ‘Top Node’ which has two children (Bob: Child of Top Node and Sally: Child of Top Node). Subsequently, Bob:Child of Top Node has two dependent nodes (children) ‘Son of Bob’ and ‘Daughter of Bob’.
The clear advantage to this style of diagram is that describing it in text is difficult, but representing it graphically makes the relationships easy to determine.
The data required to produce this type of layout needs to describe the relationships, but this is not necessarily an onerous task. For example, the following is the data (in JSON form) for the diagram above and it shows the minimum information required to form the correct layout hierarchy.
  {
    "name": "Top Node",
    "children": [
      {
        "name": "Bob: Child of Top Node",
        "parent": "Top Node",
        "children": [
          {
            "name": "Son of Bob",
            "parent": "Bob: Child of Top Node"
          },
          {
            "name": "Daughter of Bob",
            "parent": "Bob: Child of Top Node"
          }
        ]
      },
      {
        "name": "Sally: Child of Top Node",
        "parent": "Top Node"
      }
    ]
  }
It shows each node as having a name that identifies it on the tree and where appropriate, the children it has (as an array) and its parent.
The data shown above is arranged as a hierarchy and it is not always possible to source data that is organised so nicely. As we go through use examples for this type of diagram we will look at options for importing ‘flat’ data and converting it into a hierarchical form.
There is a wealth of examples of tree diagrams on the web, but I would recommend a visit to the D3.js gallery maintained by Christophe Viau as a starting point to get some ideas.
In this chapter we’re going to look at a very simple piece of code to generate a tree diagram before looking at different ways to adapt it. Including rotating it to be vertical, adding some dynamic styling to the nodes, importing from a flat file and from an external source. Finally we’ll look at a more complex example that is more commonly used on the web that allows a user to expand and collapse nodes interactively.

A simple Tree Diagram explained

We are going to work through a simple example of the code that draws a tree diagram, This is more for the understanding of the process rather than because it is a good example of code for drawing a tree diagram. It is a very limited example that lacks any real interactivity which is one of the strengths of d3.js graphics. However, we will outline the operation of an interactive version towards the end of the chapter once we have explored some possible configuration options that we might want to make.
The graphic that we are going to generate will look like this…

And the full code for it looks like this;
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8">

    <title>Collapsible Tree Example</title>

    <style>

 .node circle {
   fill: #fff;
   stroke: steelblue;
   stroke-width: 3px;
 }

 .node text { font: 12px sans-serif; }

 .link {
   fill: none;
   stroke: #ccc;
   stroke-width: 2px;
 }
 
    </style>

  </head>

  <body>

<!-- load the d3.js library --> 
<script src="http://d3js.org/d3.v3.min.js"></script>
 
<script>

var treeData = [
  {
    "name": "Top Level",
    "parent": "null",
    "children": [
      {
        "name": "Level 2: A",
        "parent": "Top Level",
        "children": [
          {
            "name": "Son of A",
            "parent": "Level 2: A"
          },
          {
            "name": "Daughter of A",
            "parent": "Level 2: A"
          }
        ]
      },
      {
        "name": "Level 2: B",
        "parent": "Top Level"
      }
    ]
  }
];

// ************** Generate the tree diagram  *****************
var margin = {top: 20, right: 120, bottom: 20, left: 120},
 width = 960 - margin.right - margin.left,
 height = 500 - margin.top - margin.bottom;
 
var i = 0;

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

var diagonal = d3.svg.diagonal()
 .projection(function(d) { return [d.y, d.x]; });

var svg = d3.select("body").append("svg")
 .attr("width", width + margin.right + margin.left)
 .attr("height", height + margin.top + margin.bottom)
  .append("g")
 .attr("transform", "translate(" + margin.left + "," + margin.top + ")");

root = treeData[0];
  
update(root);

function update(source) {

  // Compute the new tree layout.
  var nodes = tree.nodes(root).reverse(),
   links = tree.links(nodes);

  // Normalize for fixed-depth.
  nodes.forEach(function(d) { d.y = d.depth * 180; });

  // Declare the nodes…
  var node = svg.selectAll("g.node")
   .data(nodes, function(d) { return d.id || (d.id = ++i); });

  // Enter the nodes.
  var nodeEnter = node.enter().append("g")
   .attr("class", "node")
   .attr("transform", function(d) { 
    return "translate(" + d.y + "," + d.x + ")"; });

  nodeEnter.append("circle")
   .attr("r", 10)
   .style("fill", "#fff");

  nodeEnter.append("text")
   .attr("x", function(d) { 
    return d.children || d._children ? -13 : 13; })
   .attr("dy", ".35em")
   .attr("text-anchor", function(d) { 
    return d.children || d._children ? "end" : "start"; })
   .text(function(d) { return d.name; })
   .style("fill-opacity", 1);

  // Declare the links…
  var link = svg.selectAll("path.link")
   .data(links, function(d) { return d.target.id; });

  // Enter the links.
  link.enter().insert("path", "g")
   .attr("class", "link")
   .attr("d", diagonal);

}

</script>
 
  </body>
</html>
The full code for this example can be found on github, in the appendices of  D3 Tips and Tricks  or in the code samples bundled with  D3 Tips and Tricks  (simple-tree-diagram.html). A working example can be found on bl.ocks.org.
In the course of describing the operation of the file I will gloss over the aspects of the structure of an HTML file which have already been described at the start of D3 Tips and Tricks. Likewise, aspects of the JavaScript functions that have already been covered will only be briefly explained.
The start of the file deals with setting up the documents html head, body loading the d3.js script and setting up the css in the <style> section.
The css section sets styling for the circle that represents the nodes, the text alongside them and the links between them.
 .node circle {
   fill: #fff;
   stroke: steelblue;
   stroke-width: 3px;
 }

 .node text { font: 12px sans-serif; }

 .link {
   fill: none;
   stroke: #ccc;
   stroke-width: 2px;
 }
Then our JavaScript section starts and the first thing that happens is that we declare our array of data in the following code;
var treeData = [
  {
    "name": "Top Level",
    "parent": "null",
    "children": [
      {
        "name": "Level 2: A",
        "parent": "Top Level",
        "children": [
          {
            "name": "Son of A",
            "parent": "Level 2: A"
          },
          {
            "name": "Daughter of A",
            "parent": "Level 2: A"
          }
        ]
      },
      {
        "name": "Level 2: B",
        "parent": "Top Level"
      }
    ]
  }
];
As outlined at the start of the chapter, this data is encoded hierarchically in JavaScript Object Notation (JSON). Each node must have a name and either a parent or child node(s) or both. There are many examples of hierarchical data that can be encoded in this way. From the traditional parent - offspring example to directories on a hard drive or a breakdown of materials for a complex object. Any system of encoding where there is a single outcome from multiple sources like an election or an alert encoding system dependent on multiple trigger points.
The next section of our code declares some of the standard features for our diagram such as the size and shape of the svg container with margins included.
var margin = {top: 20, right: 120, bottom: 20, left: 120},
 width = 960 - margin.right - margin.left,
 height = 500 - margin.top - margin.bottom;
 
var i = 0;

var tree = d3.layout.tree()
 .size([height, width]); 
It also assigns the variable / function tree to the d3.js function that is used to assign and calculate the data required for the nodes and links for our diagram. We will be calling that later.
The next block of code declares the function that will be used to draw the links between the nodes. This isn’t the part of the code where the links are drawn, this is just declaring the variable/function that will be used when it does happen.
var diagonal = d3.svg.diagonal()
 .projection(function(d) { return [d.y, d.x]; });
This uses the d3.js diagonal function to help draw a path between two points such that the line exhibits some nice flowing curves (cubic Bézier ) to make the connection.
The next block of code appends our SVG working area to the body of our web page and creates a group elements (<g>) that will contain our svg objects (our nodes, text and links).
var svg = d3.select("body").append("svg")
 .attr("width", width + margin.right + margin.left)
 .attr("height", height + margin.top + margin.bottom)
  .append("g")
 .attr("transform", "translate(" + margin.left + "," + margin.top + ")");
The next line is one that vexed me for a while and one that I think means there are other areas of my code that could be improved (for a short interlude on why this tried me, feel free to catch this question on Stack Overflow).
root = treeData[0];
It might not look like much and to those familiar with JavaScript, it will bee a no-brainer, but what line is doing is defining what ‘tree’ from our data is going to be used. Because our data is an array, the first level of the array istreeData. The name of the first object on the first level of treeData is ‘Top Level’. This (being the first object) is object 0. Therefore our starting point is treeData[0]. We could confirm this by changing the declaration to …
root = treeData[0].children[0];
This will take the root point for our diagram as being the first child (child[0]) of the the first level of treeData. As a result, our tree diagram will look like this…

… since ‘Level 2: A’ is the first child of ‘Top Level’.
Then we call the function that draws our tree diagram.
update(root);
This calls the function update and uses the data root to create our tree.
The last significant part of the code is the function update. This is the part of the code that pulls together the functions and data that we have declared and draws our tree.
The first step in that process is to assign our nodes and links.
  var nodes = tree.nodes(root),
   links = tree.links(nodes);
This uses our previously declared tree function to work its d3.js magic on our data (root) and to determine the nodes details and from the node details we can determine the link details.
If you’re wondering how this all works, I’m afraid that I won’t be able to help much, but a starting point would be the results that the process produces which is a set of nodes, each of which has a set of characteristics. Those characteristics are; - .children: Which is an array of any children that exist for that node. - .depth: Which is the depth (described in a few paragraphs time). - .id: Which is a unique number identifier for each node. - .name: The name we have assigned from our data. - .parent: The name of the parent of the node. - .x and .y: Which are the x and y positions on the screen of the node.
From this node data a set of links joining the nodes is created. Each link consists of a .source and .target. Each of which is a node.
We then determine the horizontal spacing of the nodes.
  nodes.forEach(function(d) { d.y = d.depth * 180; });
This uses the depth of the node (as determined for each node in the nodes = tree.nodes(root) line) to calculate the position on the y axis of the screen.
The depth refers to the position in the tree relative to the root node on the left. The following picture shows how the depth relates to the position of the node in the tree.

So by adjusting our ‘expansion factor’ (currently set to 180) we can adjust the spacing of the nodes. For instance, here is the spacing changed to 80.

We then declare the variable / function node so that when we call it later it will know to select the appropriate object (a node) with the appropriate .id.
  var node = svg.selectAll("g.node")
   .data(nodes, function(d) { return d.id || (d.id = ++i); });
The next block of code assigns the variable / function nodeEnter to the action of appending a node to a particular position.
  var nodeEnter = node.enter().append("g")
   .attr("class", "node")
   .attr("transform", function(d) { 
    return "translate(" + d.y + "," + d.x + ")"; });
Then we get to the piece of code that appends the circle that comprises the node (using nodeEnter).
  nodeEnter.append("circle")
   .attr("r", 10)
   .style("fill", "#fff");
(using a radius of 10 pixels and a white fill).
And we add in the text for each node…
  nodeEnter.append("text")
   .attr("x", function(d) { 
    return d.children || d._children ? -13 : 13; })
   .attr("dy", ".35em")
   .attr("text-anchor", function(d) { 
    return d.children || d._children ? "end" : "start"; })
   .text(function(d) { return d.name; })
   .style("fill-opacity", 1);
This is a neat piece of code that allows the text to be placed on the left side of the node if it has children (d.children) or on the right if it has has no children or d._children. This is a slightly redundant piece of code (the d._children piece) for this diagram, but it becomes more useful in the interactive version towards the end of the chapter. It also aligns the text correctly and makes sure it is visible.
Then we declare the link variable / function and tell it to make a link based on all the links that have unique target id’s.
  var link = svg.selectAll("path.link")
   .data(links, function(d) { return d.target.id; });
This might not be obvious at first glance, but we only want to draw links between a node and it’s parent. There should be one less link than the total number of nodes since the root node (‘Top Level’) has no parent. Therefore only those links with unique target id’s in the data need to have links produced. If we were to replace the .target in the above code with .source we would have only two unique .source id’s. It would therefore look like this;

Our final block of JavaScript adds in our link as a diagonal path (as declared early in the JavaScript portion of the code).
  link.enter().insert("path", "g")
   .attr("class", "link")
   .attr("d", diagonal);
There are only a couple of lines of HTML to close off the file and we are left with our tree diagram!

Don’t forget, the full code for this example can be found on github, in the appendices of D3 Tips and Tricks or in the code samples bundled with D3 Tips and Tricks (simple-tree-diagram.html). A working example can be found on bl.ocks.org.

Styling nodes in a tree diagram

The nodes in a tree diagram are objects that exist to provide a representation of the structure of data, but on a tree diagram they should also be viewed as an opportunity to encode additional information about the underlying data.
From the initial simple example that we covered at the start of the chapter we have encoded a certain amount of information already. The position of the text relative to each node is determined by whether or not the node is the parent of another node (if it’s a parent it’s on the left) or a child that is on the edge of the tree (in which case it is on the right of the node).

Now, that’s nice, but are we going to be satisfied with that??? (The answer is “No” by the way.)
This example is fairly simple, but it is an example of applying different styles to the nodes to convey additional information. I should be clear at this stage that I am not advocating turning your tree diagram into something that looks like it came out of a circus, because that would be a crime against style, so don’t repeat my upcoming example, but let some of the features be a trigger for developing your own subtle, yet compelling visualizations.
Brace yourself. Here’s a picture of the tree diagram that we’re going to generate. Those with weaker constitutions should look away and flip forward a few pages.

The changes that have been made are as a result of additional data fields that have been added to the JSON array and these fields have been applied to various style options throughout the code.
The types of style changes we have made are - Variation of the diameter of nodes - Changing the fill and stroke colour of nodes - Changing the colour of links depending on the associated target node they connect to.
We’ll start by looking at the new JSON data set;
  {
    "name": "Top Level",
    "parent": "null",
    "value": 10,
    "type": "black",
    "level": "red",
    "children": [
      {
        "name": "Level 2: A",
        "parent": "Top Level",
        "value": 15,
        "type": "grey",
        "level": "red",
        "children": [
          {
            "name": "Son of A",
            "parent": "Level 2: A",
            "value": 5,
            "type": "steelblue",
            "level": "orange"
          },
          {
            "name": "Daughter of A",
            "parent": "Level 2: A",
            "value": 8,
            "type": "steelblue",
            "level": "red"
          }
        ]
      },
      {
        "name": "Level 2: B",
        "parent": "Top Level",
        "value": 10,
        "type": "grey",
        "level": "green"
      }
    ]
  }
Each node now has a value which might represent a degree of importance (we will use this to affect the radius of the nodes), a type which might indicate a difference in the type of node (they might be in an active, inactive or undetermined states) and a level which might indicate an alert level for determining problems (red = bad, orange = caution and green = normal).
Irrespective of the contrived nature of our styling options, they are applied to our tree in fairly similar ways with some subtle differences.
Remember, the full code for this example can be found on github or in the code samples bundled with D3 Tips and Tricks  (simple-tree-features.html). A working example can be found on bl.ocks.org.
The first change is to the node radius, stroke colour and fill colour.
We simply change the portion of the code that appends the circle from this…
  nodeEnter.append("circle")
   .attr("r", 10)
   .style("fill", "#fff");
… to this …
  nodeEnter.append("circle")
   .attr("r", function(d) { return d.value; })
   .style("stroke", function(d) { return d.type; })
   .style("fill", function(d) { return d.level; });
The changes return the radius attribute as a function using value, the stroke colour is returned using type and the fill colour is returned with level. This is nice and simple, but we do need to make a slight adjustment to the code that sets the distance that the text is from the nodes so that when the radius expands or contracts, the text distance from the edge of the node adjusts as well.
To do this we take the clever piece of code that adjusts the distance that the text is in the x dimension from the node that looks like this …
   .attr("x", function(d) { 
    return d.children || d._children ? -13 : 13; })
… and we add in a dynamic aspect using the value field.
   .attr("x", function(d) { 
    return d.children || d._children ? 
    (d.value + 4) * -1 : d.value + 4 })
The last thing we wanted to do is to change the colour of the link based on the colour of the target node. We accomplish this by taking the code that inserts the links…
  link.enter().insert("path", "g")
   .attr("class", "link")
   .attr("d", diagonal);
… and adding in a line that styles the link colour (the stroke) based on the level colour of the target end of the link d.target.level).
  link.enter().insert("path", "g")
   .attr("class", "link")
   .style("stroke", function(d) { return d.target.level; })
   .attr("d", diagonal);
Use the concepts here wisely. I don’t want to see any heinously styled tree diagrams floating around the internet with “Thanks to the help from D3 Tips and Tricks” next to them. Be subtle, be thoughtful :-).

Making a vertical tree diagram

Changing a tree diagram from a horizontal view to a vertical one is fairly easy. There are only three things to change from the code that we used for our original simple tree diagram.
The first is to change the orientation of the nodes by transposing the x and y coordinates.
That means taking the section of code that appends the nodes…
  var nodeEnter = node.enter().append("g")
   .attr("class", "node")
   .attr("transform", function(d) { 
    return "translate(" + d.y + "," + d.x + ")"; });
… and swapping the d.x and d.y designators so that it looks like this…
  var nodeEnter = node.enter().append("g")
   .attr("class", "node")
   .attr("transform", function(d) { 
    return "translate(" + d.x + "," + d.y + ")"; });
Because the vertical version of the tree diagram can be a lot more compact, we can adjust our difference between the depths to a more rational value. In our example we can change the separation from 180 to 100 pixels in the following line of code…
  nodes.forEach(function(d) { d.y = d.depth * 100; });
The second is to do the same adjustment for the links. We take the block of code that generates the curvy diagonal paths…
var diagonal = d3.svg.diagonal()
 .projection(function(d) { return [d.y, d.x]; });
… and swap the d.x and d.y designators so that it looks like this…
var diagonal = d3.svg.diagonal()
 .projection(function(d) { return [d.x, d.y]; });
At this point we have our tree diagram ready to go except for one small detail…

The text is still aligned to the left and right of the nodes. On this example, it looks pretty good, but if we were to introduce a few more nodes, it would start to get pretty cramped, so we can place the text above and below the nodes dependent on whether the node is a parent (above) or a child on the bottom level (below).
To do this we take the original text appending code…
  nodeEnter.append("text")
   .attr("x", function(d) { 
    return d.children || d._children ? -13 : 13; })
   .attr("dy", ".35em")
   .attr("text-anchor", function(d) { 
    return d.children || d._children ? "end" : "start"; })
   .text(function(d) { return d.name; })
   .style("fill-opacity", 1);
… and change the x attribute to a y attribute, anchor the text in the middle (which is actually a simplification of the code) and extend the distance between the node and the anchor point slightly to 18 (and -18) pixels.
  nodeEnter.append("text")
   .attr("y", function(d) { 
    return d.children || d._children ? -18 : 18; })
   .attr("dy", ".35em")
   .attr("text-anchor", "middle")
   .text(function(d) { return d.name; })
   .style("fill-opacity", 1);
And there we have it! A vertical tree diagram.

The full code for this example can be found on github or in the code samples bundled with  D3 Tips and Tricks  (simple-tree-vertical.html). A working online example can be found on bl.ocks.org.

Generating a tree diagram from ‘flat’ data

Tree diagrams are a fantastic way of displaying information, but one of the drawbacks (to the examples we’ve been using so far) is the need to have your data encoded hierarchically. Most data in a raw form will be flat. That is to say, it won’t be formatted as an array with the parent - child relationships. Instead it will be a list of objects (which we will want to turn into nodes) that might describe the relationship to each other, but they won’t be encoded that way. For example, the following is the flat representation of the example data we have been using thus far.
    { "name" : "Level 2: A", "parent":"Top Level" },
    { "name" : "Top Level", "parent":"null" },
    { "name" : "Son of A", "parent":"Level 2: A" },
    { "name" : "Daughter of A", "parent":"Level 2: A" },
    { "name" : "Level 2: B", "parent":"Top Level" }
It is actually fairly simple and consists of only the name of the node and the name of it’s parent node. It’s easy to see how this data could be developed into a hierarchical form, but it would take a little time and for a larger data set, that would be tiresome.
Luckily computers are built for shuffling data about and with kudos to ‘nrabinowitz’ for answering a question (and Prateek Tandon for asking) on Stack Overflow (and Jesus Ruiz with AmeliaBR for setting me on the right path), here is how we can take our flat data and convert it for use in our tree diagram.
We will be using the simple example that we started with at the start of the chapter and the first change we need to make is to replace our original data…
var treeData = [
  {
    "name": "Top Level",
    "parent": "null",
    "children": [
      {
        "name": "Level 2: A",
        "parent": "Top Level",
        "children": [
          {
            "name": "Son of A",
            "parent": "Level 2: A"
          },
          {
            "name": "Daughter of A",
            "parent": "Level 2: A"
          }
        ]
      },
      {
        "name": "Level 2: B",
        "parent": "Top Level"
      }
    ]
  }
];
… with our flat data array…
var data = [
    { "name" : "Level 2: A", "parent":"Top Level" },
    { "name" : "Top Level", "parent":"null" },
    { "name" : "Son of A", "parent":"Level 2: A" },
    { "name" : "Daughter of A", "parent":"Level 2: A" },
    { "name" : "Level 2: B", "parent":"Top Level" }
    ];
It’s worth noting here that we have also changed the name of the array (to data) since we are going to convert, then declare our newly massaged data with our original variable name treeData so that the remainder of our code thinks there have been no changes.
Then we create a name-based map for the nodes. In his answer on Stack Overflow, ‘nrabinowitz’ uses the.reduce method, which starts with an empty object and iterates over the data array, adding an entry for each node.
var dataMap = data.reduce(function(map, node) {
 map[node.name] = node;
 return map;
}, {});
Don’t feel upset if you don’t understand exactly how it works. I struggle to understand internal combustion engines, but I’m ok at driving a car :-). Think of this in the same way.
Then we iteratively add each child to its parents, or to the root array if no parent is found;
var treeData = [];
data.forEach(function(node) {
 // add to parent
 var parent = dataMap[node.parent];
 if (parent) {
  // create child array if it doesn't exist
  (parent.children || (parent.children = []))
   // add node to child array
   .push(node);
 } else {
  // parent is null or missing
  treeData.push(node);
 }
});
The code is essentially working through each node in the array and if it has a child it adds it to the childrensub-array and if necessary creates the array. Likewise, if the node has no parent, it simply add it as a root node.
That’s it!
The brevity of the code to do this should not detract from its elegance. It really is very clever. The end result doesn’t look any different from our original diagram…

… but it adds a significant capability for use of additional data.
The full code for this example can be found on github or in the code samples bundled with D3 Tips and Tricks  (simple-tree-from-flat.html). A working example can be found on bl.ocks.org.

Generating a tree diagram from external data

In all the examples we have looked at so far we have used data that we have declared from within the file itself. Being able to import data from an external file is an important feature that we need to know how to implement.
Starting from the simple tree diagram example that we began with at the start of the chapter, the first change that we need to make is to remove the section of code that declares our data. But don’t throw it away since we will use it to create a separate file called treeData.json. It’s contents will be;
[
  {
    "name": "Top Level",
    "parent": "null",
    "children": [
      {
        "name": "Level 2: A",
        "parent": "Top Level",
        "children": [
          {
            "name": "Son of A",
            "parent": "Level 2: A"
          },
          {
            "name": "Daughter of A",
            "parent": "Level 2: A"
          }
        ]
      },
      {
        "name": "Level 2: B",
        "parent": "Top Level"
      }
    ]
  }
]
(don’t include the treeData = part, or the semicolon at the end (you can delete those))
Then all we need to do is change the portion of the code that declared the root variable and updates the diagram;
root = treeData[0];
  
update(root);
… into a small section that uses the d3.json accessor to load the file treeData.json (Remember to correctly address the file. This one assumes that the treeData,json file is in the same directory as the html file we are opening).
d3.json("treeData.json", function(error, treeData) {
  root = treeData[0];
  update(root);
});
It then declares the variable root in the same way and calls the update function to draw the tree diagram. Viola!
The full code for this example can be found on github or in the code samples bundled with D3 Tips and Tricks  (simple-tree-from-external.html and treeData.json). A working example can be found on bl.ocks.org.

An interactive tree diagram

The examples presented thus far have all been static in the sense that they present information on a web page, but that’s where they stop. One of the strengths of web content is the ability to involve the reader to a greater extent. Therefore the following tree diagram example includes an interactive element where the user can click on any parent node and it will collapse on itself to make more room for others or to simplify a view. Additionally, any collapsed parent node can be clicked on and it will re-grow to its previous condition.
The example included here is a close derivative of Mike Bostock’s example. I won’t fully explain the operation of this file, but we will consider parts of it for interests sake.
The full code for this example can be found on github, in the appendices of  D3 Tips and Tricks or in the code samples bundled with  D3 Tips and Tricks (interactive-tree.html). A working online example can be found on bl.ocks.org.
For a brief visual description of the action. The diagram will initially display the complete tree...

Then when clicking on the ‘Level 2: A’ node, the tree partially collapses to...

We could also click on the root node (`Top Level’) to fully collapse the tree....
Then clicking on the nodes opens the diagram back up again.
One of the important changes to start with is to make each node responsive to the mouse pointer. This is done by including the following in the <style> section.
 .node {
  cursor: pointer;
 }
The code then adds sections to allow the diagram to follow the d3.js model of enter - update - exit for the nodes with a suitable transition in between.
Nodes are coloured (“steelblue”) if they have been collapsed and at the end of the script we have a function that makes use of the d._children reference we have been using in most of our examples.
function click(d) {
  if (d.children) {
 d._children = d.children;
 d.children = null;
  } else {
 d.children = d._children;
 d._children = null;
  }
  update(d);
}
This allows the action of clicking on the nodes to update the data associated with the node and as a consequence change it’s properties in the script based on if statements (Such as"fill", function(d) { return d._children ? "lightsteelblue" : "#fff"; } which will fill the node with “lightsteelblue” if d._children exists, otherwise make it white.)
The examples we have looked at in the previous sections in this chapter are all applicable to this interactive version, so this should provide you with the capability to generate some interesting visualizations.
Enjoy.

The description above (and heaps of other stuff) is in the D3 Tips and Tricks book that can be downloaded for free (or donate if you really want to :-)).