Gör ett linjediagram i D3.js v. 5

det är dags att öka vårt spel och skapa ett linjediagram från början. Och inte bara något linjediagram: en graf i flera serier som rymmer valfritt antal linjer. Förutom att hantera flera linjer kommer vi att arbeta med tid och linjära skalor, axlar och etiketter – eller snarare, få dem att fungera för oss. Det finns mycket att göra, så jag föreslår att du avfyrar din D3-server och låt oss spricka.

vi kommer att skapa denna skönhet! Kul!

dokumentförberedelse

som det första steget måste vi förbereda data och filstrukturen för visualiseringen. Skapa line_chart.html, stilar.css och data.CSV i din projektmapp och fylla dem med de utdrag som följer. Detta kommer att få oss igång.

klistra in detta i line_chart.html-fil. Koden definierar SVG-elementet för oss så att vi kan börja rita direkt. Jag har också skapat en grundläggande struktur på förhand så det är lättare att navigera i dokumentet när vi arbetar med dess specifika avsnitt.

lämna stilarna.css-dokument tomt för nu. Klistra in följande rader i data.csv. Linjediagrammet kommer att innehålla flera serier: motsvarande kolumnerna A, B och 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

Databeredning

det första steget – och ett avgörande steg för hela visualiseringen – är att korrekt läsa data. Jag har använt ett exempel på flera serier av en anledning: medan det är ganska enkelt att plotta en enda väg, kräver hantering av flera linjer (särskilt ett odefinierat antal av dem) lite mer av D3-finess. Klistra in följande i DATASEKTIONEN, ladda om html-dokumentet och granska konsolloggen i din webbläsare:

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

Låt oss granska de omvandlingar som kallas på vår datamängd en efter en:– data.kolumner returnerar CSV-rubrikerna-data.kolumn.slice (1) returnerar CSV – rubrikerna utan datumkolumnen (skivan börjar vid kolumn indexerad vid 1) – karta () anropar en funktion på varje element i matrisen (bestående av A, B och C)– låt oss kalla var och en av dessa element en ’skiva’– karta () tilldelar kolumnnamnet som ett id– element till varje skiva-sedan tilldelar det en värdematris till varje skiva-notera hur värdeelementet framkallar en funktion. Här kartlägger vi information från den ursprungliga datamängden: matrisen kommer att bestå av 2 kolumner, datum och mätning. Datumet härrör från den första kolumnen (och omvandlas till ett datumformat), och mätningen tas från kolumnen som motsvarar skivans id.At i slutet av dessa omvandlingar får vi 3 matriser: A, B och C, med 2 kolumner vardera: datum och mätning. Detta är ett otroligt flexibelt sätt att skära upp en datamängd: oavsett hur många kolumner den har! Allt är gjort i de få raderna. Jag har skrivit ut lite information till konsolen för att hjälpa dig att granska utdraget.

Konsolloggen

granska konsolloggen för mer information

Skalberedning

när data har lästs in måste vi konfigurera skalningsmekanismen. Detta görs för att skriva ut diagrammet i enlighet med svg: s fastigheter. Vågar omvandlar datainmatningen (våra datum och värden) till koordinater på svg-Planet. Klistra in följande rader i avsnittet skalor.

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

vi kommer att plotta datumen på x-axeln och värdena på y-axeln. D3 ger en scaleTime () metod för skalning datum, och en scaleLinear() metod för kontinuerliga värden. Vi bestämmer först skalans intervall: vad ingångsvärdena ska översättas till. I det här fallet sträcker vi datavärdena från 0 till svg: s bredd och de numeriska värdena från svg: s höjd till 0. Som det andra steget anger vi inmatningsdatadomänen. En domän består av alla värden mellan ett angivet minimum och maximum som en dataset kan ta. Istället för att manuellt leta upp dessa värden skickar vi det genom inbyggda D3– funktioner: – d3.extension () returnerar ett minimi – och Maximivärde för en array (i en naturlig ordning)– Detta fungerar perfekt på vår datumuppsättning-d3.max () Returnerar ett maximalt värde för matrisen. Observera hur vi i det här exemplet först extraherar ett maximalt värde från varje array för att sedan välja maximalt alla tre. Jag lade också till 4 till det maximala värdet av rent subjektiva estetiska skäl: jag ville ha lite utrymme ovanför grafen.

