Een lijndiagram maken in D3.js v. 5

de tijd is gekomen om ons spel op te voeren en een lijndiagram te maken vanuit het niets. En niet zomaar een lijndiagram: een multi-serie grafiek die geschikt is voor elk aantal lijnen. Naast het hanteren van meerdere lijnen, werken we met tijd – en lineaire schalen, assen en labels-of liever gezegd, laten ze voor ons werken. Er is genoeg te doen, dus ik stel voor dat je je D3 server afvuurt en aan de slag gaat.

we zullen deze schoonheid creëren! Leuk!

documentvoorbereiding

als eerste stap moeten we de gegevens en de bestandsstructuur voorbereiden voor de visualisatie. Line_chart aanmaken.html, stijlen.css, en data.csv in uw projectmap en vul ze met de fragmenten die volgen. Hiermee kunnen we beginnen.

plak dit in het line_chart.html-bestand. De code definieert het svg-element voor ons zodat we meteen kunnen beginnen met tekenen. Ik heb ook een basisstructuur vooraf gemaakt, zodat het gemakkelijker is om door het document te navigeren terwijl we aan de specifieke secties werken.

verlaat de stijlen.css document leeg voor nu. Plak de volgende rijen in gegevens.csv. Het lijndiagram zal meerdere reeksen bevatten: overeenkomend met de kolommen A, B en 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

gegevensvoorbereiding

de eerste stap – en een cruciale stap voor de hele visualisatie – is het correct lezen van de gegevens. Ik heb een multi-serie voorbeeld gebruikt voor een reden: hoewel het vrij eenvoudig is om een enkel pad te plotten, vereist het hanteren van meerdere lijnen (vooral een ongedefinieerd aantal) een beetje meer D3 finesse. Plak het volgende in de DATA sectie, herlaad Het html document en bekijk de console log in uw 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);

laten we eens kijken naar de transformaties opgeroepen op onze data set een voor een: – data.columns geeft de csv headers– data terug.kolom.slice (1) geeft de csv headers terug zonder de datumkolom (de slice begint bij kolom geïndexeerd op 1) – map () roept een functie aan op elk element van de array (bestaande uit A, B en C) – laten we elk van deze elementen een ‘slice’ noemen– map () wijst de kolomnaam toe als een id– element aan elke slice– dan wijst het een waardenarray toe aan elke slice-let op hoe het waardenelement een functie oproept. Hier geven we informatie van de oorspronkelijke gegevensset in kaart: de array bestaat uit 2 kolommen, datum en meting. De datum wordt afgeleid van de eerste kolom (en omgezet in een datumformaat), en de meting wordt genomen uit de kolom die overeenkomt met de slice ‘ s id.At aan het einde van deze transformaties krijgen we 3 arrays: A, B en C, met elk 2 kolommen: datum en meting. Dit is een ongelooflijk flexibele manier om een dataset op te splitsen: ongeacht hoeveel kolommen het heeft! Het is allemaal gedaan in die paar rijen. Ik heb wat informatie op de console geprint om je te helpen het fragment te bekijken.

Console log

bekijk de console log voor meer informatie

schalen voorbereiding

nadat de gegevens zijn ingelezen moeten we het schalen mechanisme configureren. Dit wordt gedaan om de grafiek af te drukken in overeenstemming met het vastgoed van de svg. Schalen transformeren de gegevensinvoer (onze data en waarden) naar coördinaten op het svg-vlak. Plak de volgende regels in de schalen sectie.

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

We zullen de datums op de x-as en de waarden op de y-as plotten. D3 biedt een scaleTime () methode voor het schalen van datums, en een scaleLinear() methode voor continue waarden. We bepalen eerst het bereik van de schaal: waar de invoerwaarden naar moeten worden vertaald. In dit geval strekken we de gegevenswaarden uit van 0 tot de breedte van de svg en de numerieke waarden van de hoogte van de svg tot 0. Als tweede stap specificeren we het invoergegevensdomein. Een domein bestaat uit alle waarden tussen een bepaald minimum en maximum dat een dataset kan nemen. In plaats van deze waarden handmatig op te zoeken, geven we het door ingebouwde D3 functies:– d3.extent () geeft een minimum en maximum waarde van een array (in een natuurlijke volgorde) – dit zal perfect werken op onze datumset– d3.max () Geeft een maximale waarde van de array terug. Merk op hoe we in dit voorbeeld eerst een maximale waarde uit elke array halen om vervolgens een maximum van alle drie te selecteren. Ik heb ook 4 toegevoegd aan de maximale waarde om puur subjectieve esthetische redenen: Ik wilde wat ruimte boven de grafiek hebben.

de schalen zijn nu ingesteld. Als je niet genoeg van schalen en zou graag meer voorbeelden te zien, neem een kijkje op mijn vorige tutorial.

een goed geconfigureerde schalen stellen ons in staat om te beginnen met het plotten van waarden op de svg. Elke keer als we de dataset oproepen, hoeven we er alleen maar een passende schaal op te noemen.

Assen

genoeg gepraat-laten we al iets tekenen! Assen zijn een goed uitgangspunt: als ze correct zijn uitgezet, verzekeren ze ons dat de gegevens zijn gelezen zoals verwacht en dat ze zo mooi schalen als we ons hadden voorgesteld.

plak dit op assen onder de sectie voorbereiding:

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

en dit naar de bijlen beneden de schilderstuk sectie:

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

en zo simpel als dat we de x-en y-assen hebben uitgezet!

de assen zijn hier!

toegegeven, de assen zijn niet de meest elegante in de wereld (er zijn een aantal mooie assen die er zijn) maar ze zijn hier! Er zijn een aantal extra tweaks die we kunnen toepassen om ze vriendelijker te maken voor de lezer.

laten we eerst naar de x-as kijken: er is iets grappigs aan de hand met de datums. Lezend van links krijgen we’ za 20′,’ Jul 21′,’ ma 22′, en op een gegeven moment bereiken we net’augustus’. Het lijkt erop dat de maanden en dagen zijn gekomen in een ongehoorzame mix van variaties. We moeten een einde maken aan deze freestyle, en daarmee bedoel ik dat we moeten beslissen welk datumformaat we op het scherm willen afdrukken. De d3.axis () methode stelt ons in staat om allerlei dingen aan te passen voor teken – hun aantal, interval tussen de punten, weergaveformaat, enz. Laten we een aantal van hen configureren voor beide assen.

Vervang de definitie van assen in de sectie voorbereiding door het volgende fragment en vernieuw de visualisatie:

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

de bovenstaande code specificeert een vast aantal teken voor de y-as (14, of zoveel als er array-elementen / csv-rijen zijn). In het geval van de x-as wordt elke dag een teek weergegeven met een korreligheid van een dag. Dat is bereikt door de tick-eigenschap in te stellen op d3.tijddag.elke(1). Het formaat van de weergegeven datums toont de dag en de verkorte maand voor elke teek. Na die veranderingen komen we met iets verbeterde Assen:

Ordnung, at last

ongehoorzame data zijn niet langer een probleem!

om het nog beter te maken (is het zelfs mogelijk!!!) we kunnen een label toevoegen aan de Y-as om te laten zien waar de waarden voor staan. Hoewel de data vanzelfsprekend zijn, bevatten de nummers zelf geen informatie. Voeg een label toe (noem het wat je wilt – Ik ging met frequentie) door het volgende toe te voegen aan de Y-as tekening:

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

(er is geen stijl ingesteld voor het label, dus het zal niet worden weergegeven op de grafiek-maar geloof me en de Google Chrome developer tools, het is er)

Label

het label van de y-as is onzichtbaar

ten slotte, laten we het uiterlijk van de assen verbeteren. Met verbeteren bedoel ik: stel de kleuren, breedtes en rendering van elk element in en bepaal het lettertype dat u wilt gebruiken. Plak het volgende in het css-bestand en voel je vrij om je eigen stijl beslissingen te nemen:

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

de teken worden gecontroleerd door de .lijn element van de as, terwijl de werkelijke as is ingesteld met de .pad element. De assen zien er scherp uit (de nederige mening van de auteur) en klaar om enkele gegevens te verwelkomen:

voorspelde Assen

zonder verder oponthoud, laten we de grafiek plotten!

lijndiagram

lijnen zijn in wezen d3.paden() die een aantal (x, y) coördinaten verbinden op een 2D vlak. Om een lijn te construeren moet je hem vertellen waar de x-en y-coördinaten te vinden zijn en die dan toevoegen aan de svg. Plak de volgende fragmenten in de eerder gemaakte placeholders en laten we de code samen bekijken.

dit moet naar regels bit onder de voorbereiding sectie:

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

In dit fragment noemden we een lijnbouwer, d3.line() die twee accessors gebruikt: x voor waarden op het horizontale vlak, en y voor de verticale as. Hier wijzen we gewoon naar de meest korrelige waarden van onze array, datum en meting (dit is niet het moment om je zorgen te maken over de geneste csv-structuur). Als dat klaar is, plak je het volgende aan regels Onder het Tekengedeelte:

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

