Erstellen eines Liniendiagramms in D3.js v.5

Es ist an der Zeit, unser Spiel zu verbessern und ein Liniendiagramm von Grund auf neu zu erstellen. Und nicht irgendein Liniendiagramm: Ein Diagramm mit mehreren Reihen, das eine beliebige Anzahl von Linien aufnehmen kann. Neben dem Umgang mit mehreren Zeilen arbeiten wir mit zeit– und linearen Skalen, Achsen und Beschriftungen – oder besser gesagt, sie arbeiten für uns. Es gibt viel zu tun, also schlage ich vor, dass Sie Ihren D3-Server abfeuern und loslegen.

Wir werden diese Schönheit schaffen! Spaß!

Dokumentenvorbereitung

Im ersten Schritt müssen wir die Daten und die Dateistruktur für die Visualisierung vorbereiten. Erstellen Sie line_chart.html, Stile.css und Daten.csv in Ihrem Projektordner und füllen Sie sie mit den folgenden Snippets. Das wird uns den Anfang machen.

Fügen Sie dies in das line_chart ein.HTML-Datei. Der Code definiert das SVG-Element für uns, damit wir sofort mit dem Zeichnen beginnen können. Ich habe auch eine grundlegende Struktur im Voraus erstellt, damit es einfacher ist, durch das Dokument zu navigieren, während wir an seinen bestimmten Abschnitten arbeiten.

Lassen Sie die Stile.CSS-Dokument vorerst leer. Fügen Sie die folgenden Zeilen in Daten ein.csv. Das Liniendiagramm enthält mehrere Reihen: entsprechend den Spalten A, B und 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

Datenaufbereitung

Der erste Schritt – und ein entscheidender Schritt für die gesamte Visualisierung – ist das korrekte Lesen der Daten. Ich habe aus einem bestimmten Grund ein Beispiel für mehrere Reihen verwendet: Während es ziemlich einfach ist, einen einzelnen Pfad zu zeichnen, erfordert die Behandlung mehrerer Zeilen (insbesondere einer undefinierten Anzahl von Zeilen) etwas mehr D3-Finesse. Fügen Sie Folgendes in den Abschnitt DATEN ein, laden Sie das HTML-Dokument neu und überprüfen Sie das Konsolenprotokoll in Ihrem 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);

Lassen Sie uns die für unseren Datensatz aufgerufenen Transformationen nacheinander überprüfen: – Daten.columns gibt die CSV–Header- Daten zurück.Spalten.slice (1) gibt die CSV–Header ohne die Datumsspalte zurück (das Slice beginnt bei der bei 1 indizierten Spalte) – map() ruft eine Funktion für jedes Element des Arrays auf (bestehend aus A, B und C) – Nennen wir jedes dieser Elemente ein ’slice’– map() weist jedem Slice den Spaltennamen als ID–Element zu – dann weist es jedem Slice ein values Array zu – Beachten Sie, wie das values Element eine Funktion hervorruft. Hier ordnen wir Informationen aus dem ursprünglichen Datensatz zu: Das Array besteht aus 2 Spalten, Datum und Messung. Das Datum wird aus der ersten Spalte abgeleitet (und in ein Datumsformat transformiert), und die Messung wird aus der Spalte entnommen, die dem Slice entspricht id.At am Ende dieser Transformationen erhalten wir 3 Arrays: A, B und C mit jeweils 2 Spalten: Datum und Messung. Dies ist eine erstaunlich flexible Möglichkeit, einen Datensatz aufzuteilen: unabhängig davon, wie viele Spalten er hat! Es ist alles in diesen wenigen Reihen gemacht. Ich habe einige Informationen in die Konsole gedruckt, damit Sie das Snippet überprüfen können.

Konsolenprotokoll

Überprüfen Sie das Konsolenprotokoll für weitere Informationen

Skalenvorbereitung

