DataJoin
<!DOCTYPE html>
<html>
<head>
<!-- character encoding -->
<meta charset="utf-8">
<!-- ratio between the device width and the viewport size -->
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<!-- latest d3 release -->
<script src="http://d3js.org/d3.v3.min.js" charset="utf-8"></script>
<!-- CSS rules -->
<style type="text/css">
</style>
</head>
<body>
<!-- graph container -->
<div id="graph"></div>
</body>
<!-- javascript code -->
<script type="text/javascript">
console.log("Hello World");
</script>
</html>
Nota: Questo tutorial lavora con la console Javascript di Chrome, ma non dovrebbero esserci grosse differenze con altri browser.
Creare il seguente file index.html
:
<!DOCTYPE html>
<html>
<head>
<!-- character encoding -->
<meta charset="utf-8">
<!-- ratio between the device width and the viewport size -->
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<!-- latest d3 release -->
<script src="http://d3js.org/d3.v3.min.js" charset="utf-8"></script>
<!-- CSS rules -->
<style type="text/css">
</style>
</head>
<body>
<!-- graph container -->
<div id="graph"></div>
</body>
<!-- javascript code -->
<script type="text/javascript">
console.log("Hello World");
</script>
</html>
Visualizzare a browser il file index.html
ed ispezionare il DOM mediante gli strumenti per sviluppatori.
D3 offre la possibilità di selezionare elementi specifici del DOM mediante i selettori CSS.
Per esempio, digitare nella console javascript il seguente comando per selezionare il nodo con id="graph"
presente nel DOM
d3.select("#graph")
Aggiungere ora due elementi <span>
dentro il nodo con id="graph"
<!-- graph container -->
<div id="graph">
<span>First</span>
<span>Second</span>
</div>
e selezionare a console i due elementi
d3.select("span")
Notare che d3.select
ritorna solo il primo dei due, per selezionarli entrambi digitare
d3.selectAll("span")
Per ora abbiamo selezionato elementi del DOM mediante id selectors e type selectors, ma possiamo selezionare anche in base alla classe (class selectors). Aggiungiamo dello style al documento:
<!-- CSS rules -->
<style type="text/css">
.first {
color: #0072ba;
}
</style>
ed associamo la classe .first
al primo elemento span
<!-- graph container -->
<div id="graph">
<span class="first">First</span>
<span>Second</span>
</div>
Come prima, usiamo la console per selezionare il nodo
d3.select(".first")
D3 offre numerosi metodi per gli elementi selezionati, vediamo alcuni esempi direttamente nella console
var el = d3.select(".first")
el.text()
el.text("D3")
el.style("font-size","40px")
el.attr("id","title")
Possiamo creare anche nuovi nodi, per esempio potremmo aggiungere un link
var x = d3.select("#graph")
x.append("a").text("link").attr("href","https://drupal.org/").style("color","red")
E' anche possibile transire con fluidità tra due valori, iniziale e finale, associati ad attributi o stili
d3.select(".first").transition().duration(2000).style("padding-left","500px")
Fin qui D3 non è molto diverso da JQuery, ciò che lo rende uno strumento diverso è il data-join. D3 offre infatti un meccanismo per legare un dato (cioè un oggetto javascript) ad un nodo del DOM. Fatto questo, diventa facile modificare il DOM in funzione dei dati ad esso associati, non per nulla "D3" sta per Data Drive Documents.
Mettiamo mano alla console, cominciamo con selezionare qualcosa che non esiste :P
var p = d3.selectAll("p")
p[0]
Ovviamente ritorna un array di 0 elementi.
Nota: più esattamente ritorna un array di un elemento, il quale è un array di zero elementi.
Detto questo, definiamo ora la variabile che contiene i dati, cominciamo con un array di interi
var data = [3,2,1]
Applichiamo il data-join associando i dati ad una selezione, così
var p = d3.selectAll("p").data(data)
Abbiamo associato i dati una selezione di zero elementi, ma così definita la variabile p
contiene ora tre elementi che scopriamo essere non definiti
p
p[0]
In javascript, avere array con elementi non definiti non è una grossa novità, provate questo per esempio
var v = []
v[10] = "hey"
v
v.length
v[0]
v[100]
E a volte javascript sa essere ben più strambo.
Dove sono finiti i dati?
Facciamo un passo indietro e ragioniamo sui possibili scenari che possono emergere nell'unione di una lista di dati ad una lista di elementi del DOM. Mettiamo che le due liste hanno lunghezze diverse, sia n
la lunghezza della lista di dati e m
la lunghezza della lista di nodi, abbiamo così tre possibilità:
n = m
: dati e nodi sono in numero uguale, abbiamo una unica selezione che è definita updaten < m
: i dati sono meno dei nodi, abbiamo due selezioni differenti. La selezione update contiene i nodi a cui sono associati dei dati, la selezione exit invece contiene i nodi non associati a dei dati.n > m
: i dati sono più dei nodi, ancora abbiamo due selezioni differenti. La selezione update contiene i nodi a cui sono associati dei dati, la selezione enter contiene invece nuovi nodi, per ora indefiniti, associati ai dati in eccessi.Nel nostro caso n = 3
e m = 0
, dunque abbiamo una enter selection
p
p[0]
p.enter()
p.enter()[0]
p.exit()
p.exit()[0]
Appendiamo ai nodi presenti nella enter selection dei paragrafi p
il cui testo è il dato associato:
p.enter().append("p").text(function(d){ return d; })
ed osserviamo nuovamente le selezioni
p
p[0]
p.enter()
p.enter()[0]
p.exit()
p.exit()[0]
Notare che ora la variabile p
contiene i tre paragrafi, ciascuno dei quali è associato ad un dato.
Facciamo una nuova selezione e un nuovo data-join e come prima osserviamo le selezioni
var q = d3.selectAll("p").data(["a","b","c"])
q
q.enter()
q.exit()
Le selezioni enter ed exit sono vuote, la update invece contiene i tre paragrafi e ciscuno di essi è associato al nuovo dato. Aggiorniamo i nodi cin base ai nuovi dati con il seguente comando
q.text(function(d){return d;})
Possiamo usare i dati anche per modificare lo stile, per esempio la dimensione del font
d3.selectAll("p").data([10,40,70]).style("font-size", function(d) { return d + "px"; })
Sperimentiamo ancora, selezioniamo nuovamente i paragrafi ed associamo ad essi una lista di soli due dati, che in questo esempio sono due oggetti
z = d3.selectAll("p").data([{"pl":10, "col":"red"},{"pl":20, "col":"blue"}])
Osserviamo le selezioni
z
z[0]
z.enter()
z.enter()[0]
z.exit()
z.exit()[0]
La selezione update contiene due nodi, la enter nessun nodo e la exit un nodo. Usualmente la update si aggiorna e la exit si butta
z.style("padding-left",function(d){return d.pl + "px";}).style("color",function(d){return d.col;})
z.exit().remove()
Avrete notato che il vocabolario di D3 proviene direttamente dagli Standard Web HTML, CSS ed SVG. Questo significa che è possibile trasmettere la propria conoscenza degli Standards nell'uso di D3 e che, vicersa, l'uso disinvolto di D3 necessita di conoscenze solide degli Standards.
Facciamo un esempio, prima abbiamo usato i comandi
var p = d3.selectAll("p").data([1,2,3])
p.enter().append("p").text(function(d){ return d; })
ora, per ottenere elementi grafici, utilizziamo il linguaggio SVG e gli stessi comandi D3
var graph = d3.select("#graph").append("svg")
var p = graph.selectAll("circle").data([1,2,3])
p.enter().append("circle").attr("r",function(d){ return d*10; }).attr("cx",function(d){ return d*100;}).attr("cy","500").style("fill","green")
Notate che non c'è nulla di speciale nei nomi degli attrinuti r
, cx
e cy
, questi sono infatti gli attributi dell'elemento circle
come definito dal W3C.
[{'x': x, 'y': y, 'r': r}, ..]
Tutorial introduttivo a D3 preparato in occasione del DrupalDay ( Riccardo Scalco | Milano, 9 Maggio 2014 )