dit vereist enige uitleg. De variabele lijnen selecteert een ongeïdentificeerd aantal regels uit de svg-en onmiddellijk vertelt D3 dat er 3 regels door te wijzen naar de slices set (lijnen A, B, en C). Het voegt dan een g-element toe aan elk van hen: een groepering-element dat ons leven te zijner tijd gemakkelijker zal maken. Het G-element verzamelt alles wat te maken heeft met een bepaalde grafiekreeks (aka een slice in de array): de lijn (hierboven weergegeven als een pad), de gegevenspunten waar we over kunnen zweven, en de serie-labels.

het eerste dat wordt toegevoegd aan regels (die in feite 3 lege g-containers zijn) zijn de diagramlijnen zelf. We bellen de d3.line () constructor op de gegevens om een pad te tekenen. Zie hoe we eerst toegang moeten krijgen tot de waarden onder elke slice. Dit wordt vervolgens doorgegeven aan de constructeur die data en metingen trekt zoals vereist.

nadat de wijzigingen zijn opgeslagen, wordt de visualisatie bijgewerkt naar dit:

in plaats van een lijndiagram kregen we een bergdiagram

OK, dit is niet perfect, maar geloof me, we komen er! Laten we enkele esthetische fixes toepassen op de grafiek en observeren hoe het vorm geeft. Voeg het volgende toe aan stijlen.css:

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

we moeten de vulling op geen zetten om de vormen weer als lijnen te laten verschijnen. De grafiek verversen:

de regels zijn opgedoken

wat scheidt een lijndiagram van een stel regels die aan elkaar vastzitten in een grafiek? De mogelijkheid om onderscheid te maken tussen de series. Op dit moment hebben we alleen het eerste.

om te beginnen moeten we een onderscheid maken tussen de regels in de code. Laten we een id toevoegen aan elke regelklasse-voeg het volgende toe aan de sectie lijnen in het Voorbereidingsgedeelte:

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

dit stukje code creëert een teller die we kunnen gebruiken om automatisch een regel-id toe te wijzen aan elke toegevoegde regel. Laten we verwijzen naar de teller in de klasse eigenschap van de paden. Pas de code in het gedeelte Regels aan om de eigenschap class toe te voegen:

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

en op magische wijze krijgt elk pad zijn eigen klasse!

Padklasse

de regels krijgen hun eigen identiteit

wat ons rest is om deze klassen in de css te verwijzen en elke regel zijn eigen unieke karakter te geven. Wijzig de lijndiagram sectie van de css om te zeggen:

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

merk op dat ik niet alleen de kleur verander, maar ook de lijn van elke lijn verander. Vergeet niet dat ongeveer 10% van alle mensen een zekere mate van kleurenblindheid heeft en in alle eerlijkheid, het onderscheiden van kleuren kan lastig zijn voor ieder van ons. Kleuren zullen gewoon samenvloeien als er te veel gegevensreeksen zijn en hun tint zal op elke monitor anders worden weergegeven.

nadat de wijzigingen zijn aangebracht, worden de regels duidelijk van elkaar gescheiden in de onderstaande grafiek.:

de lijnen verschillen visueel van elkaar

nu zijn de series gedifferentieerd, maar het is nog steeds onmogelijk om te zeggen welke welke is, tenzij je de onderliggende gegevens hebt onthouden en een behoorlijk zieke visuele verbeelding hebt.in dat geval vraag ik me af waarom je eigenlijk een grafiek nodig had. Om de meerderheid van ons in de serie herkenning te helpen stel ik voor dat we de serie naam aan de rechterkant van de grafiek toevoegen. Voeg het volgende toe aan het tekengedeelte van lijnen:

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

het fragment lokaliseert het einde van elke regel en voegt er een tekstelement aan toe. De tekst wordt afgedrukt als Serie A, Serie B of Serie C, afhankelijk van de regel. Voeg het volgende toe aan het CSS-document om de serielabels aan te passen:

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

de labels zijn bijgevoegd! Goede tijden.

elke serie heeft zijn eigen label

We zijn het er allemaal over eens dat dit een mooie lijndiagram is! Ik heb de volledige code hieronder geplakt. Zorg ervoor dat u het tweede deel van de tutorial dat twee scenario ‘ s van het toevoegen van interactiviteit aan de grafiek presenteert.

Volg mij op Twitter voor meer data-sciency / data visualisatie projecten!

Codemonsters

line_chart.HTML:

stijlen.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: