web & mobile DEVELOPER 07 / 2016 – Datenvisualisierung mit D3

Screen Shot 2016-06-17 at 16.13.16

 

Datenvisualisierung mit D3

Die Menge an Daten nimmt stetig zu und deren Struktur wird zudem immer komplexer – hier ist eine leistungsfähige Lösung gefragt, die diese Datenmengen bequem und individuell visualisieren und imm Web darstellen kann. D3 ist eine mächtige JavaScript-Bibliothek, die Entwicklern genau dabei helfen kann.

Von Patrick Lobacher

D3 steht für “Data-Driven Documents” und ist eine der mächtigsten JavaScript-Bibliotheken zur Datenvisualisierung und steht zudem unter der BSD-Lizenz. Hiermit kann man von einfachen Datentabellen über aufwändig gestylte und animierte Charts bis hin zu hochkomplexen und auch interaktiven Diagrammen alles visualisieren, was letztlich nur durch die Fähigkeiten und der Kreativität des Entwicklers begrenzt wird.

D3_lobacher_01

In der Gallerie https://github.com/mbostock/d3/wiki/Gallery kann man sich einen Eindruck von den eindrucksvollen Möglichkeiten der Bibliothek machen. Klickt man auf eines der Beispiele, erhält man den zugehörigen Programmcode, um das Beispiel nachzuprogrammieren.

Die Bibliothek erlaubt die Manipulation des DOM von HTML-Dokumenten auf der Grundlage von Datensätzen. Dafür nutzt sie die Möglichkeiten moderner Browser wie Safari, Chrome, Firefox, Opera oder IE9+ sowie der aktuellen Webstandards CSS3, HTML5 und SVG.

D3_lobacher_02

Funktionsweise

Dabei sind “Data-Driven Documents” so zu verstehen: Die Daten (data) warden vom Anwender geliefert, ddas Dokument (documents) ist ein Web-basiertes Dokument und zwar alles, was ein Webbrowser rendern kann (also HTML, SVG, …). D3 wiederum verbindet die Daten mit dem Dokument.

Dabei agiert D3 in den folgenden Bereichen:

  • Loading
    Laden der Daten in den Speicher des Browsers
  • Binding
    Verbinden der Daten zu Elemente in einem Dokument, ggf. Neuerstellung von Elementen falls benötigt
  • Transforming
    Transformation dieser Element und entsprechendes Setzen der Eigenschaften
  • Transitioning
    Überführen von Elementen zwischen Statis aufgrund von User-Eingaben

 

Setup

 

Alles was Sie brauchen ist ein einfacher Webserver. Hier können Sie entweder einen klassischen Server installieren oder aber auch schlicht unter Node.js (mittels npm install http-server –g) und anschließendem http-server . oder mittels Phyton: python –m SimpleHTTPServer 8888 bzw. python –m http.server.

Legen Sie im Dokument-Root einen Projektordner an und laden Sie dort hinein die neueste Version von D3, die Sie unter https://d3js.org finden. Zum Veröffentlichungszeitpunkt war dies die Version 3.5.16. Wenn Sie eine andere Version benötigen, werden Sie unter https://github.com/mbostock/d3/releases fündig. Das nächste Major-Release finden Sie unter: https://github.com/mbostock/d3/tree/4.

Starten wir nun mit einer einfachen HTML-Datei, welche wir unter index.html im Projektordner ablegen. Dabei gehen wir davon aus, dass sich D3 im Unterordner d3 befindet.

Listing 1: index.html

Nun legen wir noch ein Verzeichnis css an und dort hinein legen wir eine Datei d3.css mit dem folgenden Inhalt:

Listing 2: d3.css

Über den Inspektor können wir überprüfen, ob die Bibliothek ordnungsgemäß geladen wurde.

Hello World

Nun wollen wir unsere erste D3-Anweisung schreiben – diese notieren wir in unserer HTML-Datei anstelle des Kommentars „Code befindet sich hier“:

Listing 3: Erste D3-Anweisung

Als Ausgabe erhalten wir „Hello world!“.

Das d3.select Kommando wird dazu verwendet, um in D3 ein einzelnes Element auszuwählen. Als Argument wird hierzu (ähnlich wie bei jQuery) ein CSS3-Selektor übergeben oder aber eine Referenz zu einem Element.

Die Rückgabe ist ein D3 Selektionsobjekt, auf dem weitere Operationen durchgeführt werden können.

Unser Beispiel wird daher wie folgt abgearbeitet:

  • A
    Wir haben einen Absatz, dem wir die ID target gegeben haben, um diesen später besser ansprechen zu können
  • B
    Wir selektieren nun den obigen Absatz
  • C
    Und setzen den Inhalt (InnerHtml) auf „Hello world!“

 

D3 Methoden

Es gibt zahlreiche Methoden, um das Ergebnis-Set zu manipulieren:

  • attr(foo,bar)
    Spricht das Attribut foo an und setzt es auf bar. Lässt man den zweiten Parameter weg, so erhält man den Wert des Attributs zurück
  • classed(foo,bar)
    Spricht die Klasse an. Ist bar = TRUE so wird die Klasse gesetzt, ansonsten wird die Klasse zurückgegeben
  • style(foo,bar)
    Setzt den CSS-Stil foo (z.B. font-size) auf den Wert bar.
  • text(foo)
    Setzt den Text-Inhalt eines Containers auf foo oder gibt den Text schlicht zurück (wenn foo nicht gesetzt wurde)
  • html(foo)
    Setzt den HTML-Inhalt eines Containers auf foo oder gibt den Inhalt schlicht zurück (wenn foo nicht gesetzt wurde)

Es ist zudem möglich, mehrere Elemente zu selektieren – dafür wird die Methode selectAll() verwendet:

Listing 4: selectAll()

Hier werden alle DIV-Container selektiert (A) und mit der Klasse blue box versehen (B).

Über each() kann nun auf jedes der vorher selektierten Elemente per Funktion zugegriffen werden:

Listing 5: each()

Neben der Selektion (A) und dem Ausstatten mit einer Klasse (B) wird nun per each() Schleife auf jedes dieser selektierten Elemente einzeln zugegriffen (C). Der Zugriff selbst erfolgt per this. Über die Funktion append() wird zudem ein <h1>-Tag ausgegeben und dort hinein wird die Nummer des Indexes geschrieben (0 für den ersten Durchlauf, 1 für den zweiten, …).

 

Enter-Update-Exit Pattern

D3 arbeitet in Bezug auf Daten mit einem sogenannten „Enter-Update-Exit“-Pattern. D3 bindet Daten an das DOM, indem man einer Selection über den Data Operator die Daten in Form eines Arrays übergibt.

Hier kommt der sogenannte “Data Join” zum Einsatz. Zurückgeliefert werden drei neue Selections, die D3 „Enter“, „Update“,und „Exit“ nennt.

D3_lobacher_03

 

In der Update Selection sind die Elemente des DOM mit den Elementen des Data Arrays in der Reihenfolge ihres jeweiligen Erscheinens verknüpft. Wie man unschwer errät, kann jedoch eine unterschiedliche Anzahl von Elementen im DOM und in den Daten vorhanden sein.

Wenn im DOM mehr Elemente vorhanden sind als Daten, dann liegen diese Elemente in der “Exit” Selection, mit der man die Elemente aus dem DOM entfernen kann.

Gibt es mehr Daten als DOM-Elemente, dann befinden sich diese Daten in der “Enter” Selection, mit der die fehlenden Elemente im DOM erstellt werden können.

Listing 6: Enter-Update-Exit

Im Beispiel sieht man einen Absatz, der in grün eingefärbt wurde. Nun wird der Body selektiert (A) und in diesem alle Absätze (B). Darauf wird die data()-Funktion angewendet und alle Absätze rot eingefärbt (dies entspricht der “update” Funktion, die keinen eigenen Namen hat). Da es aber nur einen Absatz, dafür aber drei Daten-Einträge gibt, legen wir in der “enter” Funktion für die restlichen Daten weitere Elemente an.

D3_lobacher_04

Balkendiagramm

Wir wollen nun Daten mit Hilfe eines Balkendiagramms visualisieren. Schauen wir uns hierzu ein etwas komplexeres Beispiel an:

Listing 7: Enter-Update-Exit

Hier gibt es zunächst einen Datensatz, der aus 10 Daten besteht (A). Diese werden über die Methode render() visualisiert (B).

Nun werden in der Enter-Sektion all jene <div>-Tags selektiert (C), die eine Klasse h-bar besitzen und mappen darauf die Daten (D). Tatsächlich ist unser Markup aber absolut leer – d.h. es existieren überhaupt keine Tags.

Genau das macht sich aber die Enter-Funktion (E) zunutze – d.h. gemäß dem „Enter-Update-Exit“-Paradigma, werden nun alle Elemente angelegt (F), die es im DOM noch nicht gibt und zudem ein <span>-Tag mit einem Label erzeugt (G).

In der Update-Funktion werden nun gemäß den Daten ein Attribut width gesetzt (H) und in das Label die Breite eingetragen (I). Damit wird später eine Visualisierung (vertikale Balken) möglich gemacht.

Über die setInterval()-Funktion (K) wird nun alle 1,5 Sekunden der erste Wert aus dem Data-Array entfernt und ein neuer Wert per Zufall in das Data-Array gepusht. Die neue Liste wird anschließend als Balkendiagram ausgegeben.

D3_lobacher_05

 

Laden von Daten

Meist kommen die Daten zur Visualisierung aus einer anderen Datenquelle, wie einem Webservice, einem CSV-File oder einer JSON-Datei. Schauen wir uns daher an, wie man beispielsweise eine JSON-Datei laden kann.

Legen Sie hierzu eine Datei data.json im Webroot mit dem folgenden Inhalt an:

Listing 8: data.json

Nun greifen wir darauf in der Datei index.html zu:

Listing 9: Laden von Daten (index.html)

Der Aufbau ist weitensgehend identisch mit dem vorhergehende Beispiel. In der Funktion load() allerdings wird nun das JSON-File asynchron geladen (A) und die dort enthaltenen Daten zu den bisherigen zugefügt. Getriggert wird die Funktion mittels Klick auf den Button „Laden der Daten aus einem JSON-Feed“. Ähnliche Funktionen gibt es für Dateien vom Typ CSV, TSV, HTML und XML.

 

Skalen, Achsen und Übergänge

Im nächsten Beispiel wollen wir uns sowohl die Skalen anschauen, als auch die Achsen-Erzeugung uns Übergänge zwischen Visualierungen.

Dafür erzeugen wir ein Koordinatensystem mit einer X- und Y-Achse und platzieren darauf 50 zufällig angeordnete Datenpunkte.

Hier verwenden wir im Gegensatz zu den vorherigen Beispielen SVG als Träger der Visualisierung.

Aber zuerst müssen wir noch etwas CSS zu unserer Datei d3.css hinzufügen, um die Achsen samt Beschriftung zu stylen.

Listing 10: Erweiterung des CSS

Nun bauen wir das entsprechende HTML auf:

Listing 11: index.html

D3_lobacher_06

 

Hier passiert im Einzelnen folgendes:

 

  • A
    Ganz oben befindet sich ein Absatz mit einem Text, der signalisiert, dass bei Klick auf diesen sich die Visualisierung neu aufbaut
  • B
    Anfangs legen wir fest, dass die Visualisierung immer 500 Pixel Breite und 300 Pixel Höhe haben soll. Später passen wir die Achsen exakt auf diese Maße an
  • C
    Nun bauen wir das Datenset als Array mit Hilfe der Funktion randomValue() auf, die sich unter M befindet. Diese sorgt dafür, dass 50 zufällige Punkte (mit Koordinaten zwischen 0 und 1000) entstehen, die wir darstellen wollen
  • D
    An dieser Stelle werden die Achsen (X und Y) aufgebaut. Dafür bedienen wir uns der Funktion d3.scale.linear() (welche eine lineare Achse als Objekt zur Verfügung stelt). Über domain() wird nun festgelegt, dass die Achse Werte von 0 bis zum maximalen Wert aus C behandeln soll (sogn. “Input Value”) und diese per range() innerhalb der Größe der unter B festgelegten Werte inkl. Padding ausgeben soll (sogn. “Output Value”).
  • E
    Hier werden nun die Achsen als SVG-Objekte gerendert. Dabei wird über scale() das Achsen-Objekt aus D verwendet, als Orientierung (Funktion orient()) wird für die X-Achse „unten“ und für die Y-Achse „links“ festgelegt. Zudem werden 5 Unterteilungen in der Scale über ticks() eingebracht.
  • F
    An dieser Stelle wird das SVG-Objekt selbst erstellt und im Body als <svg>-Tag mit unter B festgelegter Breite und Höhe im DOM eingefügt.
  • G
    Schließlich werden die Datenpunkte als Kreise mit einem Radius von 2px im Koordinatensystem platziert
  • H
    Damit bei Klick auf den Absatz beim Neuaufbau der Visualisierung eine Animation zwischen altem und neuem Zustand erfolgen kann, werden entsprechende CSS3-Anweisungen eingebracht
  • I
    Der Klick auf den Absatz wird per on()-Funktion getriggert. Anschließend wird das Datenset neu erstellt (per Zufallsfunktion) und die Achsen entsprechend neu skaliert.
  • K
    Nun werden alle bestehenden Datenpunkte selektiert und diese per Übergang über transition() auf die neuen Datenpunkte mit einer Animationslänge per duration() von 1000ms animiert. Zudem werden die Punkte während der Animation kurzzeitig mit der Farbe Magenta eingefärbt attr(„fill“, „magenta“) und größer attr(„r“, 7) dargestellt.
  • L
    Natürlich müssen auch die neuen Achsen dargestellt werden – in diesem Fall ebenfalls animiert.
  • M
    Diese Funktion erzeugt das zufällige Datenset. Dabei werden 50 Datenpunkte mit einem Wert zwischen 0 und 1000 ermittelt und als Array zurückgegeben.

 

Die HTML-Ausgabe sieht hiermit wie folgt aus:

Listing 12: Erzeugtes DOM

 

Charts

Mit dem bisherigen Wissen können nun beliebige Chart-Typen realisiert werden, wie Tortendiagramme, Bubble-Charts, Ring-Charts, Stack-Layouts, Network-Layouts, Tree-Maps, Layered Area Chart, u.v.a.m.

 

D3_lobacher_07

 

Fazit

Derzeit gibt es sicherlich kein anderes Werkzeug um selbst komplexe Daten in unzähligen Darstellungsformen – selbst mit Interaktionen – zu visualisieren. Zudem existieren zahlreiche Integrationen, z.B. in AngularJS, React oder gar R, sodaß D3 sicherlich den Standard in der Datenvisualisierung setzt.

Links zum Thema

D3 Website
https://d3js.org

Intro to D3 (Slideshare)
http://de.slideshare.net/flatironschool/d3-729-3

Buch: Interactive Datavisualisation for the web
http://chimera.labs.oreilly.com/books/1230000000345/index.html

D3 Tutorials
https://github.com/mbostock/d3/wiki/Tutorials

D3 API Referenz
https://github.com/mbostock/d3/wiki/API-Reference

Website von Mike Bostock
https://bost.ocks.org/mike/

 

Leave a Comment.