skalorna är nu inställda. Om du inte har tillräckligt med skalor och vill se fler exempel, ta en titt på min tidigare handledning.

en väl konfigurerad skala gör det möjligt för oss att börja plotta värden på svg. Varje gång vi framkallar datamängden behöver vi bara ringa en lämplig skala på den.

axlar

tillräckligt chatta-låt oss rita något redan! Axlar är en bra utgångspunkt: om de ritas korrekt kommer de att försäkra oss om att uppgifterna har lästs som förväntat och att de skalar så bra som vi har föreställt oss.

klistra in detta på axlar under Förberedelsesektionen:

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

och detta till axlar under Ritningssektionen:

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

och så enkelt som att vi har ritat x – och y-axlarna!

axlarna är här!

visserligen är axlarna inte de mest eleganta i världen (Det finns några vackra axlar där ute) men de är här! Det finns några ytterligare tweaks vi kan använda för att göra dem mer vänliga för läsaren.

Låt oss titta på X-axeln först: det är något roligt som händer med datumen. Läser från vänster, vi får ’Lör 20’, ’Jul 21’, ’mån 22’, och någon gång når vi bara ’augusti’. Verkar som månader och dagar har kommit i en insubordinate blandning av variationer. Vi måste sätta och avsluta denna freestyle, och med detta menar jag att vi ska bestämma vilket datumformat vi vill skriva ut på skärmen. D3.axis () – metoden tillåter oss att justera alla slags saker för fästingar – deras antal, intervall mellan punkterna, visningsformat etc. Låt oss konfigurera några av dem för båda axlarna.

ersätt axeldefinitionen i Förberedelsesektionen med följande utdrag och uppdatera visualiseringen:

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

ovanstående kod anger ett visst antal fästingar för y-axeln (14, eller så många som det finns array-element / CSV-rader). När det gäller X-axeln visas ett fält med en granularitet på en dag, varje dag. Det har uppnåtts genom att ställa in tick-egenskapen till d3.dags.varje (1). Formatet för visade datum visar dagen och den förkortade månaden för varje bock. Efter dessa förändringar slutar vi med något förbättrade axlar:

Ordnung, äntligen

olydiga datum är inte längre ett problem!

för att göra det ännu bättre (är det till och med möjligt!!!) vi kan lägga till en etikett på y-axeln för att visa vad värdena står för. Medan datumen är självförklarande, siffrorna på egen hand har ingen information. Lägg till en etikett (kalla det vad du vill – jag gick med frekvens) genom att lägga till följande till Y-axelritningen:

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

(det finns ingen stiluppsättning för etiketten så det kommer inte att visas på grafen – men tro mig och Google Chrome developer tools, det är där)

etikett

Y-axelns etikett är osynlig

slutligen, låt oss förbättra axlarnas utseende. Med förbättra menar jag: Ställ in färger, bredder och återgivning av varje enskilt element och bestäm vilket teckensnitt som ska användas. Klistra in följande i css-filen och ta gärna dina egna stilbeslut:

