Oprettelse af et linjediagram i D3.js v. 5
tiden er inde til at intensivere vores spil og oprette et linjediagram fra bunden. Og ikke bare et hvilket som helst linjediagram: en graf i flere serier, der kan rumme et hvilket som helst antal linjer. Udover at håndtere flere linjer, vi vil arbejde med tid og lineære skalaer, akser, og etiketter – eller rettere, få dem til at fungere for os. Der er masser at gøre, så jeg foreslår, at du affyrer din D3-server, og lad os få revner.
Dokumentforberedelse
som det første trin er vi nødt til at forberede dataene og filstrukturen til visualiseringen. Opret line_chart.html, stilarter.css og data.CSV i din projektmappe og udfyld dem med de uddrag, der følger. Dette vil få os i gang.
Indsæt dette til line_chart.html-fil. Koden definerer svg-elementet for os, så vi kan begynde at tegne med det samme. Jeg har også oprettet en grundlæggende struktur på forhånd, så det er lettere at navigere i dokumentet, når vi arbejder på dets særlige sektioner.
Forlad stilarterne.css-dokument tomt for nu. Indsæt følgende rækker i data.csv. Linjediagrammet indeholder flere serier: svarende til kolonnerne A, B og 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
data forberedelse
det første skridt – og et afgørende skridt for hele visualiseringen – er at læse dataene korrekt. Jeg har brugt et multi-serie Eksempel af en grund: selvom det er ret nemt at plotte en enkelt sti, kræver håndtering af flere linjer (især et udefineret antal af dem) lidt mere af D3 finesse. Indsæt følgende i afsnittet DATA, genindlæs html-dokumentet, og gennemgå konsolloggen i din bro. ser:
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);
lad os gennemgå de transformationer, der kaldes på vores datasæt en efter en:– data.kolonner returnerer CSV-overskrifterne-data.kolonner.skive (1) returnerer CSV – overskrifterne uden datakolonnen (skiven starter ved kolonne indekseret ved 1) – kort () kalder en funktion på hvert element i arrayet (bestående af A, B og C)– lad os kalde hvert af disse elementer en ‘skive’– kort () tildeler kolonnenavnet som et id– element til hver skive-så tildeler det et værdiarray til hver skive-bemærk, hvordan værdielementet fremkalder en funktion. Her kortlægger vi oplysninger fra det originale datasæt: arrayet består af 2 kolonner, dato og måling. Datoen er afledt af den første kolonne (og omdannet til et datoformat), og målingen er taget fra kolonnen svarende til skiven id.At slutningen af disse transformationer får vi 3 arrays: A, B og C, med 2 kolonner hver: dato og måling. Dette er en utrolig fleksibel måde at skære et datasæt på: uanset hvor mange kolonner det har! Det hele gøres i de få rækker. Jeg har udskrevet nogle oplysninger til konsollen for at hjælpe dig med at gennemgå uddraget.
Scales forberedelse
efter at dataene er læst I, skal vi konfigurere skaleringsmekanismen. Dette gøres for at udskrive diagrammet i overensstemmelse med svg ‘ s fast ejendom. Vægte omdanner datainputtet (vores datoer og værdier) til koordinater på svg-planet. Indsæt følgende linjer i afsnittet skalaer.
const xScale = d3.scaleTime().range();const yScale = d3.scaleLinear().rangeRound();xScale.domain(d3.extent(data, function(d){ return timeConv(d.date)}));yScale.domain();
vi vil plotte datoerne på h-aksen og værdierne på Y-aksen. D3 giver en scaleTime () – metode til skaleringsdatoer og en scaleLinear () – metode til kontinuerlige værdier. Vi bestemmer først skalaens rækkevidde: hvad inputværdierne skal oversættes til. I dette tilfælde strækker vi dataværdierne fra 0 til svg ‘s bredde og de numeriske værdier fra svg’ s højde til 0. Som det andet trin angiver vi inputdatadomænet. Et domæne består af alle værdier mellem et specificeret minimum og maksimum, som et datasæt kan tage. I stedet for manuelt at slå disse værdier op, sender vi det gennem indbyggede D3-funktioner: – d3.omfang () returnerer en minimums – og maksimumværdi af et array (i en naturlig rækkefølge)– dette fungerer perfekt på vores datosæt-d3.MAKS () Returnerer en maksimal værdi af arrayet. Bemærk, hvordan vi i dette eksempel først udtrækker en maksimal værdi fra hvert array for derefter at vælge maksimalt alle tre. Jeg tilføjede også 4 til den maksimale værdi af rent subjektive æstetiske grunde: jeg ville have noget plads over grafen.
vægten er nu sat op. Hvis du ikke har nok af skalaer og gerne vil se flere eksempler, skal du kigge på min tidligere tutorial.
en velkonfigureret skala gør det muligt for os at begynde at plotte værdier på svg. Hver gang vi fremkalder datasættet, skal vi bare kalde en passende skala på det.
akser
nok chatter – lad os tegne noget allerede! Akser er et godt udgangspunkt: hvis de er tegnet korrekt, vil de forsikre os om, at dataene er blevet læst som forventet, og at de skaleres så pænt, som vi har forestillet os.
Indsæt dette på akser under Forberedelsesafsnittet:
const yaxis = d3.axisLeft().scale(yScale); const xaxis = d3.axisBottom().scale(xScale);
og dette til akser under tegningen sektion:
svg.append("g") .attr("class", "axis") .attr("transform", "translate(0," + height + ")") .call(xaxis);svg.append("g") .attr("class", "axis") .call(yaxis);
og så simpelt er det, at vi har tegnet h-og Y-akserne!
ganske vist er akserne ikke de mest elegante i verden (der er nogle smukke akser derude), men de er her! Der er nogle ekstra justeringer, vi kan anvende for at gøre dem mere venlige for læseren.
lad os først se på h-aksen: der sker noget sjovt med datoerne. Læsning fra venstre, vi får ‘Lør 20’, ‘Jul 21’, ‘Man 22’, og på et tidspunkt når vi bare ‘August’. Det ser ud til, at måneder og dage er kommet i en uhensigtsmæssig blanding af variationer. Vi er nødt til at sætte og afslutte denne freestyle, og med dette mener jeg, at vi skal beslutte, hvilket datoformat vi gerne vil udskrive på skærmen. D3.akse () metode giver os mulighed for at justere alle slags ting for flåter – deres antal, interval mellem punkterne, visningsformat osv. Lad os konfigurere nogle af dem til begge akser.
Udskift aksedefinitionen i Forberedelsesafsnittet med følgende uddrag og opdater 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);
ovenstående kode angiver et sæt antal flåter til Y-aksen (14 eller så mange som der er array-elementer / CSV-rækker). I tilfælde af H-aksen vises et kryds med en granularitet på en dag hver dag. Det er opnået ved at indstille tick-egenskaben til d3.timeDay.hver (1). Formatet for de viste datoer viser dagen og den forkortede måned for hvert kryds. Efter disse ændringer ender vi med noget forbedrede akser:
ulydige datoer er ikke længere et problem!
for at gøre det endnu bedre (er det endda muligt!!!) vi kan tilføje en etiket til Y-aksen for at vise, hvad værdierne står for. Mens datoerne er selvforklarende, tallene på egen hånd bærer ingen oplysninger. Tilføj en etiket (kald det hvad du vil-jeg gik med frekvens) ved at tilføje følgende til Y-aksetegningen:
//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");
(der er ingen stil indstillet til etiketten, så den ikke vises på grafen – men tro mig og Google Chrome-udviklerværktøjerne, den er der)
endelig, lad os forbedre udseendet på akserne. Ved at forbedre mener jeg: Indstil farver, bredder og gengivelse af hvert enkelt element, og vælg den skrifttype, der skal bruges. Indsæt følgende i css-filen, og tag gerne dine egne stilbeslutninger:
/* 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%;}
flåterne styres af .linje element af aksen, mens den faktiske akse er indstillet med .sti element. Akserne ser skarpe ud (forfatterens ydmyge mening) og klar til at byde nogle data velkommen:
uden videre, lad os plotte diagrammet!
linjediagram
linjer er i det væsentlige d3.stier (), der forbinder en flok koordinater på et 2D-plan. For at konstruere en linje skal du fortælle den, hvor du kan finde dens koordinater og derefter tilføje det til svg. Indsæt følgende uddrag i de tidligere oprettede pladsholdere, og lad os gennemgå koden sammen.
dette skulle komme til LINES bit under Forberedelsesafsnittet:
const line = d3.line() .x(function(d) { return xScale(d.date); }) .y(function(d) { return yScale(d.measurement); });
i dette uddrag kaldte vi en linjekonstruktør, d3.linje (), der bruger to accessorer: h for værdier på det vandrette plan og y for den lodrette akse. Her peger vi simpelthen på de mest granulære værdier af vores array, dato og måling (dette er ikke tid til at bekymre sig om den indlejrede csv-struktur). Når det er gjort, skal du indsætte følgende på linjer under Tegningsafsnittet:
const lines = svg.selectAll("lines") .data(slices) .enter() .append("g"); lines.append("path") .attr("d", function(d) { return line(d.values); });
dette kræver en vis forklaring. De variable linjer vælger et uidentificeret antal linjer fra svg-og fortæller straks D3, at der vil være 3 linjer ved at pege på skiverne (linjerne A, B og C). Det tilføjer derefter et G-element til hver af dem: et grupperingselement, der vil gøre vores liv lettere med tiden. G-elementet samler alt, hvad der har at gøre med en bestemt diagramserie (aka en skive i arrayet): linjen (repræsenteret ovenfor som en sti), dens datapunkter, som vi vil være i stand til at svæve over, og serieetiketterne.
den første ting at føje til linjer (der faktisk er 3 tomme g-containere) er diagramlinjerne selv. Vi kalder d3.linje () konstruktør på dataene for at tegne en sti. Se, hvordan vi først skal have adgang til værdierne under hver skive. Dette overføres derefter til konstruktøren, der trækker datoer og målinger efter behov.
når ændringerne er gemt, bliver visualiseringen opdateret til dette:
Okay, dette er ikke perfekt, men tro mig, vi kommer derhen! Lad os anvende nogle æstetiske rettelser på diagrammet og observere, hvordan det former sig. Tilføj følgende til stilarter.css:
/* LINE CHART */path {fill: none;stroke: #ed3700;}
vi er nødt til at indstille udfyldningen til ingen for at få figurerne igen som linjer. Opdater grafen:
Hvad adskiller et linjediagram fra en flok linjer, der sidder sammen på en graf? Evnen til at skelne mellem serien. I øjeblikket har vi kun førstnævnte.
til at begynde med skal vi skelne mellem linjerne i koden. Lad os tilføje et id til hver linjeklasse – tilføj følgende til LINJESEKTIONEN i Forberedelsesdelen:
let id = 0;const ids = function () { return "line-"+id++;}
dette lille stykke kode skaber en tæller, som vi kan udnytte til automatisk at tildele et linje-id til hver tilføjet linje. Lad os henvise til tælleren i klassens egenskab af stierne. Juster koden i afsnittet linjer for at tilføje egenskaben klasse:
const lines = svg.selectAll("lines") .data(slices) .enter() .append("g"); lines.append("path") .attr("class", ids) .attr("d", function(d) { return line(d.values); });
og magisk får hver sti sin egen klasse!
hvad der er tilbage for os at gøre er at henvise til disse klasser i css og give hver linje sin egen unikke karakter. Skift Linjediagramafsnittet i css for at sige:
/* 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;}
bemærk, hvordan jeg ikke kun ændrer farven, men også ændrer stregen på hver linje. Husk, at omkring 10% af alle mennesker har en vis grad af farveblindhed, og i al retfærdighed kan det være vanskeligt for nogen af os at skelne mellem farver. Farver vil bare blande sammen, hvis der er for mange dataserier og deres nuance vil vise forskelligt på hver skærm.
efter ændringerne er blevet anvendt, skal linjerne tydeligt adskilles på grafen – som vist nedenfor:
nu er serien differentieret, men det er stadig umuligt at fortælle, hvilken der er, medmindre du har gemt de underliggende data og har en temmelig syg visuel fantasi, i hvilket tilfælde Jeg spekulerer på, hvorfor du havde brug for en graf i første omgang. For at hjælpe flertallet af os i seriegenkendelsen foreslår jeg, at vi tilføjer serienavnet til højre for grafen. Føj følgende til tegningssektionen af 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; });
uddraget lokaliserer slutningen af hver linje og tilføjer et tekstelement til det. Teksten udskrives som Serie A, Serie B eller Serie C, afhængigt af linjen. Føj følgende til css-dokumentet for at justere serieetiketterne:
.serie_label {fill: #2b2929;font-family: Georgia;font-size: 80%;}
etiketterne er tilføjet! Hygge.
vi kan alle være enige om, at dette er et smukt linjediagram! Jeg har indsat den fulde kode nedenfor. Sørg for at tjekke den anden del af vejledningen, der præsenterer to scenarier for at tilføje interaktivitet til diagrammet.
Følg mig på kvidre for flere data-sciency / data visualisering projekter!
kodeprøver
line_chart.html:
stilarter.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: