Setup Menus in Admin Panel

School.Dataninja.it

Visualizzazioni dati con d3js: selezionare gli elementi della pagina

Per agire sugli elementi della pagina bisogna prima selezionarli opportunamente con i selettori CSS e i metodi select() e selectAll().

L’idea di fondo, quindi, è quella di scrivere istruzioni per la manipolazione degli elementi che compongono una pagina web. Per manipolazione intendo creazione e rimozione di elementi, impostazione della loro posizione e della loro forma, delle loro caratteristiche grafiche come colori, bordi, trasparenze, del loro contenuto, che sia testuale o di altri tipo (icone, immagini bitmap, ecc.). Una pagina web è in effetti una collezione di elementi, i tag, annidati l’uno dentro l’altro, come una sorta di enorme gioco di scatole cinesi. Una struttura in cui la relazione tra elementi è quella tra padri e figli (o tra contenitori e contenuti) si dice ad albero.

Gioca qualche secondo con questa visualizzazione interattiva della Northwoods Software Corporation per capire la relazione tra gli elementi di una pagina web. Tecnicamente questa rappresentazione ad albero si chiama Document Object Model (DOM). A partire dall’elemento radice è sempre possibile discendere questo albero attraverso un cammino che porti all’elemento cercato. Quello che serve è un linguaggio che descriva cammini di questo tipo e permetta quindi di individuare una particolare collezione di elementi su cui agire.

Documenti strutturati

Un documento che abbia una tale struttura ad albero è naturalmente un documento strutturato. Non può essere una serie casuale di caratteri, ma la sua stesura deve seguire delle regole ben precise, rispettando una sintassi ben precisa. È il caso dei documenti XML (eXtensible Markup Language) che si distinguono per l’indicazione dei tag mediante parentesi angolari: <div>, <span>, <p>, ecc. Questi tag possono avere un contenuto: <p>Testo semplice</p> . Ed essere annidati tra loro: <div><p>Testo semplice</p></div> . Possono anche possedere degli attributi specifici, definiti come coppie chiave=”valore”: <div id="contenitore"><p class="paragrafo">Testo semplice</p></div> .

L’XML è un formato assolutamente generico. Non dice nulla sui nomi dei tag, sui nomi degli attributi, su restrizioni nei loro valori, su particolari vincoli di annidamento. Una volta che queste regole vengono definite, però, si produce un’applicazione XML specifica. È (quasi) il caso dell’HTML.

HTML: Hyper Text Markup Language

L’HTML non è strettamente un’applicazione XML. Come tutte le avventure umane, anche l’informatica e le tecnologie associate hanno avuto un loro sviluppo storico, pieno di contraddizioni. Se vuoi approfondire ti consiglio di dare un’occhiata alla voce HTML di Wikipedia e poi leggere qualche testo sulle battaglie campali che si sono combattute (e si stanno ancora combattendo) per la definizione di standard condivisi in questo ambito.

Al di là di come ci si sia arrivati, ora lo standard de facto dei documenti web è l’HTML5 ed è sotto gli occhi di tutti. Prova ad aprire il sorgente della pagina che stai leggendo in questo momento… tutti i browser, che siano su desktop o su mobile, che siano su Windows o su Linux, interpretano lo stesso file di testo e lo presentano a video sotto forma di immagini e testi. Riconosci subito i tag che compongono la pagina: sono quelli che si aprono con “<tag” e si possono chiudere con “</tag>”, “>” o “/>”. Ognuno dei blocchi di testo compresi tra un tag di apertura e il relativo tag di chiusura descrivono un elemento della pagina, che sia un paragrafo, una foto, un rettangolo, ecc.

Si tratta proprio degli elementi che vuoi manipolare attraverso delle istruzioni in javascript, in particolare usando la libreria d3js. Vuoi selezionarli e modificarli in base ai dati che hai a disposizione: per esempio generando un paragrafo per ogni stringa in un array, impostando la dimensione del font in base a numeri interi presenti in un altro array.

SVG: Simple Vector Graphics

Come l’HTML descrive la struttura e le caratteristiche di una pagina web, così l’SVG è un’applicazione XML per la descrizione di immagini vettoriali. L’aggettivo vettoriale identifica un tipo di immagine digitale completamente diverso da quello delle foto a cui probabilmente sei più abituato. In quest’ultimo caso, quello delle immagini raster o anche bitmap, l’immagine è una griglia quadrata molto fitta (quanto fitta dipende dalla risoluzione) in cui ogni cella può essere variamente colorata (e variamente trasparente). Nel caso vettoriale, invece, l’immagine è composta esplicitamente dagli elementi della geometria euclidea: punti, linee rette, linee curve, poligoni, ellissi.

Nell’SVG questi elementi sono descritti da tag opportuni, esattamente come nel caso dell’HTML. E allo stesso modo possono quindi essere manipolati da javascript e da d3js, partendo da logiche che coinvolgono i tuoi dati. Tornando all’esempio di prima, potresti voler generare un cerchio per ogni stringa di un array, scrivere il testo di ogni stringa lungo la circonferenza di ogni cerchio, impostare il raggio dei cerchi in base ai numeri interi presenti in un altro array.

Tra i rami dell’albero

Per fortuna nei browser moderni la distinzione tra HTML e SVG in una pagina web è molto labile. È possibile inserire un documento SVG all’interno di un qualsiasi elemento di una pagina HTML5, per esempio <div class="circle-container"><svg width="100" height="100"><g><circle cx="50" cy="50" r="40"></circle></g></svg></div> . E d3js permette di manipolare elementi HTML e SVG senza distinzione alcuna grazie al fatto che, trattandosi sempre di documenti (simil) XML, il linguaggio per selezionare singoli elementi o gruppi di elementi è lo stesso.

Si tratta di un linguaggio preso a prestito da un’altra tecnologia, quella dei Cascading Style Sheets (CSS). In quel caso l’obiettivo è applicare delle regole di stile agli elementi della pagina, cioè intervenire sulla resa grafica e su come il browser mostra gli elementi del DOM all’utente finale. Ma la prima esigenza è sempre quella di selezionare gli elementi a cui applicare le regole, definendo così un linguaggio per i selettori.

In realtà le specifiche base di Javascript mettono a disposizione tutta una serie di metodi per la selezione di elementi di una pagina, come getElementById(), getElementsByTagName(), getElementsByClassName() e, più recentemente, querySelector() e querySelectorAll(). Ma librerie come d3js o jQuery inseriscono un livello di astrazione più alto, rendendo queste operazioni molto più semplici (e funzionanti su tutti i browser).

Selettori CSS

In generale i selettori sono semplici stringhe di testo che prendono in considerazione tag e attributi degli elementi della pagina. Quello che si scrive è il nome del tag, il nome dell’attributo o il valore dell’attributo che si vanno cercando. La documentazione ufficiale del W3C è chiara e completa, riporto qui alcuni dei selettori più utili.

  • tag – seleziona tutti gli elementi del tipo indicato, es. <div></div> .
  • [attributo] – seleziona tutti gli elementi con l’attributo indicato, es. <input disabled></input> .
  • [attributo=valore] – seleziona tutti gli elementi con il valore indicato per l’attributo indicato, es. <a href="#"></a> .

Ci sono però attributi in qualche modo speciali che hanno una sintassi abbreviata per comodità. È il caso degli id e delle classi degli elementi.

  • * – seleziona tutti gli elementi senza vincoli
  • #id – seleziona il singolo elemento che possiede l’id indicato, es. <div id="main-container"></div> .
  • .class1.class2 – seleziona tutti gli elementi che hanno le classi indicate, es. <div class="container large"></div> .

Ci sono poi selettori che prendono in considerazione le cosiddette pseudo-classi, ovvero stati particolari dell’elemento che derivano per esempio dall’interazione utente. È il caso dei link, che possono essere attivi (:active) o visitati (:visited), oppure degli elementi su cui il mouse si trova in un dato momento (:hover).

Le pseudo-classi permettono anche di alterare la selezione appena fatta, eliminando per esempio alcuni elementi precedentemente selezionati (:not()), oppure considerando solo gli elementi che hanno un solo figlio di un certo tipo (:only-child), ecc.

E infine ci sono gli pseudo-selettori che agiscono su elementi che non sono realmente presenti nel DOM. È il caso dei selettori della prima lettera (::first-letter) o della prima linea (::first-line) di un paragrafo, o quelli che permettono di inserire contenuto prima (::before) o dopo (::after) l’elemento selezionato.

Singoli selettori possono essere composti tra loro, indicando particolari vincoli sulle relazioni tra gli elementi selezionati.

  • selettore1, selettore2 – gli elementi selezionati dal selettore1 più quelli del selettore2
  • selettore1 selettore2 – gli elementi selezionati dal selettore2 contenuti all’interno di quelli del selettore1
  • selettore1 > selettore2 – gli elementi selezionati dal selettore2 che siano figli diretti di quelli del selettore1

Come detto ho riportato solo i selettori più utili nella maggior parte dei casi, leggiti con attenzione le specifiche ufficiali per avere un quadro completo. Ci sono davvero molti selettori e le composizioni possono essere complicate a piacere, ma tieni conto che se strutturi bene la pagina e gli elementi della pagina, difficilmente ti troverai a usarne di diversi rispetto a quelli che hai visto qui. Anche perché generalmente ci sono moltissimi selettori possibili per selezionare un gruppo di elementi, tutta sta nel cercare quelli più semplici e diretti che raggiungono il risultato dovuto.

Se ti trovi a scrivere selettori complicati, fermati e pensa se invece non sia meglio strutturare diversamente la pagina con una migliore struttura annidata, aggiungendo classi maggiormente significative, ecc.

I metodi select() e selectAll()

