Saturday, September 26, 2015

Learn D3 by "Official" Examples -- "Pack" layout

Explanation of D3 "pack" layout. click to see official example
<head>
<style>
circle {
  fill: rgb(31, 119, 180);
  fill-opacity: .25;
  stroke: rgb(31, 119, 180);
  stroke-width: 1px;
}
.leaf circle {
  fill: #ff7f0e;
  fill-opacity: 1;
}
text {
  font: 10px sans-serif;
}
</style>
<!--
Line 2-16 defines styles for tag selector "circle" and class selector ".leaf circle" and tag selector "text". Priority of CSS selectors: id selector > class selector > tag selector. Wait a second, what does Line 9 mean by ".leaf circle"? ".leaf" means all elements with "class="leaf"". ".leaf circle" means all elements with tag "<circle>" inside "class="leaf"" should follow the specified rules between Line 9 and Line 12. See the following example, "c1" and "c2" are elements belonging to ".leaf circle".
<p class="leaf">
    <circle id="c1"> Circle 1 is shown here.</circle>
    <p>
        <circle id="c2"> Circle 2 is shown here.</circle>
    </p>
</p>
<p class="leaf node">double classes</p> means this element has two classes associated with it. The way to specify the style:
<style>
.leaf.node {color:red}
</style>
-->
<!--the official CDN of D3 -->
<script src="http://d3js.org/d3.v3.min.js" charset="utf-8"></script>
</head>
<body>
<script>
var diameter = 960,
    /*d3.format is a function converting a number to a string. See: https://github.com/mbostock/d3/wiki/Formatting#d3_format.  Variable "format" is a function that converts integer as string with a comma for a thousands separator. If the number is not an integer it will be ignored. Try console.log(format(123456)) and console.log(format(123.456)) for details.*/
    format = d3.format(",d");
var pack = d3.layout.pack()                    //This is the way to use pack layout.*/
    .size([diameter - 4, diameter - 4])        //Specify size of the layout.*/
    .value(function(d) { return d.size; });    //".value" determines the size of each node in the pack layout. Where is "d"? "d" will be associated when the pack connects to dataset.  
                                               
var svg = d3.select("body").append("svg")      //append a svg which is like a whiteboard to draw.
    .attr("width", diameter)                   //specify width of svg     
    .attr("height", diameter)                  //specify height of svg
    .append("g")                               //define a group
    .attr("transform", "translate(2,2)");      //Translate will move the entire svg by 2 along x-axis and by 2 along y-axis. 
d3.json("flare.json", function(error, root) {  //read in a json file and store it to variable "root".
  if (error) throw error;
  var node = svg.datum(root)     //connect the retrieved data "root" to "svg". Similar to "d3.selection.data". 
      .selectAll(".node")        //select the "virtual" elements with <class="node">. These elements are not created yet but will be created later.
      .data(pack.nodes)          //pack.nodes comes from its parent node "svg.datum(root)". then pack.nodes are assigned to elements with <class="node">. From now on, you can believe each node in pack corresponds to some data (an object in the given json) from pack.nodes. 
      .enter().append("g")     
      /*for each node, we assign css attribute to it. Here the node's class depends on whether it has children.*/  
      .attr("class", function(d) { return d.children ? "node" : "leaf node"; })
      /*assign location for each node. d.x and d.y are automatically computed by pack layout.*/
      .attr("transform", function(d) { return "translate(" + d.x + "," + d.y + ")"; });
  node.append("title")           //assign a title to each node
      .text(function(d) { return d.name + (d.children ? "" : ": " + format(d.size)); });
  node.append("circle")          //assign a shape to each node
      .attr("r", function(d) { return d.r; });  //where is d.r from? pack layout automatically computes it. I guess it is computed based on node.value (Line 36).
  node.filter(function(d) { return !d.children; }) //choose all leaf nodes and assign text to them. 
      .append("text")
      .attr("dy", ".3em")
      .style("text-anchor", "middle")
      .text(function(d) { return d.name.substring(0, d.r / 3); });
});
d3.select(self.frameElement).style("height", diameter + "px");
</script>
</body>

The concept of "node" in a pack layout is important to understand how pack layout works.
Each node has the following attributes:

  • parent - the parent node, or null for the root. Automatically computed!
  • children - the array of child nodes, or null for leaf nodes.
  • value - the node value, as returned by the value accessor.Line 40. If not existing, it is the sum of its children's size.
  • depth - the depth of the node, starting at 0 for the root.Automatically computed!
  • x - the computed x-coordinate of the node position. Automatically computed!
  • y - the computed y-coordinate of the node position.Automatically computed!
  • r - the computed node radius.Automatically computed!

So only "children" and "value" are assigned from the given input dataset. I will use an example to explain the concept.
{
 "name": "flare",
 "children": [
      {"name": "sub1", "size": 3938},
      {
          "name": "sub2", 
          "children": [
                  {"name": "sub2_sub1", "size": 6714},
                  {"name": "sub2_sub2", "size": 743}
           ]
      }
 ]
}
We have 5 objects in the json file:
Object { name: "flare", children: Array[2], depth: 0, value: 11395, y: 478, x: 478, r: 478 } 
Object { name: "sub1", size: 3938, parent: Object, depth: 1, value: 3938, r: 174.4465055101499, x: 174.4465055101499, y: 478 } 
Object { name: "sub2", children: Array[2], parent: Object, depth: 1, value: 7457, r: 303.5534944898501, x: 652.4465055101499, y: 478 } 
Object { name: "sub2_sub1", size: 6714, parent: Object, depth: 2, value: 6714, r: 227.7797367518277, x: 728.2202632481724, y: 478 }
Object { name: "sub2_sub2", size: 743, parent: Object, depth: 2, value: 743, r: 75.77375773802244, x: 424.6667687583222, y: 478 }
"Node" of the pack corresponds to each object. So we have 5 nodes in this pack layout. "x", "y", "r" and "parent" are already there which means they are computed by pack layout. "name" and "children" are assigned by the dataset in the given json file. The size of each node is controlled by pack.value (Line 40). Note object "flare" does not have a "size" attribute, so its size is the sum of its children's sizes.

No comments:

Post a Comment