Come realizzare un dendrogramma con la libreria D3 (parte 2)

Questo è il secondo di una serie di articoli che illustrano come sviluppare un dendrogramma, utilizzando la libreria JavaScript D3,  costruito in base ad una particolare struttura dati contenuta all’interno di un file JSON.

Leggi l’articolo Come realizzare un dendrogramma con la libreria D3 (parte 1)

D3 - come realizzare un dendrogramma parte 2

In questo articolo per prima cosa svilupperemo il codice JavaScript che ci permetterà di visualizzare la seguente struttura ad albero:

dendrogram_es01
Fig.1: struttura ad albero

Come abbiamo detto nell’articolo precedente, quando in un dendrogramma non si tiene conto delle distanze ultrametriche, abbiamo a che fare, in realtà, con una struttura ad albero.

Quello che segue è il codice della pagina Web che salveremo come dendrogram01.html.

<!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;  
} 
</style>  
<script type="text/javascript" src="http://d3js.org/d3.v3.min.js"></script> <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 [d.y, d.x];});  
var svg = d3.select("body").append("svg")        
    .attr("width",width)        
    .attr("height",height)        
    .append("g")        
    .attr("transform","translate(100,0)");  
d3.json("dendrogram01.json", function(error, root){        
     var nodes = cluster.nodes(root);        
     var links = cluster.links(nodes);        
     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(" + d.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;});  
});  
</script>

Se adesso aprite il file HTML all’interno di un qualsiasi browser otterrete il dendrogramma rappresentato in Fig.1. Attenzione: il file dendrogram01.json deve essere contenuto nella stessa directory della pagina HTML, altrimenti dovrete correggerre il path all’interno della funzione d3.json(). Adesso cominciamo ad analizzare il codice JavaScript che abbiamo utilizzato.

var width = 600; 
var height = 500;

Con queste due variabili definiamo le dimensioni dell’area di disegno all’interno della nostra pagina Web. Avremo quindi un’are 600×500 dedicata alla rappresentazione del nostro dendrogramma.

var cluster = d3.layout.cluster()   
     .size([height, width-200]);
var diagonal = d3.svg.diagonal()
     .projection (function(d) { return [d.y, d.x];});

Per prima cosa, avendo deciso di lavorare con una struttura ad albero, dovremo di conseguenza gestire tutta una serie di dati che risponderanno ad una struttura gerarchica. La libreria D3 ci fornisce tutta una serie di strumenti per gestire strutture dati di questo tipo (layouts) che ci facilitano enormemente il compito. Per i dendrogrammi, il layout che andremo ad utilizzare è proprio il cluster (d3.layout.cluster).

Il layout cluster è una variabile object con una struttura interna ad albero composta da tanti object node, ciascuno dei quali è caratterizzato dai seguenti attributi:

  • parent : il nodo genitore, è null per il nodo radice (root).
  • children : l’array di nodi figlio, è null per i nodi foglia.
  • depth : il livello del nodo nell’albero.
  • x : la coordinata x del nodo.
  • y : la coordinata y del nodo.
cluster_node
Fig.2: node object

Quindi nel codice definiamo il layout cluster, assegnandogli una porzione (500x400pixel) dell’area di disegno (500×600).

Un’altra classe di oggetti di cui abbiamo bisogno per rappresentare correttamente la nostra struttura ad albero, sono i diagonal, cioè quegli elementi grafici che ci permettono di disegnare i rami (link) che uniscono i vari nodi tra di loro.

I diagonal (d3.svg.diagonal) sono degli elementi grafici che generano una curva di Bezier cubica tra due punti, e questo genere di oggetti sono molto usati nella libreria D3 per tutti i diagrammi in cui c’è la necessita di rappresentare un collegamento tra due elementi in punti separati nell’area di disegno.

Le curve di Bezier sono delle curve costruite attraverso un certo insieme parametri ( in SVG sono espresse attraverso i path), molto utilizzate nella computer grafica. A seconda del numero di parametri passati, si otterranno via via curve sempre più complesse.

Le figure seguenti sono prese da WikiPedia e rappresentano rispettivamente la costruzione delle curve di Bezier quadratiche (1 punto definito oltre l’origine e la destinazione), cubiche ( 2 punti definiti oltre l’origine e la destinazione) e quartiche (3 punti definiti oltre l’orifine e la destinazione).

240px-Bezier_2_big
Fig. 3: curva quadratica di Bezier
240px-Bezier_3_big
Fig.4: curva cubiva di Bezier
240px-Bezier_4_big
Fig.5: curva quartica di Bezier

Una volta definite queste curve, tramite il metodo projection() gli assegniamo i valori x e y di ciascun nodo (d.x e d.y).

Una volta definite questi oggetti, è il momento di definire il codice necessario per costruire l’elemento root

var svg = d3.select("body").append("svg")     
     .attr("width",width)     
     .attr("height",height)     
     .append("g")     
     .attr("transform","translate(100,0)");

Adesso è giunto il momento di leggere ed interpretare i dati contenuti all’interno del file dendrogram01.json (questo file è stato descritto nell’articolo precedente). La funzione anonima iterativa partirà la lettura dall’elemento radice della struttura dati (root) che passeremo opportunamente alla funzione cluster.nodes(). Questa funzione appartenente al layout cluster, restituisce un array di object nodes che assegneremo alla variabile nodes. Subito dopo, utilizzeremo questo array nodes passandolo come argomento alla funzione cluster.links(). Quest’altra funzione, sempre appartenente al layout cluster, calcola i rami della struttura ad albero definendo un array di objects links, che assegneremo alla variabile links.

d3.json("dendrogram01.json", function(error, root){     
    var nodes = cluster.nodes(root);     
    var links = cluster.links(nodes);

Con queste poche righe di codice, abbiamo costruito tutta la struttura dati del dendrogramma (è davvero comodo).

Adesso possiamo cominciare a disegnare direttamente i vari elementi grafici. Partiamo proprio dai rami del dendrogramma. Le seguenti righe disegneranno un path SVG (cioè una curva di Bezier) per ogni oggetto link contenuto all’interno dell’array links (l’array generato in precedenza durante la lettura del file JSON).

var link = svg.selectAll(".link")       
    .data(links)       
    .enter().append("path")       
    .attr("class","link")       
    .attr("d", diagonal);

Adesso passiamo a disegnare i nodi. Ciascun nodo verrà rappresentato mediante un piccolo cerchio (SVG circle) di 4.5 pizel di raggio. Ogni cerchietto verrà disegnato nella posizione (x,y) definita all’interno dell’object node. In questo esempio (struttura ad albero) i valori x e y di ciascun nodo vengono calcolati automaticamente attraverso la funzione cluster.nodes().

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

Adesso è il momento di aggiungere il nome di ciascun nodo, anch’esso contenuto all’interno del file JSON.

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;});

E con questo abbiamo terminato il codice di questo primo esempio.

Nel prossimo articolo realizzeremo un dendrogramma vero e proprio, in cui le distanze dei nodi sono quantitativamente valutabili.

Come realizzare un dendrogramma con la libreria D3 (parte 3)

4 commenti su “Come realizzare un dendrogramma con la libreria D3 (parte 2)

  1. Jaime Tinker

    Hi there,

    Thanks for this article, exactly what I need and really appreciate being taking through it step by step. I am however struggling to see anything when I load the html page (after copying and saving the html code provided) and following part 1 to produce the json file.

    When I inspect the element of the page it doesn’t appear that any error is occuring. Any suggestions on what it might be/how to test? I’m using Google Chrome (if that makes a difference).

    I’m relatively new to coding but have managed to produce a couple of other d3 visualisations. Any help really appreciated.

    Thanks,
    Jaime

    • Fabio Nelli

      Hi Jaime, if you see only a blank page, maybe you are not using a web server or Aptana IDE (they are necessary if you are using a json file to be called by the d3.json() function. If you want to work without them (only browser) you need to specify an object variable containing the same data structure in the Json file, and then instead of using Json() function, you can specify directly the object properties where is necessary.
      I.e var data = [ … {value:12 …
      data.value …

      • Philip

        I am having a similar problem I hope you can help.

        I am using Express on Node.js to render the page. I copied the code and used the naming convention you used but I am getting a blank scree. I am using Jade as my template and converted the html to jade and I still get a blank screen. I am a newbie so any help would be great.

Lascia un commento