riccardoscalco
5/18/2015 - 2:23 PM

DataJoin

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>

D3.js Data-Join

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.


Selettori CSS

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")

Le selezioni hanno metodi

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")

Il data-join

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 update
  • n < 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()

D3 ed i web standards

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.

Eventuali esercizi

  • applicare transizioni ai cerchi
  • usare nuove figure geometriche (rect, line, path)
  • implementare il codice nella pagina web
  • visualizzare dati tipo: [{'x': x, 'y': y, 'r': r}, ..]

Tutorial introduttivo a D3 preparato in occasione del DrupalDay ( Riccardo Scalco | Milano, 9 Maggio 2014 )