/* 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%;}

fästingarna styrs av .linjeelement av axeln, medan den faktiska axeln är inställd med .path element. Axlarna ser skarpa ut (författarens ödmjuka åsikt) och redo att välkomna några data:

Prettified axlar

utan vidare, Låt oss rita diagrammet!

linjediagram

linjer är i huvudsak d3.banor () som ansluter en massa (x, y) koordinater på ett 2D-plan. För att konstruera en linje måste du berätta var du ska hitta dess X-och y-koordinater och sedan lägga till det i svg. Klistra in följande utdrag till de tidigare skapade platshållarna och låt oss granska koden tillsammans.

detta borde komma till linjer bit under Förberedelsesektionen:

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

i detta utdrag kallade vi en linjekonstruktör, d3.linje () som använder två accessorer: x för värden på horisontalplanet och y för den vertikala axeln. Här pekar vi helt enkelt på de mest granulära värdena för vår array, datum och mätning (det här är inte dags att oroa sig för den kapslade csv-strukturen). När det är klart klistrar du in följande på rader under Ritningssektionen:

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

detta kräver en viss förklaring. De variabla linjerna väljer ett oidentifierat antal linjer från svg-och berättar omedelbart D3 att det kommer att finnas 3 linjer genom att peka på skivorna (linjerna A, B och C). Det lägger sedan till ett g-element till var och en av dem: ett grupperingselement som kommer att göra vårt liv enklare i sinom tid. G-elementet samlar allt som har att göra med en viss diagramserie (aka en skiva i matrisen): linjen (representerad ovan som en sökväg), dess datapunkter som vi kommer att kunna sväva över och serieretiketterna.

det första att lägga till linjer (som i själva verket är 3 tomma g-behållare) är själva diagramlinjerna. Vi kallar d3.linje () konstruktör på data för att rita en bana. Se hur först måste vi komma åt värdena under varje skiva. Detta skickas sedan till konstruktören som drar datum och mätningar efter behov.

när ändringarna har sparats uppdateras visualiseringen till detta:

istället för ett linjediagram fick vi ett bergsdiagram

okej, det här är inte perfekt men tro mig, Vi kommer dit! Låt oss tillämpa några estetiska korrigeringar på diagrammet och observera hur det formar sig. Lägg till följande i stilar.css:

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

vi måste ställa in fyllningen till ingen för att formerna ska visas igen som linjer. Uppdatera diagrammet:

linjerna har dykt upp

Vad skiljer ett linjediagram från en massa linjer som sitter ihop på en graf? Förmågan att skilja mellan serien. Just nu har vi bara den förra.

till att börja med måste vi skilja mellan raderna i koden. Låt oss lägga till ett id i varje radklass-Lägg till följande i avsnittet rader i Förberedelsedelen:

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

den här lilla koden skapar en räknare som vi kan utnyttja för att automatiskt tilldela ett rad-id till varje tillagd rad. Låt oss referera till räknaren i klassens egendom för banorna. Justera koden i avsnittet rader för att lägga till klassegenskapen:

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

och magiskt får varje väg sin egen klass!

 Path class

raderna får sin egen identitet

vad som återstår för oss att göra är att referera till dessa klasser i css och ge varje rad sin egen unika karaktär. Ändra avsnittet linjediagram i css för att säga:

/* 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;}

observera hur jag inte bara ändrar färgen utan också ändrar stroke på varje rad. Kom ihåg att cirka 10% av alla människor har en viss grad av färgblindhet och i all rättvisa kan det vara svårt för någon av oss att skilja mellan färger. Färger kommer bara att blandas om det finns för många dataserier och deras nyans kommer att visas annorlunda på varje bildskärm.

efter att ändringarna har tillämpats separeras linjerna tydligt i diagrammet – som visas nedan:

linjerna skiljer sig visuellt från varandra

nu är serien differentierad men det är fortfarande omöjligt att berätta vilken som är vilken om du inte har memorerat de underliggande uppgifterna och har en ganska sjuk visuell fantasi i vilket fall Jag undrar varför du behövde en graf i första hand. För att hjälpa majoriteten av oss i serieigenkänningen föreslår jag att vi lägger till seriens namn till höger om diagrammet. Lägg till följande i ritningssektionen av linjer:

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; });

utdraget lokaliserar slutet på varje rad och lägger till ett textelement. Texten kommer att skrivas ut som Serie A, Serie B eller Serie C, beroende på raden. Lägg till följande i css-dokumentet för att justera serieetiketterna:

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

etiketterna är bifogade! Roligt.

varje serie har sin egen etikett

vi kan alla vara överens om att detta är ett snyggt linjediagram! Jag har klistrat in hela koden nedan. Se till att kolla in den andra delen av handledningen som presenterar två scenarier för att lägga till interaktivitet i diagrammet.

Följ mig på Twitter för mer datavetenskap / datavisualiseringsprojekt!

kodexempel

line_chart.html:

stilar.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%;}

data.csv: