Sono passati già alcuni anni da quando ho scritto un libro completamente dedicato alla libreria D3.js e alla visualizzazionde dei dati. In questo tempo la libreria si è sviluppata ulteriormente passando dalla versione 3 alla verione 6. Alcune cose sono cambiate ed altre sono rimaste le stesse. Con questo articolo, colgo l’occasione per riprendere in mano questa libreria e cominciare a riscoprirla insieme.
La libreria D3
Il termine D3 sta per Data Driven Documents. Mike Bostock ha sviluppato questa libreria nel 2011.
Avendo a disposizione una serie di dati, la libreria D3 ci permette di poterli rappresentare sotto forma di grafici molto accattivanti senza doversi troppo preoccupare di coordinate, pixel e altri aspetti legati alla rappresentazione di ogni singolo oggetto che costituisce il grafico.
Il sito ufficiale è d3js.org, una valida collezione di esempi dove è possibile trovare molto di quello di cui si può avere bisogno. D3 combina tecnologie come HTML, CSS e SVG per la creazione di grafici da visualizzare mediante browser. D3 è una classica libreria JavaScript da importare in ogni pagina in cui ne vogliamo fare uso. La struttura HTML base da utilizzare è la seguente.
<!DOCTYPE html>
<html>
<head>
<script src="http://d3js.org/d3.v6.min.js"></script>
</head>
<body>
<script type="text/javascript">
// D3 Code here ...
</script>
</body>
</html>
D3 si basa su alcune tecnologie base:
- DOM – Document Object Model
- SVG – Scalable Vector Graphics
- CSS – Cascading Style Sheets
In questo articolo vedremo come la libreria D3 sfrutta al meglio queste tre tecnologie per poter realizzare dei grafici accattivanti, dinamici ed interattivi.
Lavorare sul DOM con le selezioni
Il Document Object Model (DOM) è un linguaggio a modelli per la rappresentazione di dati strutturati in HTML, XML e altri standard di Markup Language. Quando si scrive del codice di una pagina HTML, la prima riga è occupata dal seguente tag
<!DOCTYPE html>
A seguire vi è un preciso albero annidato di tag
<html> <head> <title> ... </title> <script> ... </script> </head> <body> <div> <p> ... </p> </div> </body> </html>
Quindi una pagina HTML è caratterizzata da un albero DOM composto da una serie di nodi delimitati e definiti in HTML sotto forma di tag (<tag> … </tag>).
Ognuno di questi nodi ha una serie di metodi e proprietà che permettono di modificarne la modalità di rappresentazione quando la pagina viene raffigurata nel browser (e anche dopo…). Questi metodi e proprietà possono essere richiamate all’interno del codice JavaScript ed utilizzati per convertire una pagina statica in una pagina dinamica (che varia dopo il suo caricamento iniziale (interattività) e che varia in caricamento a seconda del caso specifico (dinamicità)). Questo sarà possibile agendo programmaticamente sulle proprietà dei vari nodi del DOM.
Per fare un esempio, il linguaggio di programmazione base per lavorare con il DOM è il JavaScript, e quindi se vogliamo effettuare una selezione, si inserisce un codice simile a quello seguente:
document.getElementsByTagName('h1')[0].style.textAlign = "center";
Questo riga di codice, seleziona la radice dell’albero DOM “document” e poi va a scansionare tutti i nodi sottostanti. Durante la ricerca andrà a selezionare tutti i nodi definiti dal tag <h1>. Una volta trovati, li inserisce in un array, di cui andrà a selezionare il primo elemento (indice [0]).
Questa operazione viene appunto chiamata selezione: si va infatti a selezionare un particolare nodo (o più nodi) DOM tramite il tag che lo rappresenta e lo si manipolerà ( DOM Manipulation ) variandone proprietà e funzioni a seconda delle nostre esigenze.
Nell’esempio della riga riportata sopra, si andrà a modificare la proprietà di stile textAling (che regola l’allineamento del testo) di quel singolo elemento selezionato, inserendo il valore “center“. Quindi eseguendo questa riga di codice JavaScript, il browser modificherà il comportamento di default del primo tag <h1> spostando il testo definito al suo interno al centro della pagina web.
Per comprendere meglio vediamo direttamente un esempio. Aprite un semplice editor di testo e inserite il codice seguente, salvandolo come firstJS.html.
<!DOCTYPE html>
<html>
<head>
<script>
function myFunction() {
document.getElementsByTagName('h1')[0].style.textAlign = "center";
}
</script>
</head>
<body onload="myFunction();">
<h1>This is the Title</h1>
<h1>This is not the title</h1>
</body>
</html>
Come si può vedere dall’esempio sopra, caricando la pagina nel browser, troverete il testo del titolo al centro (cioè il testo incluso nel primo tag <h1> mentre il secondo <h1> verrà ignorato, e si comporterà di default, con un allineamento a sinistra.
Come potete vedere il codice JavaScript è stato incluso in una funzione myFunction, che poi verrà richiamato al momento del caricamento della pagina HTML tramite onload nel tag <BODY>.
DOM Manipulation con le selezioni D3
Sullo stesso principio lavora la libreria D3. Allo stesso modo è in grado di effettuare una selezione degli oggetti DOM per poi variarne anche qui le proprietà. Riscriviamo lo stesso esempio di sopra sviluppato in JavaScript, utilizzando questa volta la libreria D3.
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<script src="http://d3js.org/d3.v6.min.js"></script>
</head>
<body>
<h1>This is the Title</h1>
<h1>This is not the Title</h1>
<script>
d3.select('h1').attr('align','center');
</script>
</body>
</html>
Questa istruzione esegue le stesse operazioni descritte in precedenza, ma ci permette di lavorare completamete all’interno della libreria D3.
Se guardate il codice, non vi è un indice che indichi quale elemento viene selezionato. Nella pagina HTML ci sono due tag <h1>, e in D3 se si fa una selezione con d3.select(), selezionerà solo il primo elemento con quel tag.
Se invece avessimo voluto selezionare tutti i tag con <h1> si utilizza la funzione d3.selectAll(). Sostituite nel codice HTML la vecchia funzione con questa:
<script>
d3.selectAll('h1').attr('align','center');
</script>
e vedrete che adesso tutti gli elementi DOM con i tag <h1> verranno selezionati ed i loro attributi modificati. Questa volta entrambe i testi saranno allineati al centro della pagina.
Lavorare con D3.js sulle pagine Web
Quindi anche D3, lavora con le selezioni ed i selettori con le regole definite dal W3C Selectors API e quindi è supportato su tutti i browser moderni oggi in distribuzione. Gli elementi dell’albero DOM possono essere selezionati non solo con i tag, ma anche attraverso il nome delle classi e degli ID che li specificano.
Una volta selezionato uno o più elementi dell’albero DOM, la libreria D3 fornisce una serie di metodi che ci permettono di poterli manipolare e non solo modificando i loro valori di style o attribute, ma anche agendo sugli event listener e altro ancora. Per esempio si può agire sugli elementi stessi del DOM, spostandoli lungo l’albero, oppure crearli di nuovi oppure rimuoverli.
Certo, sono tutte operazioni che si possono fare lavorando direttamente con il codice JavaScript, ma la libreria D3 è un tool a più alto livello, e permette di lavorare sui documenti DOM in maniera più semplice ed intuitiva (una volta compreso bene il meccanismo, si intende….). E concentrarsi così maggiormente sull’aspetto visivo e sul tracciamento dei dati.
Conversione dei dati in elementi SVG per generare grafici
Ma la peculiarità di D3 non finisce qui. Infatti quello visto finora lo fa già la libreria jQuery e lo fa piuttosto egregiamente. Lo scopo della libreria D3.js è quella di generare delle rappresentazioni grafiche utilizzando dei dati in input, che possono essere non solo numerici, ma anche testuali. Le selezioni ed i selettori sono solo uno strumento utile per conseguire questi scopi.
La tecnologia SVG (Scalable Vector Graphics), è un formato XML (quindi funziona sempre con tag) che permette di visualizzare oggetti e grafici in forma vettoriale su tutti i browser. “In forma vettoriale” signica che le immagini realizzate con questa tecnica non sono legate a particoli valori di dimensioni, ma vengono generati in modo che siano ingrandibili e rimpicciolibili a piacere senza perdere in risoluzione grafica.
Per un semplice esempio, per capire come questa tecnologia funzioni, disegniamo un piccolo rettangolo giallo, con all’interno scritto box. Create un file box.html e scriveteci all’interno il codice SVG seguente:
<svg width="420" height="120" font-family="sans-serif" font-size="20" text-anchor="end">
<g transform="translate(0,0)">
<rect fill="yellow" width="100" height="100"></rect>
<text fill="black" x="75" y="50" dy=".35em">RECT</text>
</g>
</svg>
Lanciando il file da un qualsiasi browser, vedrete visualizzare la seguente figura in alto a sinistra:
Come si può vedere dal testo, SVG come HTML funziona per tag annidati, e a ciascun tag si hanno diverse funzioni, come quella di disegnare un rettangolo <RECT> o quella di aggiungere un testo <TEXT>.
La tecnologia SVG si è dimostrata davvero molto efficiente e ad alta prestazione per la rappresentazione di figure visive, occupa poco spazio in memoria. Soprattutto la sua struttura ad albero, con nodi e tag identici a quelli HTML, permette di poterci lavorare mediante selettori e selezioni, e quindi anche con D3.js.
Quindi il passo successivo che fa la libreria D3.js è quella di ricevere un insieme di dati in forma strutturata (array, json, o altre forme) e poi utilizzare quei valori per generare tanti elementi grafici in relazione a questi dati. SVG inoltre permette di modificare un grafico anche in forma dinamica (dopo che la pagina è stata caricata) e quindi con la libreria D3.js è possibile creare grafici dinamici ed interattivi.
Inserire un semplice grafico in una pagina web
Ma per comprendere questo vediamo un breve e semplice esempio. Generiamo un grafico cartesiano che visualizza l’andamento lineare di una serie di dati. Per renderci le cose molto semplici utilizzeremo questi dati sotto forma di un semplice array data. Nei casi più complessi questi valori saranno letti dinamicamente da una sorgente di dati, che potrà essere un file CSV, JSON o altro.
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<script src="http://d3js.org/d3.v3.min.js"></script>
<style>
path {
stroke: steelblue;
stroke-width: 3;
fill: none;
}
line {
stroke: black;
}
.xGrids {
stroke: lightgray;
}
.yGrids {
stroke: lightgray;
}
text {
font-family: Verdana;
font-size: 9pt;
}
</style>
</head>
<body>
<script>
var data = [87,45,67,78,101,66,34];
w = 400;
h = 300;
margin_x = 32;
margin_y = 20;
max = d3.max(data)*1.2
y = d3.scale.linear().domain([0, max]).range([0 + margin_y, h - margin_y]);
x = d3.scale.linear().domain([0, data.length-1]).range([0 + margin_x, w - margin_x]);
var svg = d3.select("body")
.append("svg:svg")
.attr("width", w)
.attr("height", h);
var g = svg.append("svg:g")
.attr("transform", "translate(0," + h + ")");
var line = d3.svg.line()
.x(function(d,i) { return x(i); })
.y(function(d) { return -y(d); });
// draw the x axis
g.append("svg:line")
.attr("x1", x(0))
.attr("y1", -y(0))
.attr("x2", x(w))
.attr("y2", -y(0));
// draw the y axis
g.append("svg:line")
.attr("x1", x(0))
.attr("y1", -y(0))
.attr("x2", x(0))
.attr("y2", -y(max));
//draw the xLabels
g.selectAll(".xLabel")
.data(x.ticks(3))
.enter().append("svg:text")
.attr("class", "xLabel")
.text(String)
.attr("x", function(d) { return x(d) })
.attr("y", 0)
.attr("text-anchor", "middle");
// draw the yLabels
g.selectAll(".yLabel")
.data(y.ticks(4))
.enter().append("svg:text")
.attr("class", "yLabel")
.text(String)
.attr("x", 25)
.attr("y", function(d) { return -y(d) })
.attr("text-anchor", "end");
//draw the x grid
g.selectAll(".xGrids")
.data(x.ticks(3))
.enter().append("svg:line")
.attr("class", "xGrids")
.attr("x1", function(d) { return x(d); })
.attr("y1", -y(0))
.attr("x2", function(d) { return x(d); })
.attr("y2", -y(max));
// draw the y grid
g.selectAll(".yGrids")
.data(y.ticks(4))
.enter().append("svg:line")
.attr("class", "yGrids")
.attr("y1", function(d) { return -1 * y(d); })
.attr("x1", x(w))
.attr("y2", function(d) { return -y(d); })
.attr("x2", x(0));
// draw the x axis
g.append("svg:line")
.attr("x1", x(0))
.attr("y1", -y(0))
.attr("x2", x(w))
.attr("y2", -y(0));
// draw the y axis
g.append("svg:line")
.attr("x1", x(0))
.attr("y1", -y(0))
.attr("x2", x(0))
.attr("y2", -y(max));
// draw the line of data points
g.append("svg:path").attr("d", line(data));
</script>
</body>
</html>
Senza entrare nei vari comandi della libreria D3 (che analizzeremo insieme in altri articoli), con questo semplicissimo esempio potete vedere come funziona e come è in grado di creare dei grafici a seconda dei dati utilizzati. Per prova provate a modificare i dati contenuti all’interno dell’array data. Potrete vedere come cambia il grafico lineare.
Lavorare con D3.js su Observable
Visitando il sito d3js.org, ti accorgerai immediatamente che è ricchissimo di esempi con moltissimi grafici ed applicazioni complesse. Bene un buon metodo per lavorare con D3, non è quello di partire da zero, ma di cercare tra i modelli disponibili quello che più si avvicina alle nostre esigenze e modificarlo gradualmente per avvicinarlo sempre di più al nostro progetto.
Facendo attenzione, vedrete che tutti gli esempi sono in realtà delle pagine di Observable, cioè un vero e proprio Notebook di lavoro su cui è possibile effettuare modifiche in tempo reale sul codice, modificando o aggiungendo un dato, una funzione o altro e vedere in tempo reale quello che succede. Al termine delle modifiche, salvare il nostro lavoro su una pagina a parte (forking) e continuare a lavorare su di essa gradualmente su di essa, fino a quando non abbiamo raggiunto ciò che desideravamo.
Observable è quindi una applicazione su web che ci fornisce un ambiente di lavoro specializzato per lavorare con JavaScript (e quindi anche con la libreria D3) in maniera semplice ed interattiva e con la possibilità di salvare e rivedere i propri lavori in modo davvero pratico. Una volta poi completati possono essere pubblicati come un vero e proprio documento grazie alla possibilità di aggiungere testi, immagini e pulsanti interattivi nel Notebook, oppure portarlo su web e pubblicarlo su server Web di qualsiasi piattaforma come un classico codice JavaScript.
Conclusione
Questo articolo è solamente introduttivo e il suo scopo è più che altro far vedere le basi su cui funziona la libreria D3.js. Lavorando su tecnologie come DOM, CSV e CSS e tramite le selezioni è possibile quindi creare e manipolare dati in corrispettivi oggetti grafici, creando grafici accattivanti e soprattutto dinamici ed interattivi. Per chi è interessato, potrà approfondire l’argomento passando a casi più pratici e complessi, leggendo altri articoli presenti nel sito.