Tuesday, October 6, 2015

Learn D3 by "Official" examples -- Treemap

Explanation of D3 clickable "pack" layout. click to see official example. "elements" in this article means HTML elements. In treemap layou, we are using rectangular as basic units while in pack layout, we are using circles as basic units.

<style>
body {
  font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
  margin: auto;
  position: relative;    /*CSS positioning: http://www.w3schools.com/css/css_positioning.asp*/
  width: 960px;
}
form {
  position: absolute;
  right: 10px;
  top: 10px;
}
.node {
  border: solid 1px white;
  font: 10px sans-serif;
  line-height: 12px;
  overflow: hidden;
  position: absolute;
  text-indent: 2px;
}
</style>
<form>
  <label><input type="radio" name="mode" value="size" checked> Size</label>
  <label><input type="radio" name="mode" value="count"> Count</label>
</form>
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.5.5/d3.min.js"></script>
<script>
var margin = {top: 40, right: 10, bottom: 10, left: 10},
    width = 960 - margin.left - margin.right,
    height = 500 - margin.top - margin.bottom;
var color = d3.scale.category20c();
var treemap = d3.layout.treemap()
    .size([width, height])
    .sticky(true)
    .value(function(d) { return d.size; });  /*size of rect*/
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");
d3.json("flare.json", function(error, root) {
  if (error) throw error;
  var node = div.datum(root).selectAll(".node").data(treemap.nodes) //same as div.selectAll(".node").data(treemap.nodes(root))
      .enter().append("div")
      .attr("class", "node")
      .call(position)
      .style("background", function(d) { return d.children ? color(d.name) : null; }) /* "backgournd:null" means transparent.*/
      .text(function(d) { return d.children ? null : d.name; });
  d3.selectAll("input").on("change", function change() {
    var value = this.value === "count" ? function() { return 1; } : function(d) { return d.size; };
    node.data(treemap.value(value).nodes)
        .transition()
        .duration(1500)
        .call(position);
  });
});
/*compute the boundary of rectangular*/
function position() {
  this.style("left", function(d) { return d.x + "px"; })  /*(d.x,d.y) is the top left point of the rect.*/
      .style("top", function(d) { return d.y + "px"; })
      .style("width", function(d) { return Math.max(0, d.dx - 1) + "px"; }) /*d.dx is width of rect.*/
      .style("height", function(d) { return Math.max(0, d.dy - 1) + "px"; }); /*d.dy is eight of rect.*/
}
</script>

Treemap nodes:
  • parent - the parent node, or null for the root.
  • children - the array of child nodes, or null for leaf nodes.
  • value - the node value, as returned by the value accessor.
  • depth - the depth of the node, starting at 0 for the root.
  • x - the minimum x-coordinate of the node position.
  • y - the minimum y-coordinate of the node position.
  • dx - the x-extent of the node position.
  • dy - the y-extent of the node position.
For each rectangular, top left point is (x,y) and the width is "dx" and the height is "dy". "value" is the size of rect.

Friday, October 2, 2015

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

Explanation of D3 clickable "pack" layout. click to see official example. "elements" in this article means HTML elements.
<head>
<style>
.node {
  cursor: pointer;
}
/*CSS selector ".node:hover" means select elements with "class=node" when mouse is over the element. */
.node:hover {
  stroke: #000;
  stroke-width: 1.5px;
}
/*CSS selector ".node--leaf" means select elements with "class=node--leaf". "node--leaf" is a class name. */
.node--leaf {
  fill: white;
}
.label {
  font: 11px "Helvetica Neue", Helvetica, Arial, sans-serif;
  text-anchor: middle;
  text-shadow: 0 1px 0 #fff, 1px 0 0 #fff, -1px 0 0 #fff, 0 -1px 0 #fff;
}
.label,
.node--root,
.node--leaf {
  pointer-events: none; /*no mouse event response on elements with these classes*/
}
</style>
</head>
<body>
<script>
var margin = 20,
    diameter = 960;
/*define the color. d3.scale transforms the input from .domain to .range using .interpolate. In this example, it means the input to the function "color" will be in [-1,5] and the output will be in ["hsl(152,80%,80%)", "hsl(228,30%,40%)"]. Basically, it is a mapping from [-1,5] to ["hsl(152,80%,80%)", "hsl(228,30%,40%)"]. How to compute which value in [-1,5] corresponds to which value in ["hsl(152,80%,80%)", "hsl(228,30%,40%)"]? We use interpolate "d3.interpolateHcl".*/
var color = d3.scale.linear()
    .domain([-1, 5])
    .range(["hsl(152,80%,80%)", "hsl(228,30%,40%)"])
    .interpolate(d3.interpolateHcl);
/*Here is how we use pack layout. We define a size of the entire pack and a value for each of its elements.*/
var pack = d3.layout.pack()
    .padding(2)
    .size([diameter - margin, diameter - margin])
    .value(function(d) { return d.size; })     /*d.size is defined in the given dataset in file "flare.json". Just a heads up, if you do not return anything, the default return value is 0.*/
