Fare un grafico a linee in D3.js v. 5

È giunto il momento di intensificare il nostro gioco e creare un grafico a linee da zero. E non solo qualsiasi grafico a linee: un grafico multi-serie che può ospitare qualsiasi numero di linee. Oltre a gestire più linee, lavoreremo con il tempo e le scale lineari, gli assi e le etichette – o meglio, li faremo lavorare per noi. C’è molto da fare, quindi ti suggerisco di licenziare il tuo server D3 e iniziamo a cracking.

Creeremo questa bellezza! Divertente!

Preparazione dei documenti

Come primo passo dobbiamo preparare i dati e la struttura dei file per la visualizzazione. Crea line_chart.html, stili.css e dati.csv nella cartella del progetto e popolarli con i frammenti che seguono. Questo ci farà iniziare.

Incolla questo nel line_chart.file html. Il codice definisce l’elemento svg per noi in modo che possiamo iniziare a disegnare subito. Ho anche creato una struttura di base in anticipo, quindi è più facile navigare nel documento mentre lavoriamo sulle sue sezioni particolari.

Lascia gli stili.documento css vuoto per ora. Incolla le righe seguenti nei dati.csv. Il grafico a linee sarà caratterizzato da più serie: corrispondenti alle colonne A, B e C.

date,A,B,C20-Jul-2019,10,20,1621-Jul-2019,11,22,1822-Jul-2019,13,19,2123-Jul-2019,11,17,2224-Jul-2019,15,16,2025-Jul-2019,16,19,1826-Jul-2019,19,21,1827-Jul-2019,22,25,1528-Jul-2019,18,24,1229-Jul-2019,14,20,1630-Jul-2019,14,18,1831-Jul-2019,16,18,2101-Aug-2019,15,20,2202-Aug-2019,14,21,19

Preparazione dei dati

Il primo passo – e un passo cruciale per l’intera visualizzazione – è quello di leggere correttamente i dati. Ho usato un esempio multi-serie per un motivo: mentre è piuttosto semplice tracciare un singolo percorso, la gestione di più linee (in particolare un numero indefinito di esse) richiede un po ‘ più di finezza D3. Incolla quanto segue nella sezione DATI, ricarica il documento html e rivedi la console accedi al tuo browser:

const timeConv = d3.timeParse("%d-%b-%Y");const dataset = d3.csv("data.csv");dataset.then(function(data) { const slices = data.columns.slice(1).map(function(id) { return { id: id, values: data.map(function(d){ return { date: timeConv(d.date), measurement: +d }; }) }; });console.log("Column headers", data.columns);console.log("Column headers without date", data.columns.slice(1));// returns the sliced datasetconsole.log("Slices",slices); // returns the first sliceconsole.log("First slice",slices);// returns the array in the first sliceconsole.log("A array",slices.values); // returns the date of the first row in the first sliceconsole.log("Date element",slices.values.date); // returns the array's lengthconsole.log("Array length",(slices.values).length);

Esaminiamo le trasformazioni chiamate sul nostro set di dati uno per uno:– dati.colonne restituisce le intestazioni csv-dati.colonna.slice(1) restituisce le intestazioni csv senza la colonna date (la slice inizia dalla colonna indicizzata a 1) – map() chiama una funzione su ogni elemento dell’array (costituito da A, B e C) – chiamiamo ciascuno di questi elementi un ‘slice’– map() assegna il nome della colonna come elemento id a ogni slice– quindi assegna un array values a ogni slice– nota come l’elemento values evoca una funzione. Qui mappiamo le informazioni dal set di dati originale: l’array sarà composto da 2 colonne, data e misurazione. La data viene derivata dalla prima colonna (e trasformata in un formato di data) e la misurazione viene presa dalla colonna corrispondente alla sezione id.At alla fine di queste trasformazioni otteniamo 3 array: A, B e C, con 2 colonne ciascuno: data e misurazione. Questo è un modo incredibilmente flessibile per suddividere un set di dati: indipendentemente dal numero di colonne che ha! E ‘ tutto fatto in quelle poche file. Ho stampato alcune informazioni sulla console per aiutarti a rivedere lo snippet.

Registro console

Rivedere il registro console per ulteriori informazioni

Preparazione scale

Dopo la lettura dei dati è necessario configurare il meccanismo di ridimensionamento. Questo viene fatto al fine di stampare il grafico in base al settore immobiliare del svg. Le scale trasformano l’input di dati (le nostre date e valori) in coordinate sul piano svg. Incolla le seguenti righe nella sezione SCALE.

const xScale = d3.scaleTime().range();const yScale = d3.scaleLinear().rangeRound();xScale.domain(d3.extent(data, function(d){ return timeConv(d.date)}));yScale.domain();

Tracceremo le date sull’asse x e i valori sull’asse Y. D3 fornisce un metodo scaleTime() per ridimensionare le date e un metodo scaleLinear () per i valori continui. Per prima cosa decidiamo l’intervallo della scala: a cosa dovrebbero essere tradotti i valori di input. In questo caso estenderemo i valori dei dati da 0 alla larghezza di svg e i valori numerici dall’altezza di svg a 0. Come secondo passo specifichiamo il dominio dei dati di input. Un dominio è costituito da tutti i valori compresi tra un minimo e un massimo specificati che un set di dati può assumere. Invece di cercare manualmente quei valori, lo passiamo attraverso le funzioni D3 incorporate: – d3.extent () restituisce un valore minimo e massimo di un array – in un ordine naturale)– questo funzionerà perfettamente sul nostro set di date-d3.max () restituisce un valore massimo della matrice. Nota come in questo esempio estraggiamo prima un valore massimo da ogni array per selezionare un massimo di tutti e tre. Ho anche aggiunto 4 al valore massimo per ragioni estetiche puramente soggettive: volevo avere un po ‘ di spazio sopra il grafico.

Le scale sono ora impostate. Se non hai abbastanza scale e vorresti vedere altri esempi, dai un’occhiata al mio precedente tutorial.

Una scala ben configurata ci consente di iniziare a tracciare i valori sull’svg. Ogni volta che evochiamo il set di dati, abbiamo solo bisogno di chiamare una scala appropriata su di esso.

Assi

Basta chiacchierare – disegniamo già qualcosa! Gli assi sono un buon punto di partenza: se tracciati correttamente, ci assicureranno che i dati sono stati letti come previsto e che si adattano bene come abbiamo immaginato.

Incollalo sugli ASSI nella sezione Preparazione:

const yaxis = d3.axisLeft().scale(yScale); const xaxis = d3.axisBottom().scale(xScale);

E questo agli ASSI sotto la sezione Disegno:

svg.append("g") .attr("class", "axis") .attr("transform", "translate(0," + height + ")") .call(xaxis);svg.append("g") .attr("class", "axis") .call(yaxis);

E così semplice abbiamo tracciato gli assi x e y!

Gli assi sono qui!

Certo, gli assi non sono i più eleganti del mondo (ci sono alcuni bei assi là fuori) ma sono qui! Ci sono alcune modifiche aggiuntive che possiamo applicare per renderli più amichevoli per il lettore.

Diamo un’occhiata prima all’asse x: c’è qualcosa di divertente nelle date. Leggendo da sinistra, otteniamo ‘Sab 20’, ‘Jul 21’, ‘Mon 22’, e ad un certo punto raggiungiamo solo ‘Agosto’. Sembra che i mesi e i giorni siano arrivati in un mix insubordinato di variazioni. Dobbiamo mettere fine a questo freestyle, e con questo intendo che dovremmo decidere quale formato di data vorremmo stampare sullo schermo. Il d3.il metodo axis () ci consente di regolare tutti i tipi di cose per le zecche: il loro numero, l’intervallo tra i punti, il formato di visualizzazione, ecc. Configuriamo alcuni di essi per entrambi gli assi.

Sostituire la definizione degli assi nella sezione Preparazione con il seguente frammento e aggiornare la visualizzazione:

const yaxis = d3.axisLeft() .ticks((slices.values).length) .scale(yScale);const xaxis = d3.axisBottom() .ticks(d3.timeDay.every(1)) .tickFormat(d3.timeFormat('%b %d')) .scale(xScale);

Il codice precedente specifica un numero impostato di tick per l’asse y (14, o quanti sono gli elementi dell’array / righe csv). Nel caso dell’asse x verrà visualizzato un segno di spunta con una granularità di un giorno, ogni giorno. Ciò è stato ottenuto impostando la proprietà tick su d3.timeDay.ogni(1). Il formato delle date visualizzate mostrerà il giorno e il mese abbreviato per ogni tick. Dopo questi cambiamenti finiamo con assi un po migliorati:

Ordnung, finalmente

Le date disobbedienti non sono più un problema!

Per renderlo ancora migliore (è persino possibile!!!) possiamo aggiungere un’etichetta all’asse y per mostrare quali sono i valori. Mentre le date sono auto-esplicative, i numeri da soli non portano alcuna informazione. Aggiungi un’etichetta (chiamala come vuoi – sono andato con frequenza) aggiungendo quanto segue al disegno dell’asse y:

//this you hadsvg.append("g") .attr("class", "axis") .call(yaxis)//this you append .append("text") .attr("transform", "rotate(-90)") .attr("dy", ".75em") .attr("y", 6) .style("text-anchor", "end") .text("Frequency");

(Non esiste uno stile impostato per l’etichetta, quindi non verrà visualizzato sul grafico, ma credimi e gli strumenti di sviluppo di Google Chrome, è lì)

Label

L’etichetta dell’asse y è invisibile

Infine, miglioriamo l’aspetto degli assi. Per migliorare intendo: impostare i colori, le larghezze e il rendering di ogni singolo elemento e decidere il font da utilizzare. Incolla quanto segue nel file css e sentiti libero di prendere le tue decisioni di stile:

/* AXES *//* ticks */.axis line{stroke: #706f6f;stroke-width: 0.5;shape-rendering: crispEdges;}/* axis contour */.axis path {stroke: #706f6f;stroke-width: 0.7;shape-rendering: crispEdges;}/* axis text */.axis text {fill: #2b2929;font-family: Georgia;font-size: 120%;}

Le zecche sono controllate dal .elemento linea dell’asse, mentre l’asse reale è impostato con il.elemento percorso. Gli assi sembrano nitidi (il modesto parere dell’autore) e pronti ad accogliere alcuni dati:

Assi Prettified

Senza ulteriori indugi, tracciamo il grafico!

Grafico a linee

Le linee sono essenzialmente d3.percorsi () che collegano un gruppo di coordinate (x, y) su un piano 2D. Per costruire una linea è necessario dirgli dove trovare le sue coordinate x e y e quindi aggiungerlo all’svg. Incolla i seguenti frammenti nei segnaposto creati in precedenza e rivediamo insieme il codice.

Questo dovrebbe arrivare a linee bit sotto la sezione di preparazione:

const line = d3.line() .x(function(d) { return xScale(d.date); }) .y(function(d) { return yScale(d.measurement); });

In questo frammento abbiamo chiamato un costruttore di linee, d3.line () che utilizza due accessor: x per i valori sul piano orizzontale e y per l’asse verticale. Qui indichiamo semplicemente i valori più granulari del nostro array, data e misurazione (questo non è il momento di preoccuparsi della struttura csv nidificata). Una volta fatto ciò, incollare quanto segue alle LINEE sotto la sezione Disegno:

const lines = svg.selectAll("lines") .data(slices) .enter() .append("g"); lines.append("path") .attr("d", function(d) { return line(d.values); });

Ciò richiede qualche spiegazione. La variabile lines seleziona un numero non identificato di linee dall’svg-e dice immediatamente a D3 che ci saranno 3 linee puntando alle sezioni impostate (linee A, B e C). Quindi aggiunge un elemento g a ciascuno di essi: un elemento di raggruppamento che renderà la nostra vita più facile a tempo debito. L’elemento g raccoglierà tutto ciò che ha a che fare con una particolare serie di grafici (ovvero una fetta nell’array): la linea (rappresentata sopra come un percorso), i suoi punti dati che saremo in grado di passare il mouse e le etichette della serie.

La prima cosa da aggiungere alle linee (che sono in realtà 3 contenitori g vuoti) sono le linee del grafico stesse. Chiamiamo il d3.line () costruttore sui dati per disegnare un percorso. Guarda come prima dobbiamo accedere ai valori sotto ogni sezione. Questo viene quindi passato al costruttore che estrae date e misurazioni come richiesto.

Dopo aver salvato le modifiche, la visualizzazione viene aggiornata a questo:

Invece di un grafico a linee abbiamo ottenuto un grafico di montagna

Ok, questo non è perfetto ma credimi, ci stiamo arrivando! Applichiamo alcune correzioni estetiche sul grafico e osserviamo come si modella. Aggiungi quanto segue agli stili.css:

/* LINE CHART */path {fill: none;stroke: #ed3700;}

Dobbiamo impostare il riempimento su nessuno per far riapparire le forme come linee. Aggiorna il grafico:

Le linee sono emerse

Cosa separa un grafico a linee da un gruppo di linee incollate su un grafico? La capacità di distinguere tra le serie. Al momento abbiamo solo il primo.

Per cominciare, dobbiamo fare una distinzione tra le righe nel codice. Aggiungiamo un id a ogni classe di riga-aggiungi quanto segue alla sezione LINEE nella parte di preparazione:

let id = 0;const ids = function () { return "line-"+id++;}

Questo piccolo pezzo di codice crea un contatore che possiamo sfruttare per assegnare automaticamente un id di linea ad ogni riga aggiunta. Facciamo riferimento al contatore nella proprietà di classe dei percorsi. Regolare il codice nella sezione RIGHE per aggiungere la proprietà della classe:

const lines = svg.selectAll("lines") .data(slices) .enter() .append("g"); lines.append("path") .attr("class", ids) .attr("d", function(d) { return line(d.values); });

E magicamente, ogni percorso ha la sua classe!

Path class

Alle linee viene data la propria identità

Ciò che resta da fare è fare riferimento a queste classi nel css e dare a ciascuna riga il proprio carattere univoco. Cambia la sezione del grafico a linee del css per dire:

/* LINE CHART */path.line-0 {fill: none;stroke: #ed3700;}path.line-1 {fill: none;stroke: #2b2929;stroke-dasharray: 2;}path.line-2 {fill: none;stroke: #9c9c9c;stroke-dasharray: 6;}

Nota come non sto solo modificando il colore, ma anche cambiando il tratto di ogni linea. Ricordate che circa il 10% di tutte le persone hanno un certo grado di daltonismo e in tutta onestà, differenziare tra i colori può essere difficile per nessuno di noi. I colori si fondono solo se ci sono troppe serie di dati e la loro tonalità mostrerà in modo diverso su ogni monitor.

Dopo che le modifiche sono state applicate le linee essere chiaramente separati sul grafico, come mostrato di seguito:

Le linee sono visivamente diverso da ogni altro

Ora la serie si differenziano, ma è ancora impossibile dire che uno è che a meno che non hai memorizzato i dati sottostanti e abbastanza malato immaginario visivo nel qual caso mi chiedo perché hai bisogno di un grafico, in primo luogo. Per aiutare la maggior parte di noi nel riconoscimento della serie propongo di aggiungere il nome della serie sul lato destro del grafico. Aggiungere quanto segue alla sezione di disegno delle LINEE:

lines.append("text") .attr("class","serie_label") .datum(function(d) { return { id: d.id, value: d.values}; }) .attr("transform", function(d) { return "translate(" + (xScale(d.value.date) + 10) + "," + (yScale(d.value.measurement) + 5 ) + ")";}) .attr("x", 5) .text(function(d) { return ("Serie ") + d.id; });

Lo snippet localizza la fine di ogni riga e vi aggiunge un elemento di testo. Il testo verrà stampato come Serie A, Serie B o Serie C, a seconda della riga. Aggiungere quanto segue al documento css per regolare le etichette delle serie:

.serie_label {fill: #2b2929;font-family: Georgia;font-size: 80%;}

Le etichette sono aggiunte! Bei tempi.

Ogni serie ha la sua etichetta

Siamo tutti d’accordo che questo è un bel grafico a linee! Ho incollato il codice completo qui sotto. Assicurati di controllare la seconda parte del tutorial che presenta due scenari di aggiunta di interattività al grafico.

Seguimi su Twitter per ulteriori progetti di data-sciency / visualizzazione dei dati!

Esempi di codice

line_chart.html:

stili.css:

/* AXES *//* ticks */.axis line{stroke: #706f6f;stroke-width: 0.5;shape-rendering: crispEdges;}/* axis contour */.axis path {stroke: #706f6f;stroke-width: 0.7;shape-rendering: crispEdges;}/* axis text */.axis text {fill: #2b2929;font-family: Georgia;font-size: 120%;}/* LINE CHART */path.line-0 { fill: none; stroke: #ed3700;}path.line-1 { fill: none; stroke: #2b2929; stroke-dasharray: 2;}path.line-2 { fill: none; stroke: #9c9c9c; stroke-dasharray: 6;}.serie_label { fill: #2b2929; font-family: Georgia; font-size: 80%;}

dati.csv: