How to make a dendrogram using the D3 library (part 4)

This is the fourth of a series of articles which illustrates how to develop a dendrogram using the JavaScript D3 library. These dendrograms will be built according to a particular data structure defined in a JSON file.

See the previous articles:

In the previous article we have implemented a dendrogram taking the ultrametric distances into accout.

dendrogram02
Fig.1: dendrogramma

In this article we will see how it is possible to introduce a further distance between nodes in the data visualization. This new distance will be represented within the dendrogram visualization by the vertical position of the nodes.

dendrogram03
Fig.2: the dendrogram with a further distance vertically scaled

We have seen in the previous articles that the position of the dendrogram nodes is specified in the JSON structure by assigning (x,y) values to the corresponding x and y attributes for each node.

Thus, in order to obtain the dendrogram as shown in Fig.2 we have to edit the following JSON structure, and then save it as dendrogram03.json.

{
  "name": "root", "y" : 0, "x" : 100,
  "children": [
  {
    "name": "parent A", "y" : 30, "x" : 40,
    "children": [
      {"name": "child A1", "y" : 100, "x" : 0},
      {"name": "child A2", "y" : 100, "x" : 10},
      {"name": "child A3", "y" : 100, "x" : 40}
    ]
  },{
    "name": "parent B", "y" : 21, "x" : 130,
    "children": [
      {"name": "child B1", "y" : 100, "x" : 80},
      {"name": "child B2", "y" : 100, "x" : 150}
    ]
  }
  ]
}

As we can see, this time there is a x value and a y value defined within each node of the JSON structure.

Now let’s write a new Web page (or modify an existing one) in which we will implement our new dendrogram.

<!doctype html><html>
<meta charset="utf-8">
<style><
.node circle {   
   fill: #fff;   
   stroke: steelblue;   
   stroke-width: 1.5px; 
} 
.node {   
   font: 20px sans-serif; 
} 
link {   
   fill: none;   
   stroke: #ccc;   
   stroke-width: 1.5px; 
}
line {   
   stroke: black; 
}
</style>
<script type="text/javascript" src="http://d3js.org/d3.v3.min.js">
<script type="text/javascript">
var width = 600; var height = 500; 
var cluster = d3.layout.cluster()   
   .size([height, width-200]); 
var diagonal = d3.svg.diagonal()   
   .projection (function(d) { return [x(d.y), y(d.x)];}); 
var svg = d3.select("body").append("svg")  
   .attr("width",width)   
   .attr("height",height)   
   .append("g")   
   .attr("transform","translate(100,0)"); 
xs = []; 
ys = []; 
function getXYfromJSONTree(node){   
   xs.push(node.x);   
   ys.push(node.y);  
   if(typeof node.children != 'undefined'){     
      for ( j in node.children){       
         getXYfromJSONTree(node.children[j]);      
      }   
   } 
} 
var ymax = Number.MIN_VALUE; 
var ymin = Number.MAX_VALUE; 
var xmax = Number.MIN_VALUE; 
var xmin = Number.MAX_VALUE; 
d3.json("dendrogram03.json", function(error, json){   
   getXYfromJSONTree(json);   
   var nodes = cluster.nodes(json);   
   var links = cluster.links(nodes);   
   nodes.forEach( function(d,i){     
      if(typeof xs[i] != 'undefined'){       
         d.x = xs[i];     
      }     
      if(typeof ys[i] != 'undefined'){       
         d.y = ys[i];     
      }   
   });   
   nodes.forEach( function(d){     
      if(d.y > ymax)
         ymax = d.y;
      if(d.y < ymin)       
         ymin = d.y;     
      if(d.x > xmax)
         xmax = d.x;
      if(d.x < xmin)       
         xmin = d.x;   
   });   
   x = d3.scale.linear().domain([ymin, ymax]).range([0, width-250]);   
   xinv = d3.scale.linear().domain([ymax, ymin]).range([0, width-250]);    
   y = d3.scale.linear().domain([xmin, xmax]).range([100, height-20]);   
   var link = svg.selectAll(".link")     
      .data(links)     
      .enter().append("path")     
      .attr("class","link")     
      .attr("d", diagonal);   
   var node = svg.selectAll(".node")    
      .data(nodes)     
      .enter().append("g")     
      .attr("class","node")    
      .attr("transform", function(d) { 
          return "translate(" + x(d.y) + "," + y(d.x) + ")"; 
      });  
   node.append("circle")     
      .attr("r", 4.5);   
   node.append("text")     
      .attr("dx", function(d) { return d.children ? -8 : 8; })     
      .attr("dy", 3)     
      .style("text-anchor", function(d) { return d.children ? "end" : "start"; })    
      .text( function(d){ return d.name;});   
   // x-Axis   
   var g = d3.select("svg").append("g")     
      .attr("transform","translate(100,40)");   
   g.append("line")     
      .attr("x1",x(ymin))     
      .attr("y1",0)     
      .attr("x2",x(ymax))     
      .attr("y2",0);   
   g.selectAll(".xticks")    
      .data(x.ticks(5))     
      .enter().append("line")     
      .attr("class","ticks")     
      .attr("x1", function(d) { return xinv(d); })    
      .attr("y1", -5)     
      .attr("x2", function(d) {return xinv(d); })     
      .attr("y2", 5);   
   g.selectAll(".xlabel")     
      .data(x.ticks(5))     
      .enter().append("text")     
      .attr("class","label")    
      .text(String)    
      .attr("x", function(d) {return xinv(d); })     
      .attr("y", -5)    
      .attr("text-anchor","middle");  
    // y-Axis   
    var g = d3.select("svg").append("g")    
      .attr("transform","translate(100,0)");   g.append("line")     
      .attr("x1",x(ymax)+100)    
      .attr("y1",y(xmin))     
      .attr("x2",x(ymax)+100)     
      .attr("y2",y(xmax));   
    g.selectAll(".yticks")    
      .data(y.ticks(5))     
      .enter().append("line")   
      .attr("class","ticks")    
      .attr("x1", x(ymax)+95)    
      .attr("y1", function(d) { return y(d); })    
      .attr("x2", x(ymax)+105)     
      .attr("y2", function(d) {return y(d); });  
    g.selectAll(".ylabel")     
      .data(y.ticks(5))     
      .enter().append("text")     
      .attr("class","label")    
      .text(String)    
      .attr("x", x(ymax)+120)     
      .attr("y", function(d) {console.log(y.ticks(4));return y(d); })    
      .attr("text-anchor","middle"); });
</script>

Let’s analyze all the changes referring to the code of the previous article. Since we have to define a further scale (y-axis) managing the range of x values defined in the JSON file, we need to get the xmax e xmin values.

var xmax = Number.MIN_VALUE;
var xmin = Number.MAX_VALUE;

Within the iteration function looking for the maximum and minimum values, we need to add the y attribute along with the x.

  nodes.forEach( function(d){
    if(d.y > ymax)
      ymax = d.y;
    if(d.y < ymin)       ymin = d.y;     if(d.x > xmax)
      xmax = d.x;
    if(d.x < xmin)
      xmin = d.x;
  });

Let’s define a new distance on the y-axis, specifying a y scale and  the domain and the range related to it.

  y = d3.scale.linear().domain([xmin, xmax]).range([100, height-20]);

Now that even the d.y must be represented scalarly, then we have to make some changes to the code (in bold):

  var node = svg.selectAll(".node")
    .data(nodes)
    .enter().append("g")
    .attr("class","node")
    .attr("transform", function(d) { return "translate(" + x(d.y) + "," + y(d.x) + ")"; });

Finally, let’s add a further axis to our dendrogram, taking care to place it to the right edge of the figure. Moreover, let’s draw on it the ticks and their corresponding values.

 // y-Axis
  var g = d3.select("svg").append("g")
    .attr("transform","translate(100,0)");

  g.append("line")
    .attr("x1",x(ymax)+100)
    .attr("y1",y(xmin))
    .attr("x2",x(ymax)+100)
    .attr("y2",y(xmax));

  g.selectAll(".yticks")
    .data(y.ticks(5))
    .enter().append("line")
    .attr("class","ticks")
    .attr("x1", x(ymax)+95)
    .attr("y1", function(d) { return y(d); })
    .attr("x2", x(ymax)+105)
    .attr("y2", function(d) {return y(d); });

  g.selectAll(".ylabel")
    .data(y.ticks(5))
    .enter().append("text")
    .attr("class","label")
    .text(String)
    .attr("x", x(ymax)+120)
    .attr("y", function(d) {console.log(y.ticks(4));return y(d); })
    .attr("text-anchor","middle");

Here is the dendrogram with two distances.

Bye.See you next article.[:]

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.