Ora che hai chiaro come scrivere selettori efficaci è arrivato il momento di usarli. Al pari di molte altre librerie finalizzate alla manipolazione del DOM di una pagina web (la più famosa è probabilmente jQuery), d3js mette a disposizione alcuni metodi per selezionare elementi che accettano selettori validi come parametro di ingresso (stringhe) e restituiscono array di oggetti che rappresentano gli elementi selezionati. Il metodo select() torna una selezione con un solo elemento (il primo se il selettore ha selezionato più elementi), il metodo selectAll() invece torna tutti gli elementi selezionati in un array. Ecco un paio di esempi.

Selettore d3js jQuery Plain Javascript Descrizione
#id d3.select("#id") $("#id") document.getElementById("id") Seleziona un solo elemento con id="id"
.class d3.selectAll(".class") $(".class") document.getElementsByClassName("class") Seleziona una collezione di elementi con class="class"

Un avvertimento. Tutti questi metodi ritornano oggetti o array di oggetti che rappresentano l’elemento o gli elementi selezionati, ma sono diversi in ogni caso. D3 torna una selezione d3, jQuery torna una selezione jQuery, i metodi standard ritornano altri oggetti ancora. È importante che tieni a mente questo fatto, perché tipicamente con queste selezioni valorizzerai una variabile per poi riusarla: metodi e attributi che potrai usare successivamente dipenderà dalla libreria con cui hai effettuato la selezione. D3js mette a disposizione il metodo node() per passare da una selezione d3 all’equivalente oggetto javascript standard, che per esempio può essere selezionato a sua volta da jQuery (e viceversa).

Prova ora tu. Apri la finestra degli strumenti per sviluppatori del browser, posizionati nella console e comincia a selezionare gli elementi di questa pagina, tenendo d’occhio la scheda che li mostra tutti con i loro attributi. Ecco alcuni suggerimenti.

  • d3.select("body")
  • d3.select("#unit_content")
  • d3.selectAll(".single_unit_content p")
  • d3.selectAll("p .side_comment")

Osserva bene gli oggetti ritornati da questi comandi nella console, puoi vedere quanto siano ricche le informazioni contenute in una selezione.

Selezioni annidate

Una volta selezionata una collezione di elementi della pagina, puoi accedere ai metodi che d3js mette a disposizione per questo tipo di oggetti. Ce ne sono tanti e molti li vedrai nelle prossime unità, ma intanto soffermati sul fatto che una selezione permette di effettuare un’ulteriore selezione annidata. Puoi così agire su una selezione di elementi padri, poi effettuare un’ulteriore selezione di elementi figli (il secondo selettore agirà solo sugli elementi figli di quelli selezionati dal primo selettore) e agire su questi. In altre parole, i risultati di questi due comandi sono uguali: d3.selectAll("#unit_content p")  e d3.select("#unit_content").selectAll("p") .

Non è però sempre vero. L’equivalenza appena vista vale solo se il primo selettore ritorna un solo elemento selezionato (mediante il metodo select()), ma non nel caso della concatenazione di due metodi selectAll(). Considera infatti questa selezione: d3.selectAll("p").selectAll("span") . Prima selezioni tutti gli elementi p della pagina, poi tutti gli elementi span che sono figli dei p selezionati in precedenza. Ora prova a usare il selettore composto: d3.selectAll("p span") . In questo caso selezioni tutti gli elementi span che siano figli di elementi p. Ma perdi completamente l’informazione di quale elemento p è figlio un dato elemento span. La prima selezione è rappresentabile come un array di array (array di p e, per ogni p, array di span), la seconda invece solo come un array semplice (array di span).

Questa differenza sarà importante quando assocerai i tuoi dati agli elementi della pagina, perché gli elementi figli di una selezione annidata possono accedere ai dati associati ai rispettivi padri. Sarebbe impossibile selezionando semplicemente tutti gli span in una volta sola, usando il selettore composto con un’unica chiamata al metodo selectAll().

Concatenazione di metodi: il chaining

Con le selezioni annidate hai visto una possibilità interessante: una volta ottenuta una selezione puoi applicarvi direttamente i metodi delle selezioni a catena, in un’unica istruzione. In questo caso si tratta di un’ulteriore selezione, ma nella prossima unità vedrai la potenza di questo concetto con i metodi che agiscono su stili e attributi. Tecnicamente si tratta del chaining pattern (Gregory Schier ne fa una buona introduzione parlando di jQuery) e l’idea di fondo è che ogni metodo che modifica qualcosa, e che quindi non ha necessità di tornare un’informazione specifica, può ritornare la selezione stessa su cui ha agito, pronta per un nuovo metodo.

Ti accorgerai così che è possibile effettuare azioni complesse sul DOM della pagina con un’unica istruzione, semplicemente concatenando innumerevoli metodi l’uno all’altro. L’ordine di esecuzione è quello in cui scrivi la catena: agiscono prima i primi metodi, poi gli ultimi. È buona norma non scrivere queste catene su un’unica riga, ma spezzarla su più righe indentate. Ecco un esempio:

Vedrai il significato e il funzionamento di style() nella prossima unità, intanto prova a copiare e incollare questo codice sulla console del tuo browser per questa pagina…

Letture: 654