Nachdem die Daten eingelesen wurden, müssen wir den Skalierungsmechanismus konfigurieren. Dies geschieht, um das Diagramm entsprechend den Immobilien des Kunden zu drucken. Skalen transformieren die Dateneingabe (unsere Daten und Werte) in Koordinaten auf der SVG-Ebene. Fügen Sie die folgenden Zeilen in den Abschnitt SKALEN ein.

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

Wir zeichnen die Daten auf der x-Achse und die Werte auf der y-Achse. D3 bietet eine scaleTime() -Methode zum Skalieren von Daten und eine scaleLinear () -Methode für kontinuierliche Werte. Wir entscheiden zuerst den Bereich der Skala: In was die Eingabewerte übersetzt werden sollen. In diesem Fall dehnen wir die Datenwerte von 0 auf die Breite der SVG und die numerischen Werte von der Höhe der SVG auf 0. Als zweiten Schritt geben wir die Eingabedatendomäne an. Eine Domäne besteht aus allen Werten zwischen einem angegebenen Minimum und Maximum, die ein Datensatz annehmen kann. Anstatt diese Werte manuell nachzuschlagen, übergeben wir sie über integrierte D3–Funktionen: – d3.extent() gibt einen minimalen und maximalen Wert eines Arrays (in einer natürlichen Reihenfolge) zurück – dies funktioniert perfekt auf unserem Datumssatz– d3.max() gibt einen Maximalwert des Arrays zurück. Beachten Sie, wie wir in diesem Beispiel zuerst einen Maximalwert aus jedem Array extrahieren, um dann ein Maximum aller drei auszuwählen. Aus rein subjektiven ästhetischen Gründen habe ich dem Maximalwert auch 4 hinzugefügt: Ich wollte etwas Platz über dem Diagramm haben.

Die Waage ist nun eingerichtet. Wenn Sie nicht genug davon haben und weitere Beispiele sehen möchten, schauen Sie sich mein vorheriges Tutorial an.

Eine gut konfigurierte Skala ermöglicht es uns, Werte auf der SVG zu zeichnen. Jedes Mal, wenn wir den Datensatz aufrufen, müssen wir nur eine entsprechende Skala aufrufen.

Genug geplaudert – lasst uns schon etwas zeichnen! Achsen sind ein guter Ausgangspunkt: Wenn sie richtig gezeichnet werden, versichern sie uns, dass die Daten wie erwartet gelesen wurden und dass sie so gut skaliert werden, wie wir es uns vorgestellt haben.

Fügen Sie dies in die ACHSEN unter dem Abschnitt Vorbereitung ein:

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

Und das zu ACHSEN unter dem Zeichnungsabschnitt:

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

Und so einfach haben wir die x- und y-Achse aufgetragen!

Die Achsen sind da!

Zugegeben, die Achsen sind nicht die elegantesten der Welt (es gibt einige hübsche Achsen da draußen), aber sie sind hier! Es gibt einige zusätzliche Verbesserungen, die wir anwenden können, um sie für den Leser freundlicher zu gestalten.

Schauen wir uns zuerst die x-Achse an: Mit den Daten ist etwas Lustiges los. Wenn wir von links lesen, erhalten wir ‚Sat 20‘, ‚Jul 21‘, ‚Mon 22‘ und irgendwann erreichen wir nur noch ‚August‘. Es scheint, als wären die Monate und Tage in einer ungehorsamen Mischung von Variationen gekommen. Wir müssen diesen Freestyle beenden, und damit meine ich, dass wir entscheiden sollten, welches Datumsformat wir auf dem Bildschirm drucken möchten. Die d3.mit der axis () –Methode können wir alle möglichen Dinge für Ticks anpassen – ihre Anzahl, das Intervall zwischen den Punkten, das Anzeigeformat usw. Lassen Sie uns einige davon für beide Achsen konfigurieren.

Ersetzen Sie die Achsendefinition im Abschnitt Vorbereitung durch das folgende Snippet und aktualisieren Sie die Visualisierung:

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