var svg = d3.select("body").append("svg")
    .attr("width", diameter)
    .attr("height", diameter)
  .append("g")
    .attr("transform", "translate(" + diameter / 2 + "," + diameter / 2 + ")");
d3.json("flare.json", function(error, root) {
  if (error) throw error;
  var focus = root,
      nodes = pack.nodes(root), /*"nodes" is an array of objects which are extracted from Json file.*/
      view;
  var circle = svg.selectAll("circle")
      .data(nodes)   /*each element of circle is associate with an object stored in "nodes".*/
      .enter().append("circle")
      /*determine the classes for different HTML elements. "node node--leaf" means that the html element with two attributes "class=node" and "class=node--leaf" which further means the CSS styles defined for ".node" and ".node--leaf" will be applied to this element. However, in this example, "node" is not needed for "node--leaf" and "node--root". There will be no differences if we write like this:
      .attr("class", function(d) { return d.parent ? d.children ? "node" : "node--leaf" : "node--root"; })  */
      .attr("class", function(d) { return d.parent ? d.children ? "node" : "node node--leaf" : "node node--root"; })
      .style("fill", function(d) { return d.children ? color(d.depth) : null; }) /*d.depth is level of the node in hierarchy. automatically computed.*/
      .on("click", function(d) { if (focus !== d) zoom(d), d3.event.stopPropagation(); }); /*when we click a node and it is different from previous clicked node, we "zoom" to that node. d3.event.stopPropagation() stops the click event propagated to its parent node. what does this mean? Suppose you have two circles A and A_sub. A_sub is inside A. If you click A_sub, it will trigger the actions for A_sub first and then the actions for A. If you have d3.event.stopPropagation() put in A_sub's actions, then you click A_sub, only A_sub's actions will be triggered. See an example here: http://bl.ocks.org/jasondavies/3186840*/
  var text = svg.selectAll("text")  /*select all elements with tag name "text"*/
      .data(nodes)
    .enter().append("text")
      .attr("class", "label")       /*define a class "label" for tag "text"*/
      .style("fill-opacity", function(d) { return d.parent === root ? 1 : 0; }) /*Opacity can be used if you want to create transparency or fade effect. without this line, you won't see the fade effect.*/
      .style("display", function(d) { return d.parent === root ? null : "none"; }) /*display the elements when they are the children of root module.*/
      .text(function(d) { return d.name; });
  var node = svg.selectAll("circle,text"); /*all elements with "class="circle" and "class="text" are selected.*/
  d3.select("body")
      .style("background", color(-1))      /*the parameter of color is in [-1,5]*/
      .on("click", function() { zoom(root); }); /*if you click the background, it will zoom to root.*/
  zoomTo([root.x, root.y, root.r * 2 + margin]);     /*initial display of the entire data from json file.*/
  function zoom(d) {
    var focus0 = focus; focus = d;/*update the current focus. focus0 is not used.*/
    /*simply put, d3.transition performs the transition procedure from one point to another point. During these two points, there are many "frames" (think it as film frame). ".tween" is used to control how to display each frame. The tween function is called repeatedly, being passed the current normalized time t in [0, 1].*/
    var transition = d3.transition()
        .duration(d3.event.altKey ? 7500 : 750)     /*specify the length of the transition.*/
        .tween("zoom", function(d) {                
          var i = d3.interpolateZoom(view, [focus.x, focus.y, focus.r * 2 + margin]); /*compute each frame between "view" and "focus", where "view" is a global variable and set in function "zoomto".*/
          return function(t) { zoomTo(i(t)); }; /*"i" is a function, t is passed to the 2nd parameter of tween function. i(t) is the view of frame.*/
        });
    transition.selectAll("text")
      .filter(function(d) { return d.parent === focus || this.style.display === "inline"; })
        .style("fill-opacity", function(d) { return d.parent === focus ? 1 : 0; })        /*fade effect*/
        .each("start", function(d) { if (d.parent === focus) this.style.display = "inline"; }) /*beginning of frame.*/
        .each("end", function(d) { if (d.parent !== focus) this.style.display = "none"; });    /*end of frame.*/
  }
  function zoomTo(v) {
    /*room from current  "view" to "v" which is computed by d3.interpolateZoom.*/
    var k = diameter / v[2]; view = v; /*v[2] is the new radius of the clicked circle.*/
    node.attr("transform", function(d) { return "translate(" + (d.x - v[0]) * k + "," + (d.y - v[1]) * k + ")"; });
    circle.attr("r", function(d) { return d.r * k; }); /*update the circle size*/
    /*note, all nodes ("circle" and "text" on Line 66) will be updated.*/
  }
});
d3.select(self.frameElement).style("height", diameter + "px");
</script>
</body>