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)
In questo articolo per prima cosa svilupperemo il codice JavaScript che ci permetterà di visualizzare la seguente 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.
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).
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)