Der obige Code gibt eine festgelegte Anzahl von Häkchen für die y-Achse an (14 oder so viele, wie Array-Elemente / CSV-Zeilen vorhanden sind). Im Falle der x-Achse wird jeden Tag ein Häkchen mit einer Granularität von einem Tag angezeigt. Dies wurde erreicht, indem die tick-Eigenschaft auf d3 gesetzt wurde.timeDay.jeder(1). Das Format der angezeigten Daten zeigt den Tag und den abgekürzten Monat für jedes Häkchen an. Nach diesen Änderungen erhalten wir etwas verbesserte Achsen:

Ordnung, endlich

Ungehorsame Termine sind kein Problem mehr!

Um es noch besser zu machen (ist es überhaupt möglich!!!) wir können der y-Achse eine Beschriftung hinzufügen, um zu zeigen, wofür die Werte stehen. Während die Daten selbsterklärend sind, Die Zahlen allein enthalten keine Informationen. Fügen Sie eine Beschriftung hinzu (nennen Sie sie wie Sie möchten – ich habe sie verwendet), indem Sie Folgendes an die y-Achsenzeichnung anhängen:

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

( Es gibt keinen Stil für das Label, so dass es nicht in der Grafik angezeigt wird – aber glauben Sie mir und den Google Chrome Developer Tools, es ist da)

 Beschriftung

Die Beschriftung der y-Achse ist unsichtbar

Zum Schluss wollen wir das Aussehen der Achsen verbessern. Damit meine ich: Legen Sie die Farben, Breiten und das Rendering jedes einzelnen Elements fest und entscheiden Sie, welche Schriftart verwendet werden soll. Fügen Sie Folgendes in die CSS-Datei ein und treffen Sie Ihre eigenen Stilentscheidungen:

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

Die Zecken werden durch die gesteuert .Linienelement der Achse, während die tatsächliche Achse mit dem gesetzt .pfad-Element. Die Achsen sehen scharf aus (die bescheidene Meinung des Autors) und sind bereit, einige Daten zu begrüßen:

Prettified axes

Lassen Sie uns ohne weiteres das Diagramm zeichnen!

Liniendiagramm

Linien sind im Wesentlichen d3.pfade (), die eine Reihe von (x, y) Koordinaten in einer 2D-Ebene verbinden. Um eine Linie zu konstruieren, müssen Sie ihr mitteilen, wo sie ihre x- und y-Koordinaten finden soll, und diese dann an die SVG anhängen. Fügen Sie die folgenden Snippets in die zuvor erstellten Platzhalter ein und überprüfen Sie den Code gemeinsam.

Dies sollte etwas unter dem Vorbereitungsabschnitt stehen:

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

In diesem Snippet haben wir einen Zeilenkonstruktor, d3, aufgerufen.line(), die zwei Accessoren verwendet: x für Werte in der horizontalen Ebene und y für die vertikale Achse. Hier zeigen wir einfach auf die detailliertesten Werte unseres Arrays, Datums und Maßes (dies ist nicht die Zeit, sich um die verschachtelte CSV-Struktur zu kümmern). Sobald dies erledigt ist, fügen Sie Folgendes in LINIEN unter dem Zeichenabschnitt ein:

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

Dies erfordert eine Erklärung. Die Variable lines wählt eine nicht identifizierte Anzahl von Zeilen aus der SVG aus – und teilt D3 sofort mit, dass es 3 Zeilen geben wird, indem sie auf die Slices (Zeilen A, B und C) zeigt. Es fügt dann jedem von ihnen ein g-Element hinzu: ein Gruppierungselement, das unser Leben zu gegebener Zeit erleichtern wird. Das g-Element sammelt alles, was mit einer bestimmten Diagrammserie zu tun hat (auch bekannt als Slice im Array): die Linie (oben als Pfad dargestellt), die Datenpunkte, über die wir den Mauszeiger bewegen können, und die Serienbeschriftungen.

Das erste, was an Linien angehängt werden muss (die tatsächlich 3 leere g-Container sind), sind die Diagrammlinien selbst. Wir nennen das d3.line() -Konstruktor auf die Daten, um einen Pfad zu zeichnen. Sehen Sie, wie wir zuerst auf die Werte unter jedem Slice zugreifen müssen. Dies wird dann an den Konstruktor übergeben, der Daten und Messungen nach Bedarf abruft.

Nachdem die Änderungen gespeichert wurden, wird die Visualisierung:

Anstelle eines Liniendiagramms haben wir ein Bergdiagramm

Okay, das ist nicht perfekt, aber glauben Sie mir, wir kommen dorthin! Wenden wir einige ästhetische Korrekturen auf das Diagramm an und beobachten Sie, wie es sich formt. Fügen Sie Folgendes an Stile an.css:

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

Wir müssen die Füllung auf keine setzen, damit die Formen wieder als Linien angezeigt werden. Aktualisieren des Diagramms:

Die Linien sind aufgetaucht

Was unterscheidet ein Liniendiagramm von einem Bündel von Linien, die in einem Diagramm zusammengeklebt sind? Die Fähigkeit, zwischen den Serien zu unterscheiden. Im Moment haben wir nur ersteres.

Für den Anfang müssen wir zwischen den Zeilen im Code unterscheiden. Fügen wir jeder Zeilenklasse eine ID hinzu – fügen Sie dem Abschnitt ZEILEN im Vorbereitungsteil Folgendes hinzu:

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

Dieser kleine Code erstellt einen Zähler, mit dem wir jeder hinzugefügten Zeile automatisch eine Zeilen-ID zuweisen können. Verweisen wir auf den Zähler in der Klasseneigenschaft der Pfade. Passen Sie den Code im Abschnitt ZEILEN an, um die Klasseneigenschaft hinzuzufügen:

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

Und magisch bekommt jeder Pfad seine eigene Klasse!

Pfadklasse

Die Zeilen erhalten ihre eigene Identität

Wir müssen nur noch auf diese Klassen im CSS verweisen und jeder Zeile ihren eigenen eindeutigen Charakter geben. Ändern Sie den Liniendiagrammabschnitt des CSS zu sagen:

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

Beachten Sie, wie ich nicht nur die Farbe ändere, sondern auch den Strich jeder Zeile ändere. Denken Sie daran, dass etwa 10% aller Menschen ein gewisses Maß an Farbenblindheit haben und fairerweise kann es für jeden von uns schwierig sein, zwischen Farben zu unterscheiden. Farben mischen sich einfach zusammen, wenn zu viele Datenreihen vorhanden sind, und ihr Farbton wird auf jedem Monitor anders angezeigt.

Nachdem die Änderungen angewendet wurden, werden die Linien in der Grafik klar getrennt – wie unten gezeigt:

Die Linien unterscheiden sich visuell voneinander

Jetzt werden die Reihen unterschieden, aber es ist immer noch unmöglich zu sagen, welche welche ist, es sei denn, Sie haben die zugrunde liegenden Daten gespeichert und haben eine ziemlich kranke visuelle Vorstellungskraft. Um die Mehrheit von uns bei der Erkennung von Serien zu unterstützen, schlage ich vor, den Seriennamen an die rechte Seite des Diagramms anzuhängen. Fügen Sie dem Zeichnungsabschnitt von LINIEN Folgendes hinzu:

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

Das Snippet lokalisiert das Ende jeder Zeile und hängt ein Textelement an. Der Text wird je nach Zeile als Serie A, Serie B oder Serie C gedruckt. Fügen Sie dem CSS-Dokument Folgendes hinzu, um die Serienbeschriftungen anzupassen:

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

Die Etiketten sind angehängt! Bestzeit.

Jede Serie hat ein eigenes Label

Wir können uns alle einig sein, dass dies ein hübsches Liniendiagramm ist! Ich habe den vollständigen Code unten eingefügt. Schauen Sie sich unbedingt den zweiten Teil des Lernprogramms an, in dem zwei Szenarien zum Hinzufügen von Interaktivität zum Diagramm dargestellt werden.

Folgen Sie mir auf Twitter für weitere datenwissenschaftliche / Datenvisualisierungsprojekte!

Codebeispiele

line_chart.html:

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

daten.artikelnummer: