image

Der Autor

Philipp K. Janert ist in Deutschland geboren und aufgewachsen. 1997 erwarb er an der Universität von Washington einen Doktorgrad in Theoretischer Physik. Seitdem arbeitet er als Programmierer, Wissenschaftler und Mathematiker im technischen Bereich. Er ist der Autor der Bücher Data Analysis with Open Source Tools (O’Reilly), Feedback Control for Computer Systems (O’Reilly) und Gnuplot in Action (Manning Publications).

image

Zu diesem Buch – sowie zu vielen weiteren dpunkt.büchern – können Sie auch das entsprechende E-Book im PDF-Format herunterladen. Werden Sie dazu einfach Mitglied bei dpunkt.plus+:

www.dpunkt.plus

Philipp K. Janert

D3-Praxisbuch

Interaktive JavaScript-Grafiken im Browser

Aus dem Amerikanischen von Volkmar Gronau

image

Philipp K. Janert

Lektorat: Michael Barabas

Bibliografische Information der Deutschen Nationalbibliothek

ISBN:

1. Auflage 2020

Hinweis:

image

Schreiben Sie uns:

Die vorliegende Publikation ist urheberrechtlich geschützt. Alle Rechte vorbehalten. Die Verwendung der Texte und Abbildungen, auch auszugsweise, ist ohne die schriftliche Zustimmung des Verlags urheberrechtswidrig und daher strafbar. Dies gilt insbesondere für die Vervielfältigung, Übersetzung oder die Verwendung in elektronischen Systemen.

5 4 3 2 1 0

Inhalt

Der Autor

Vorbemerkungen

Teil I

1Einleitung

Zielgruppe

Warum D3?

Was Sie in diesem Buch finden werden …

Ein Leitfaden durch dieses Buch

Konventionen

2Los geht’s: erste Graphen mit D3

Erstes Beispiel: eine einzige Datenmenge

Zweites Beispiel: zwei Datenmengen

Drittes Beispiel: Listeneinträge animieren

3Der Kern der Sache: Selections und Bindungen

Selections

Daten binden

Selections bearbeiten

Angaben zu gemeinsamen Eltern in Selections und Gruppen

4Ereignisse, Interaktivität und Animation

Ereignisse

Graphen mit der Maus erkunden

Fließende Übergänge

Animation mit Timer-Ereignissen

5Generatoren, Komponenten und Layouts: Kurven und Formen zeichnen

Generatoren, Komponenten und Layouts

Symbole

Linien und Kurven

Kreise, Bögen und Tortendiagramme: Arbeiten mit Layouts

Andere Formen

Eigene Komponenten schreiben

6Dateien, Datenabruf und Formate: Ein- und Ausgeben von Daten

Dateien abrufen

Tabellendaten parsen und schreiben

Zahlen formatieren

7Werte visualisieren: Interpolationen, Skalierungen und Achsen

Interpolation

Skalierungen

Achsen

Beispiele

8Farben, Farbskalen und Heatmaps

Farben und Farbräume

Farbschemas

Farbskalierungen

Falschfarbendiagramme und verwandte Techniken

9Bäume und Netze

Bäume und hierarchische Datenstrukturen

Kräftebasierte Partikelanordnung

10Hilfsmittel: Arrays, Statistiken und Zeitstempel

Strukturelle Bearbeitung von Arrays

Deskriptive Statistik für numerische Arrays

Datumsangaben und Zeitstempel

Teil II

AEinrichtung, Werkzeuge und Quellen

Einrichtung

Werkzeuge

Quellen

BSVG-Überlebensausrüstung

Einführung

Allgemeiner Überblick

Formen

Pfade

Text

Präsentationsattribute

Farben

Transformationen

Strukturelemente und Dokumentengliederung

Koordinaten, Skalierung und Rendering

SVG und CSS

Quellen

CJavaScript und das DOM

JavaScript

Das DOM

Der Browser als Entwicklungsumgebung

Quellen

Stichwortverzeichnis

Vorbemerkungen

Schreibweisen in diesem Buch

In diesem Buch werden die folgenden speziellen Schreibweisen verwendet:

Kursivschrift

Kennzeichnet neue Begriffe, URLs, E-Mail-Adressen, Dateinamen und Dateinamenerweiterungen.

Nichtproportionale Schrift

Wird für Programmlistings, aber auch für Programmelemente innerhalb des Fließtextes wie Variablen- und Funktionsnamen, Datenbanken, Datentypen, Umgebungsvariablen, Anweisungen und Schlüsselwörter verwendet.

Die Codebeispiele

Ergänzendes Material, wie Codebeispiele, Übungen usw., steht auf https://github.com/janert/d3-for-the-impatient zum Download bereit.

Danksagung

Ich möchte Mike Loukides und Scott Murray danken, die dieses Projekt von Anfang an voller Begeisterung unterstützt haben. Giuseppe Verni, Jane Pong, Matt Kirk, Noah Iliinsky, Richard Kreckel, Sankar Rao Bhogi, Scott Murray und Sebastien Martel haben das Manuskript oder Teile davon gelesen, die Beispiele getestet und viele wichtige Anregungen gegeben. Darüber hinaus haben Matt, Scott und Sebastien in einer umfangreichen Korrespondenz Fragen beantwortet und mir ihre Kenntnisse vermittelt. Besonderer Dank gilt Giuseppe Verni, der das gesamte Manuskript mit großem Interesse und Hingabe gelesen und viele hilfreiche Ratschläge gegeben hat.

Der Originaltitel ist eine verspätete Hommage an das Buch Unix for the Impatient von Paul W. Abrahams und Bruce R. Larson (Addison-Wesley Professional).

Teil I

1
Einleitung

D3.js (oder kurz D3 für Data-Driven Documents, also »datengestützte Dokumente«) ist eine JavaScript-Bibliothek, die dazu dient, den DOM-Baum (Document Object Model) zu bearbeiten, um Informationen grafisch darzustellen. Sie ist zu einem De-facto-Standard für Infografiken im Web geworden.

Trotz ihrer Beliebtheit wird D3 eine steile Lernkurve nachgesagt. Meiner Meinung nach liegt das nicht daran, dass D3 kompliziert wäre (was sie nicht ist), und auch nicht an ihrer umfangreichen API (die zwar groß, aber gut strukturiert und sehr gut gestaltet ist). Viele der Schwierigkeiten, mit denen neue Benutzer zu kämpfen haben, sind, so glaube ich, auf falsche Vorstellungen zurückzuführen. Da D3 verwendet wird, um eindrucksvolle Grafiken zu erstellen, liegt es nahe, sie als »Grafikbibliothek« anzusehen, die den Umgang mit grafischen Grundelementen erleichtert und es ermöglicht, gängige Arten von Plots zu erstellen, ohne sich um die Einzelheiten kümmern zu müssen. Neulinge, die sich D3 mit dieser Erwartung nähern, sind unangenehm überrascht von der Ausführlichkeit, mit der so grundlegende Dinge wie die die Farbe eines Elements festzulegen sind. Und was hat es mit diesen ganzen »Selections« auf sich? Warum kann man nicht einfach ein leinwandartiges Element verwenden?

Das Missverständnis beruht darauf, dass D3 eben keine Grafikbibliothek ist, sondern eine JavaScript-Bibliothek zur Bearbeitung des DOM-Baums. Ihre Grundbausteine sind keine Kreise und Rechtecke, sondern Knoten und DOM-Elemente. Die typischen Vorgehensweisen bestehen nicht darin, grafische Formen auf eine »Leinwand« (Canvas) zu zeichnen, sondern Elemente durch Attribute zu formatieren. Die »aktuelle Position« wird nicht durch x/y-Koordinaten auf einer Leinwand angegeben, sondern durch die Auswahl von Knoten im DOM-Baum.

Das führt – aus meiner Sicht – zu dem zweiten Hauptproblem für viele neue Benutzer: Bei D3 handelt es sich um eine Webtechnologie, die sich auf andere Webtechnologien stützt, auf die DOM-API und das Ereignismodell, auf CSS-Selektoren und -Eigenschaften (Cascading Style Sheets), auf das JavaScript-Objektmodell und natürlich auf das SVG-Format (Scalable Vector Graphics). In vielen Aspekten stellt D3 nur eine relativ dünne Schicht über diesen Webtechnologien dar und ihr eigenes Design spiegelt oft die zugrunde liegenden APIs wider. Das führt zu einer sehr umfangreichen und uneinheitlichen Umgebung. Wenn Sie bereits mit dem kompletten Satz moderner Webtechnologien vertraut sind, der als HTML5 bekannt ist, werden Sie sich darin zu Hause fühlen, doch wenn nicht, dann kann das Fehlen einer ausgeprägten, einheitlichen Abstraktionsschicht ziemlich verwirrend sein.

Glücklicherweise müssen Sie nicht sämtliche dieser zugrunde liegenden Technologien ausgiebig studieren. D3 erleichtert ihre Nutzung und bietet eine erhebliche Vereinheitlichung und Abstraktion. Der einzige Bereich, in dem es definitiv nicht möglich ist, sich einfach durchzumogeln, ist SVG. Sie müssen auf jeden Fall ein ausreichendes Verständnis von SVG mitbringen, und zwar nicht nur der darstellenden Elemente, sondern auch der Strukturelemente, die steuern, wie die Informationen in einem Graphen gegliedert sind. Alles, was Sie wissen müssen, habe ich in Anhang B zusammengestellt. Wenn Sie mit SVG nicht vertraut sind, sollten Sie diesen Anhang durcharbeiten, bevor Sie sich an den Rest des Buches machen. Sie werden später dafür dankbar sein.

Zielgruppe

Dieses Buch ist für Programmierer und Wissenschaftler gedacht, die D3 zu ihrem Werkzeugkasten hinzufügen möchten. Ich gehe davon aus, dass Sie ausreichende Erfahrungen als Programmierer aufweisen und ohne Schwierigkeiten mit Daten und Grafiken arbeiten können. Allerdings erwarte ich nicht, dass Sie mehr als oberflächliche Kenntnisse in professioneller Webentwicklung haben.

Folgende Voraussetzungen sollten Sie mitbringen:

Insbesondere aber gehe ich davon aus, dass meine Leser ungeduldig sind: erfahren und fähig, aber frustriert von früheren Versuchen, mit D3 zurechtzukommen. Wenn Sie sich darin wiedererkennen, dann ist dieses Buch genau das richtige für Sie!

Warum D3?

Warum sollten sich Programmierer und Wissenschaftler – oder überhaupt irgendwelche Personen, die nicht vorrangig Webentwickler sind – mit D3 beschäftigen? Dafür gibt es vor allem die folgenden Gründe:

Vor allem aber bin ich der Meinung, dass D3 eine emanzipierende Technologie ist, die es ihren Benutzern erlaubt, ihren Bestand an verfügbaren Lösungsmöglichkeiten ganz allgemein zu erweitern. Die bemerkenswertesten Nutzanwendungen von D3 sind wahrscheinlich diejenigen, die noch nicht entdeckt wurden.

Was Sie in diesem Buch finden werden ...

Dieses Buch soll eine möglichst umfassende und gleichzeitig knappe Einführung in D3 sein, die die wichtigsten Aspekte in ausreichender Tiefe darstellt.

Im Grunde genommen wünsche ich mir, dass dieses Buch Sie auf die Dinge vorbereitet, die Sie mit D3 tun können, an die ich aber selbst niemals gedacht hätte.

... und was nicht!

Dieses Buch ist bewusst auf D3 und die Möglichkeiten und Mechanismen dieser Bibliothek beschränkt. Daher fehlt eine ganze Reihe von Dingen:

Ich möchte insbesondere die beiden letzten Punkte betonen. In diesem Buch geht es ausschließlich um D3, ohne Nutzung oder Abhängigkeiten von anderen JavaScript-Frameworks und Bibliotheken. Das ist volle Absicht: Ich möchte D3 auch solchen Lesern zugänglich machen, die mit dem reichhaltigen, aber uneinheitlichen Umfeld von JavaScript nicht vertraut sind oder sogar auf Kriegsfuß stehen. Aus demselben Grund werden in diesem Buch auch keine anderen Themen der modernen Webentwicklung besprochen. Insbesondere finden Sie hier keinerlei Diskussionen zur Browserkompatibilität und ähnlichen Themen. Ich setze voraus, dass Sie einen modernen, aktuellen JavaScript-fähigen Browser verwenden, der in der Lage ist, SVG darzustellen.1

Ein weiterer Aspekt, der nicht behandelt wird, betrifft die Unterstützung von D3 für geografische und raumbezogene Informationen. Diese Themen sind zwar wichtig, aber gut überschaubar, sodass es nicht allzu schwierig sein sollte, sie durch die Lektüre der D3-Referenzdokumentation (https://github.com/d3/d3/blob/master/API.md) zu erlernen, wenn Sie mit den Grundlagen von D3 vertraut sind.

Ein Leitfaden durch dieses Buch

Dieses Buch ist kontinuierlich aufgebaut. Von Kapitel zu Kapitel wird neuer Stoff eingeführt. Allerdings können Sie insbesondere die hinteren Kapitel in beliebiger Reihenfolge lesen, nachdem Sie die Grundlagen in der ersten Hälfte des Buches erlernt haben. Ich schlage die folgende Vorgehensweise vor:

  1. Sofern Sie nicht bereits solide Kenntnisse in SVG haben, empfehle ich Ihnen dringend, mit Anhang B anzufangen. Ohne diese Kenntnisse ergibt alles andere nicht viel Sinn.
  2. Lesen Sie auf jeden Fall Kapitel 2 zur Einführung und um Ihre Erwartungen für die kommenden Themen richtig einzuordnen.
  3. Kapitel 3 ist Pflichtlektüre. Selections bilden das grundlegende Ordnungsprinzip von D3. Sie bieten nicht nur Zugriff auf den DOM-Baum, sondern kümmern sich auch um die Verknüpfung zwischen den DOM-Elementen und den Datenmengen. Praktisch jedes D3-Programm beginnt mit einer Selection, und das Verständnis, was eine Selection ist und was sie kann, ist für die Arbeit mit D3 unbedingt erforderlich.
  4. Streng genommen ist Kapitel 4 zum Thema Ereignisbehandlung, Interaktivität und Animation optional. Da diese Dinge jedoch zu den faszinierenden Möglichkeiten gehören, die D3 bietet, wäre es schade, dieses Kapitel zu überspringen.
  5. Kapitel 5 ist wichtig, da es die Grundprinzipien des Designs von D3 beschreibt (wie Komponenten und Layouts) und eine Einführung in allgemein nützliche Techniken gibt (wie SVG-Transformationen und benutzerdefinierte Komponenten).
  6. Die restlichen Kapitel können Sie im Großen und Ganzen in beliebiger Reihenfolge lesen, wann immer Sie etwas über die jeweiligen Themen wissen müssen. Insbesondere möchte ich jedoch Ihre Aufmerksamkeit auf Kapitel 7 mit seiner ausführlichen Beschreibung der unscheinbaren, aber äußerst vielseitigen Skalierungsobjekte sowie auf die Vielzahl der Funktionen zur Handhabung von Arrays in Kapitel 9 lenken.

Konventionen

In diesem Abschnitt werden einige Vereinbarungen vorgestellt, die in diesem Buch gelten.

Konventionen in der D3-API

In der D3-API gelten einige Konventionen, die die Nützlichkeit der Bibliothek erhöhen. Bei einigen davon handelt es sich nicht um D3-spezifische Regeln, sondern um gängige JavaScript-Idiome, mit denen Sie aber möglicherweise nicht vertraut sind, wenn Sie nicht selbst in JavaScript programmieren. Ich gebe Sie hier einmal gesammelt an, um die nachfolgenden Erörterungen von überflüssigen Wiederholungen frei zu halten.

Konventionen für die API-Referenztabellen

In diesem Buch finden Sie Tabellen mit Erklärungen zu einzelnen Teilen der D3-API. Die Einträge in diesen Tabellen sind nach Relevanz geordnet, wobei zusammengehörige Funktionen auch zusammen aufgeführt werden.

Konventionen für die Codebeispiele

Die Codebeispiele sollen die Merkmale und Mechanismen von D3 veranschaulichen. Um den Kernpunkt jeweils möglichst deutlich zu zeigen, wurden die Beispiele auf das Notwendige reduziert. Ich habe auf die meisten Feinheiten wie ansprechende Farben und interessante Datenmengen verzichtet. Meistens verwende ich Primärfarben und kleine und einfache Datenmengen.

Dafür ist jedes Beispiel in sich abgeschlossen, kann wie gezeigt ausgeführt werden und erstellt den zugehörigen Graphen. Bis auf wenige Ausnahmen gebe ich keine Codefragmente an. Meiner Erfahrung nach ist es besser, einfache Beispiele komplett darzustellen, anstatt nur die »interessanten Teile« längerer Beispiele zu zeigen. Auf diese Weise besteht keine Gefahr, dass der Gesamtzusammenhang verloren geht. Alle Beispiele sind ausführbar und können nach Belieben erweitert und ausgeschmückt werden.

Namenskonventionen

In den Beispielen gilt die folgende Namenskonvention für Variablen:

Aufbau der Quelldateien

Ab Kapitel 3 setze ich bei jedem Codelisting voraus, dass die Seite bereits ein <svg>-Element mit einem eindeutigen id-Attribut und ordnungsgemäß festgelegten width- und height-Attributen enthält. Der Beispielcode wählt dann dieses SVG-Element anhand seines id-Attributs aus und weist diese Selection häufig einer Variablen zu, um später darauf verweisen zu können:

var svg = d3.select( "#fig" );

Dadurch wird die Mehrdeutigkeit vermieden, die sich bei der Verwendung eines allgemeineren Selektors (wie d3.select( "svg" )) einstellen würde, und es erleichtert, mehrere Beispiele in eine einzige HTML-Seite aufzunehmen.

Zu jedem Graphen gibt es eine JavaScript-Funktion, die die SVG-Elemente des Diagramms dynamisch erstellt. Vereinbarungsgemäß beginnen die Funktionsnamen mit make, worauf der Wert des id-Attributs für das SVG-Zielelement folgt.

Bis auf Kapitel 2 gibt es in jedem Kapitel nur eine HTML-Seite und eine JavaScript-Datei. (Von wenigen Ausnahmen abgesehen nehme ich den JavaScript-Code nicht unmittelbar in die HTML-Seite auf.)

Plattform, JavaScript-Version und Browser

Um die Beispiele auszuführen, brauchen Sie einen lokalen oder gehosteten Webserver (siehe Anhang A). Die Beispiele sollten in jedem modernen Browser mit aktiviertem JavaScript laufen. Zurzeit sind verschiedene Versionen von JavaScript im Umlauf.2 In den Codebeispielen wird fast ausschließlich »klassisches« JavaScript (ES5, freigegeben 2009/2011) ohne weitere Frameworks und Bibliotheken verwendet. Für die folgenden drei Merkmale ist jedoch eine jüngere Version von JavaScript (ES6, veröffentlicht 2015) erforderlich:

2
Los geht’s: erste Graphen mit D3

Zu Anfang wollen wir einige Beispiele durcharbeiten, die die Möglichkeiten von D3 demonstrieren und Sie in die Lage versetzen, selbst Aufgaben aus der Praxis zu lösen – und sei es auch nur, indem Sie diese Beispiele entsprechend anpassen. Die ersten beiden Beispiele in diesem Kapitel zeigen, wie Sie aus Datendateien die gebräuchlichen Streu- und XY-Diagramme erstellen, und zwar komplett mit Achsen. Die Diagramme sehen nicht sehr hübsch aus, erfüllen aber ihren Zweck, und es ist auf einfache Weise möglich, sie zu verschönern und die Prinzipien auf andere Datenmengen zu übertragen. Das dritte Beispiel ist nicht vollständig, sondern dient mehr der Veranschaulichung, um Ihnen zu zeigen, wie einfach es ist, Ereignisbehandlung und Animationen in Ihre Dokumente aufzunehmen.

Erstes Beispiel: eine einzige Datenmenge

Um D3 kennenzulernen, betrachten wir die kleine, einfache Datenmenge aus Beispiel 2–1. Bei der grafischen Ausgabe dieser Daten mithilfe von D3 kommen wir schon mit vielen der Grundprinzipien in Berührung.

Beispiel 2–1: Eine einfache Datenmenge (examples-simple.tsv)

xy1

10050

200100

300150

400200

500250

Wie bereits in Kapitel 1 erwähnt, ist D3 eine JavaScript-Bibliothek zur Bearbeitung des DOM-Baums, um Informationen grafisch darzustellen. Das deutete schon darauf hin, dass jeder D3-Graph über mindestens zwei oder drei »bewegliche Teile« verfügt:

Beispiel 2–2 zeigt die gesamte HTML-Datei.

Beispiel 2–2: Eine HTML-Datei, die ein SVG-Element definiert

<!DOCTYPE html>

<html>

<head>

<meta charset="utf-8">

<script src="d3.js"></script>image

<script src="examples-demo1.js"></script>image

</head>

<body onload="makeDemo1()">image

<svg id="demo1" width="600" height="300"

style="background: lightgrey" />image

</body>

</html>

Ja, Sie sehen richtig – die HMTL-Datei ist im Grunde genommen leer! Alle Aktionen finden im JavaScript-Code statt. Sehen wir uns kurz die wenigen Dinge an, die in dem HTML-Dokument geschehen:

  1. imageAls Erstes lädt das Dokument d3.js, also die Bibliothek D3.
  2. imageDanach lädt das Dokument unsere JavaScript-Datei. Sie enthält alle Befehle zur Definition des Graphen, den wir anzeigen wollen.
  3. imageDas <body>-Tag definiert einen onload-Ereignishandler, der ausgelöst wird, wenn der Browser das <body>-Element komplett geladen hat. Die Ereignishandlerfunktion makeDemo1() ist in unserer JavaScript-Datei examples-demo1.js definiert.1
  4. imageAm Ende enthält das Dokument ein SVG-Element mit einer Größe von 600 × 300 Pixeln. Es hat einen hellgrauen Hintergrund, damit Sie es erkennen können, ist sonst aber leer.

Der dritte und letzte Bestandteil ist die JavaScript-Datei, die Sie in Beispiel 2–3 sehen.

Beispiel 2–3: Befehle für Abbildung 2–1

function makeDemo1() {image

d3.tsv( "examples-simple.tsv" )image

.then( function( data ) {imageimage

d3.select( "svg" )image

.selectAll( "circle" )image

.data( data )image

.enter()image

.append( "circle" )image

.attr( "r", 5 ).attr( "fill", "red" )image

.attr( "cx", function(d) { return d["x"] } )image

.attr( "cy", function(d) { return d["y"] } );

} );

}

Wenn Sie diese drei Dateien (die Datendatei, die HTML-Seite und die JavaScriptDatei) zusammen mit der Bibliotheksdatei d3.js in ein gemeinsames Verzeichnis stellen und die Seite im Browser laden, sollte der Browser den Graphen aus Abbildung 2–12 anzeigen.

Sehen wir uns nun nacheinander die einzelnen JavaScript-Befehle an:

  1. imageDas Skript definiert nur eine einzige Funktion, nämlich den Callback makeDemo1(), der aufgerufen wird, wenn die HTML-Seite vollständig geladen ist.
  2. imageDie Funktion lädt die Datendatei mithilfe der Fetch-Funktion tsv() (sie »ruft sie ab«, daher der Bezug zu »to fetch«). In D3 sind mehrere Funktionen definiert, um Dateiformate mit Trennzeichen zu lesen. tsv() ist für tabulatorgetrennte Dateien bestimmt.
  3. imageWie alle Funktionen aus der JavaScript-API Fetch gibt auch tsv() ein Promise-Objekt zurück. Ein solches Objekt enthält ein Resultset und einen Callback und ruft Letzteren auf, wenn das Resultset vollständig und zur Verarbeitung bereit ist. Anschließend stellt das Promise-Objekt die Funktion then() bereit, um den gewünschten Callback zu registrieren. (Mehr über JavaScript-Promises erfahren Sie im Abschnitt »Promises in JavaScript« auf S. 120.)
  4. imageDer Callback, der nach dem Laden der Datei aufgerufen werden soll, wird als anonyme Funktion definiert, die den Inhalt der Datendatei als Argument erhält. Die Funktion tsv() gibt den Inhalt der tabulatorgetrennten Datei als Array von JavaScript-Objekten zurück. Jede Zeile in der Datei ergibt ein Objekt, wobei die Eigenschaftennamen in der Kopfzeile der Eingabedatei definiert sind.
  5. imageWir wählen das <svg>-Element als die Position im DOM-Baum, die den Graphen enthalten soll. Die Funktionen select() und selectAll() nehmen einen CSS-Selektorstring entgegen (siehe »CSS-Selektoren« auf S. 38 in Kapitel 3) und geben die übereinstimmenden Knoten zurück – select() nur die erste Übereinstimmung, selectAll() eine Sammlung aller Übereinstimmungen.
  6. imageAls Nächstes wählen wir alle <circle>-Elemente innerhalb des <svg>-Knotens aus. Das mag absurd erscheinen, denn schließlich gibt es darin gar keine <circle>-Elemente! Dieser merkwürdige Aufruf stellt jedoch kein Problem dar, da selectAll("circle") lediglich eine leere Sammlung (von <circle>-Elementen) zurückgibt, sondern erfüllt sogar eine wichtige Funktion, da er mit dieser leeren Sammlung einen Platzhalter erstellt, den wir anschließend füllen werden. Das ist ein gängiges D3-Idiom, um Graphen mit neuen Elementen zu versehen.
  7. imageAls Nächstes verknüpfen wir die Sammlung der <circle>-Elemente mit der Datenmenge. Dazu verwenden wir den Aufruf data(data). Es ist wichtig, zu erkennen, dass die beiden Sammlungen (DOM-Elemente auf der einen Seite und Datenpunkte auf der anderen) nicht im Ganzen miteinander verbunden werden. Stattdessen versucht D3 eine 1:1-Beziehung zwischen den DOM-Elementen und den Datenpunkten herzustellen: Jeder Datenpunkt wird durch ein eigenes DOM-Element dargestellt¸ das wiederum seine Eigenschaften (wie Position, Farbe und Erscheinungsbild) aus den Informationen über den Datenpunkt bezieht. Der Aufbau und die Pflege dieser 1:1-Beziehungen zwischen einzelnen Datenpunkten und den zugehörigen DOM-Elementen ist ein grundlegendes Merkmal von D3. (In Kapitel 3 werden wir uns diesen Vorgang noch ausführlicher ansehen.)
  8. imageDie Funktion data() gibt die Sammlung der Elemente zurück, die mit den einzelnen Datenpunkten verknüpft wurden. Zurzeit kann D3 die einzelnen Datenpunkte nicht mit <circle>-Elementen verknüpfen, da es (noch) keine gibt. Die zurückgegebene Sammlung ist daher leer. Allerdings bietet D3 mithilfe der (merkwürdig benannten) Funktion enter() auch Zugriff auf alle restlichen Datenpunkte, für die keine übereinstimmenden DOM-Elemente gefunden werden konnten. Die übrigen Befehle werden für die einzelnen Elemente in dieser »Restsammlung« aufgerufen.
  9. imageAls Erstes wird der Sammlung der <circle>-Elemente innerhalb des in Zeile 6 ausgewählten SVG-Elements ein <circle>-Element angehängt.
  10. imageAnschließend werden einige fixe (also nicht datenabhängige) Attribute und Formatierungen festgelegt, nämlich der Radius (das Attribut r) und die Füllfarbe.
  11. imageSchließlich wird die Position der einzelnen Kreise aufgrund des Werts bestimmt, den der zugehörige Datenpunkt hat. Die Attribute cx und cy der einzelnen <circle>-Elemente werden jeweils auf der Grundlage der Einträge in der Datendatei festgelegt. Statt eines festen Werts stellen wir Zugriffsfunktionen bereit, die zu einem gegebenen Eintrag der Datendatei (also zu einem einzeiligen Datensatz) den entsprechenden Wert zurückgeben.
image

Abb. 2–1Grafische Darstellung einer einfachen Datenmenge (siehe Beispiel 2–3)

Um ehrlich zu sein: Für einen so einfachen Graphen ist das ein erheblicher Aufwand! Hieran können Sie schon etwas erkennen, was im Laufe der Zeit noch deutlicher wird: D3 ist keine Grafikbibliothek und erst recht kein Werkzeug zur Diagrammerstellung, sondern eine Bibliothek, um den DOM-Baum zu bearbeiten und dadurch Informationen grafisch darzustellen. Sie werden die ganze Zeit damit beschäftigt sein, Operationen an Teilen des DOM-Baums vorzunehmen (die Sie mit Selections auswählen). Außerdem werden Sie feststellen, dass bei D3 nicht gerade wenig Tipparbeit anfällt, da Sie Attributwerte bearbeiten und eine Zugriffsfunktion nach der anderen schreiben müssen. Andererseits ist der Code meiner Meinung nach zwar sehr wortreich, aber auch sauber und unkompliziert.

Wenn Sie ein wenig mit den Beispielen herumspielen, werden Sie womöglich noch einige weitere Überraschungen erleben. Beispielsweise ist die Funktion tsv() ziemlich wählerisch: Spalten müssen durch Tabulatoren getrennt sein, Weißraum wird nicht ignoriert, eine Kopfzeile muss vorhanden sein usw. Bei genauerer Untersuchung der Datenmenge und des Graphen werden Sie schließlich auch feststellen, dass das Diagramm nicht korrekt ist, sondern auf dem Kopf steht! Das liegt daran, dass SVG »grafische Koordinaten« verwendet, bei denen die horizontale Achse zwar wie üblich von links nach rechts, die vertikale aber von oben nach unten läuft.

Um diese ersten Eindrücke besser einordnen zu können, fahren wir mit einem zweiten Beispiel fort.

Zweites Beispiel: zwei Datenmengen

Für unser zweites Beispiel verwenden wir die Datenmenge aus Beispiel 2–4. Sie sieht zwar fast genauso harmlos aus wie die vorherige, aber bei genauerer Betrachtung werden einige zusätzliche Schwierigkeiten deutlich. Sie enthält nämlich nicht nur zwei Datenmengen (in den Spalten y1 und y2), sondern umfasst auch Datenbereiche, die unserer Aufmerksamkeit bedürfen. Im vorherigen Beispiel konnten wir die Datenwerte unmittelbar als Pixelkoordinaten verwenden, doch die Werte der neuen Datenmenge erfordern eine Transformation, bevor sie als Bildschirmkoordinaten genutzt werden können. Wir müssen also etwas mehr Arbeit aufwenden.

Beispiel 2–4: Eine kompliziertere Datenmenge (examples-multiple.tsv)

xy1y2

1.00.0010.63

3.00.0030.84

4.00.0240.56

4.50.0540.22

4.60.0620.15

5.00.1000.08

6.00.1760.20

8.00.1980.71

9.00.1990.65

Symbole und Linien darstellen

Die HTML-Seite für dieses Beispiel ist im Großen und Ganzen identisch mit derjenigen, die wir zuvor benutzt haben (Beispiel 2–2). Allerdings müssen wir die folgende Zeile ersetzen:

<script src="examples-demo1.js"></script>

Für dieses Beispiel muss sie wie folgt lauten, damit sie auf das neue Skript verweist:

<script src="examples-demo2.js"></script>

Auch der onload-Ereignishandler muss den neuen Funktionsnamen angeben:

<body onload="makeDemo2()">

Das Skript sehen Sie in Beispiel 2–5, das resultierende Diagramm in Abbildung 2–2.

image

Abb. 2–2Einfaches Diagramm der Datenmenge aus Beispiel 2–4 (siehe auch Beispiel 2–5)

Beispiel 2–5: Befehle für Abbildung 2–2

function makeDemo2() {

d3.tsv( "examples-multiple.tsv" )

.then( function( data ) {

var pxX = 600, pxY = 300;image

var scX = d3.scaleLinear()image

.domain( d3.extent(data, d => d["x"] ) )image

.range( [0, pxX] );

var scY1 = d3.scaleLinear()image

.domain(d3.extent(data, d => d["y1"] ) )

.range( [pxY, 0] );image

var scY2 = d3.scaleLinear()

.domain( d3.extent(data, d => d["y2"] ) )

.range( [pxY, 0] );

d3.select( "svg" )image

.append( "g" ).attr( "id", "ds1" )image

.selectAll( "circle" )image

.data(data).enter().append("circle")

.attr( "r", 5 ).attr( "fill", "green" )image

.attr( "cx", d => scX(d["x"]) )image

.attr( "cy", d => scY1(d["y1"]) );image

d3.select( "svg" )image

.append( "g" ).attr( "id", "ds2" )

.attr( "fill", "blue" )image

.selectAll( "circle" )image

.data(data).enter().append("circle")

.attr( "r", 5 )

.attr( "cx", d => scX(d["x"]) )

.attr( "cy", d => scY2(d["y2"]) );image

var lineMaker = d3.line()image

.x( d => scX( d["x"] ) )image

.y( d => scY1( d["y1"] ) );

d3.select( "#ds1" )image

.append( "path" )image

.attr( "fill", "none" ).attr( "stroke", "red" )

.attr( "d", lineMaker(data) );image

lineMaker.y( d => scY2( d["y2"] ) );image

d3.select( "#ds2" ) image

.append( "path" )

.attr( "fill", "none" ).attr( "stroke", "cyan" )

.attr( "d", lineMaker(data) );

// d3.select( "#ds2" ).attr( "fill", "red" );image

} );

}

  1. imageWir weisen die Abmessungen des eingebetteten SVG-Bereichs Variablen zu (px für Pixel), um später darauf verweisen zu können. Es ist natürlich auch möglich, die Größenangabe aus dem HTML-Dokument herauszunehmen und sie stattdessen über JavaScript festzulegen. (Probieren Sie es aus!)
  2. imageIn D3 gibt es Skalierungsobjekte, die den eingegebenen Definitionsbereich auf den ausgegebenen Wertebereich abbilden. Hier verwenden wir lineare Skalierungen, um die Datenwerte aus ihrem natürlichen Definitionsbereich in den Wertebereich der Pixel im Graphen zu übertragen. Die Bibliothek enthält jedoch auch logarithmische und exponentielle Skalierungen und sogar solche, die die Zahlenbereiche auf Farben abbilden, um Falschfarbendiagramme und Heatmaps zu erstellen (siehe Kapitel 7 und 8). Skalierungen sind Funktionsobjekte: Sie werden mit einem Wert des Definitionsbereichs aufgerufen und geben den skalierten Wert zurück.
  3. imageSowohl der Definitions- als auch der Wertebereich werden als zweielementiges Array angegeben. Bei d3.extent() handelt es sich um eine Komfortfunktion, die ein Array (von Objekten) entgegennimmt und den größten und den kleinsten Wert als zweielementiges Array zurückgibt (siehe Kapitel 10). Um den gewünschten Wert aus den Objekten im Eingabearray zu entnehmen, müssen wir eine Zugriffsfunktion bereitstellen (ähnlich wie im letzten Schritt des vorherigen Beispiels). Um uns Tipparbeit zu ersparen, nutzen wir hier (und in den meisten folgenden Beispielen!) die Pfeilfunktionen von JavaScript (siehe Anhang C).
  4. imageDa die drei Spalten in der Datenmenge unterschiedliche Wertebereiche aufweisen, brauchen wir drei Skalierungsobjekte, eines für jede Spalte.
  5. imageUm die auf dem Kopf stehende Ausrichtung des SVG-Koordinatensystems auszugleichen, kehren wir für die vertikale Achse den Ausgabebereich in der Definition des Skalierungsobjekts um.
  6. imageWir wählen das <svg>-Element aus, um Symbole für die erste Datenmenge hinzuzufügen.
  7. imageDieser Vorgang ist neu: Bevor wir irgendwelche Elemente des Graphen hinzufügen, hängen wir ein <g>-Element an und geben ihm einen eindeutigen Bezeichner mit. Das endgültige Element sieht wie folgt aus:

    <g id="ds1">...</g>

    Das SVG-Element <g> sorgt für eine logische Gruppierung, sodass wir auf alle Symbole für die erste Datenmenge gleichzeitig verweisen und sie von denen der zweiten Datenmenge unterscheiden können (siehe Anhang B und den Kasten »Das praktische <g>-Element« auf S. 115).

  8. imageWie zuvor erstellen wir mit selectAll( "circle" ) eine leere Platzhaltersammlung. Die <circle>-Elemente werden als Kinder des gerade hinzugefügten <g>-Elements erstellt.
  9. imageFeste Formatierungen werden unmittelbar auf die einzelnen <circle>-Elemente angewendet.
  10. imageEine Zugriffsfunktion wählt die geeignete Spalte für die horizontale Achse aus. Beachten Sie, dass der Skalierungsoperator auf die Daten angewendet wird, bevor sie zurückgegeben werden.
  11. imageEine Zugriffsfunktion wählt die geeignete Spalte für die erste Datenmenge aus. Auch hier erfolgt wieder eine passende Skalierung.
  12. imageDas schon bekannte Verfahren wird erneut angewandt, um Elemente für die zweite Datenmenge hinzuzufügen. Beachten Sie aber die Unterschiede!
  13. imageFür die zweite Datenmenge wird die Füllfarbe mit dem Attribut fill des <g>-Elements angegeben. Dieses Erscheinungsbild werden die Kinder erben. Durch die Definition im Elternelement können wir das Aussehen aller Kinder später in einem Rutsch ändern.
  14. imageAbermals wird eine leere Sammlung für die neu hinzugefügten <circle>-Elemente erstellt. Hier ist das <g>-Element zu mehr als nur zu unserer Bequemlichkeit da: Wenn wir an dieser Stelle selectAll( "circle" ) für das <svg>-Element aufriefen, würden wir keine leere Sammlung, sondern die <circle>-Elemente der ersten Datenmenge erhalten. Anstatt neue Elemente hinzuzufügen, würden wir die vorhandenen bearbeiten und die erste Datenmenge mit der zweiten überschreiben. Das <g>-Element erlaubt es uns, klar zwischen den Elementen und ihrer Zuordnung zu Datenmengen zu unterscheiden. (Das wird noch deutlicher, wenn wir uns in Kapitel 3 eingehender mit der Selection-API von D3 beschäftigen.)
  15. imageDie Zugriffsfunktion wählt jetzt eine geeignete Spalte für die zweite Datenmenge aus.
  16. imageUm die beiden Datenmengen besser unterscheiden zu können, wollen wir die Symbole, die zu einer Menge gehören, jeweils durch gerade Linien verbinden. Diese Linien sind komplizierter als die Symbole, da jedes Liniensegment von zwei aufeinanderfolgenden Datenpunkten abhängt. D3 kommt uns dabei aber zu Hilfe: Die Factory-Funktion d3.line() gibt ein Funktionsobjekt zurück, das bei Übergabe einer Datenmenge einen für das Attribut d des SVG-Elements <path> geeigneten String produziert. (Mehr über das Element <path> und seine Syntax lernen Sie in Anhang B.)
  17. imageDer Liniengenerator erfordert eine Zugriffsfunktion, um die horizontalen und vertikalen Koordinaten für jeden Datenpunkt zu entnehmen.
  18. imageDas <g>-Element der ersten Datenmenge wird anhand des Werts seines id-Attributs ausgewählt. Ein ID-Selektorstring besteht aus dem Nummernzeichen (#), gefolgt von dem Attributwert.
  19. imageEin <path>-Element wird als Kind der Gruppe <g> der ersten Datenmenge hinzugefügt ...
  20. image... und sein Attribut d wird durch den Aufruf des Liniengenerators für die Datenmenge festgelegt.
  21. imageAnstatt einen komplett neuen Liniengenerator zu erstellen, verwenden wir den vorhandenen weiter. Dabei geben wir eine neue Zugriffsfunktion an, diesmal für die zweite Datenmenge.
  22. imageEin <path>-Element für die zweite Datenmenge wird an der passenden Stelle im SVG-Baum angehängt und gefüllt.
  23. imageDa der Füllungsstil für die Symbole der zweiten Datenmenge im Elternelement definiert wurde (nicht in den einzelnen <circle>-Elementen), ist es möglich, ihn in einer einzigen Operation zu ändern. Wenn Sie die Auskommentierung dieser Zeile aufheben, werden alle Kreise für die zweite Datenmenge rot dargestellt. Nur Optionen für das Erscheinungsbild werden vom Elternelement geerbt. Dagegen ist es nicht möglich, etwa den Radius aller Kreise auf diese Weise zu ändern oder die Kreise in Rechtecke umzuwandeln. Für solche Operationen müssen alle betroffenen Elemente einzeln angefasst werden.