Anzeige
Anzeige
Anzeige
Anzeige
Anzeige
Anzeige
Lesedauer 9 Min.

Webkartenkönig D3js

Eine interaktive Deutschlandkarte erstellen und mit Daten hinterlegen.
© Autor
Wer die Besucher seiner Webseite mit dynamischen interaktiven Landkarten im Webbrowser beeindrucken möchte, sollte sich mit Data-Driven Documents (kurz D3) beschäftigen. D3js – siehe Bild 1 – ist eine JavaScript-Bibliothek, die es erlaubt, die DOM-Elemente von Webseiten zu verändern. Gesteuert werden die Änderungen anhand von Daten, die in CSV- oder JSON-Dateien vorliegen [1]. Im Zusammenspiel von D3js mit den Webtechnologien HTML5, SVG und CSS ergeben sich Möglichkeiten, dynamische und interak­tive Landkarten im Webbrowser darzustellen, ohne dafür ein proprietäres Framework zu benutzen. Bild 2 zeigt einen Einblick in die Möglichkeiten von D3js.
Logoder Bibliothek D3js(Bild 1) © Autor
Beispielemöglicher Karten(Bild 2) © Autor
Die Bibliothek kann mithilfe der nachfolgenden Anweisung direkt online angebunden werden:

<span class="hljs-tag">&lt;<span class="hljs-name">script</span> <span class="hljs-attr">src</span>=<span class="hljs-string">"https://d3js.org/d3.v5.min.js"</span>&gt;</span><span class="undefined"></span><span class="hljs-tag">&lt;/<span class="hljs-name">script</span>&gt;</span> 
 
Alternativ laden Sie sie von der Herstellerseite als ZIP-Datei und entpacken sie in das Verzeichnis der Internetseite.

Das Beispielprojekt

Ziel dieses Workshops ist es, eine Karte mit den Arbeitslosenquoten der deutschen Bundesländer zu erstellen. Das fertige Ergebnis sehen Sie in Bild 3 und können es unter [2] direkt ausprobieren.
Bundesländermit Arbeitslosenquoten im Browser(Bild 3) © Autor
Zum besseren Verständnis von D3js bleiben im ersten Beispiel die für die Darstellung von Karten erforderlichen SVG-Elemente und die asynchrone Verarbeitung der einzulesenden Daten zunächst außen vor. Gestartet wird vorerst damit, in einer HTML-Datei eine mit dem <ul>-Element erstellte Aufzählungsliste unter Verwendung von D3js mit Einträgen zu füllen. Der dafür erforderliche HTML- und JavaScript-Code sieht so aus:

<span class="hljs-tag">&lt;<span class="hljs-name">body</span>&gt;</span> 
  <span class="hljs-tag">&lt;<span class="hljs-name">script</span> <span class="hljs-attr">src</span>=<span class="hljs-string">"./d3.min.js"</span>&gt;</span><span class="undefined"></span><span class="hljs-tag">&lt;/<span class="hljs-name">script</span>&gt;</span> 
  Was D3-gesteuerte Diagramme brauchen: 
  <span class="hljs-tag">&lt;<span class="hljs-name">ul</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">ul</span>&gt;</span> 
  <span class="hljs-tag">&lt;<span class="hljs-name">script</span>&gt;</span><span class="javascript"> </span>
<span class="javascript">    d3.select(<span class="hljs-string">"ul"</span>) </span>
<span class="javascript">      .selectAll(<span class="hljs-string">"li"</span>) </span>
<span class="javascript">      .data([<span class="hljs-string">"HTML"</span>,<span class="hljs-string">"SVG"</span>,<span class="hljs-string">"CSS"</span>]) </span>
<span class="javascript">      .join(<span class="hljs-string">"li"</span>) </span>
<span class="javascript">      .text(<span class="hljs-function"><span class="hljs-params">d</span> =&gt;</span> d); </span>
<span class="javascript">  </span><span class="hljs-tag">&lt;/<span class="hljs-name">script</span>&gt;</span> 
<span class="hljs-tag">&lt;/<span class="hljs-name">body</span>&gt;</span> 
 
Zunächst wird in der HTML-Datei das <ul>-Element definiert, welches im DOM-Baum des Dokuments als Eltern-Element für die einzelnen noch zu erzeugenden <li>-Kind-Elemente der Aufzählungsliste dient.Mit der Anweisung d3.select(”ul”) wird das Eltern-Element für die nachfolgenden Operationen festgelegt. Die Anweisung selectAll(”li”) ist zunächst etwas verwirrend, da im DOM-Baum etwas ausgewählt wird, was zu Beginn noch gar nicht vorhanden ist. Das hängt damit zusammen, dass der D3js-Bibliothek nicht mitgeteilt wird, wie etwas getan werden soll, sondern was man sich als Ergebnis wünscht. Damit die Bibliothek weiß, welche Daten es mit welchen DOM-Elementen synchronisieren soll, gibt es die data- und join-Aufrufe. Während die data-Methode ein Array mit den einzufügenden Aufzählungselementen enthält, gibt der join-Aufruf an, dass die <li>-Kind-Elemente im DOM-Baum mit den einzelnen Elementen des Daten-Arrays gefüllt werden sollen. Der dem join-Aufruf nachgeordneten text-Methode wird als Callback-Funktion beziehungsweise Lambda-Ausdruck das jeweilige Element des Daten-Arrays zugewiesen. Bild 4 zeigt das Ergebnis im Browser.
Von D3js erzeugteAufzählungsliste(Bild 4) © Autor
Bevor das Erstellen von Landkarten mit SVG-Elementen klappt, ist es erforderlich, dass sich Daten auch von außerhalb des Quelltextes zuführen lassen. Ziel dieses Workshops ist ­eine Deutschlandkarte, welche neben den Bundesländern auch die Millionenstädte Berlin, Hamburg, München und Köln zeigt. Dafür wird eine GeoJSON-formatierte Datei [3] benötigt.

GeoJSON-Daten und deren Projektion

Neben den eigentlichen GPS-Koordinaten, also den Länderumrissen als Sammlungen von Punkten (Polygone) gemäß der Simple-Feature-Access-Spezifikation [4] mit Längen- und Breitengrad notiert, enthält solch eine Datei abhängig vom jeweiligen Herausgeber auch noch Metainformationen wie etwa den Namen des Landes oder den Landestyp. Bild 5 zeigt einen Auszug der verwendeten Geo-JSON-Datei.
GeoJSON-Dateimit den Koordinaten der Bundesländer(Bild 5) © Autor
Grundsätzlich gibt es verschiedene Möglichkeiten, die Oberfläche einer Kugel auf eine zweidimensionale Karte zu projizieren. Eine absolut längen-, flächen- und winkeltreue Kartenprojektionsmethode gibt es allerdings nicht. Insbesondere bei der Abbildung größerer Erdteile in der Nähe von Nord- und Südpol kommt es teilweise zu erheblichen Verzerrungen. Daher stellt die Bibliothek mehrere Funktionen zur Projektion zur Verfügung, wie d3.geoNaturalEarth1 oder auch d3.geoMercator. Weil diese Projektionen dazu geeignet sind, die gesamte Erdkarte darzustellen, würde Deutschland auf der SVG-Projektionsfläche nicht mittig erscheinen. Daher wird mit center([10,51]) der Längen- und Breitengrad der Mitte Deutschlands angegeben und noch eine Skalierung hinzugefügt. Der komplette Ausdruck, um eine Instanz der Projek­tions­funktion zu erzeugen, lautet daher:

const projection = 
  d3.geoMercator().center(
  [<span class="hljs-number">10</span>,<span class="hljs-number">51</span>]).scale(<span class="hljs-number">2000</span>); 
 

Die Karte erstellen

Die eigentliche Karte wird mit SVG-Path-Elementen dargestellt. Diese besitzen ein Attribut d (für data), die als Attributwerte die Zeichenkommandos für die komma- oder leerzeichengetrennten Koordinaten innerhalb des SVG-Elements enthalten. Hier ein simples Beispiel:

<span class="hljs-tag">&lt;<span class="hljs-name">path</span> <span class="hljs-attr">d</span>=<span class="hljs-string">"M50,50L100,100"</span> <span class="hljs-attr">stroke</span>=<span class="hljs-string">"black"</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">path</span>&gt;</span> 
 
Dieser Pfad zeichnet eine Linie von links oben nach rechts unten. Da die Konturen eines Bundeslandes in der Regel mit einigen Hundert solcher Kommandos gezeichnet werden, ist es äußerst praktisch, dass die Bibliothek D3js mit folgendem Aufruf eine Instanz einer Pfad-Generatorfunktion erzeugen und nach dem erfolgreichen Laden der GeoJSON-Datei verwenden kann:

<span class="hljs-attribute">const pathGenerator</span> = 
  d3.geoPath().projection(projection); 
Damit später im Rahmen des Zoomens und Schwenkens der Karte alle Pfadelemente, Markierungen und Texte gemeinsam verschoben werden können, hat sich folgendes Verfahren als nützlich erwiesen: Alle Elemente werden nicht direkt dem SVG-Element unterstellt, sondern in einem SVG-Group-Element <g> gesammelt, welches sozusagen dazwischengeschaltet wird. Das Group-Element hat leider keine x- und y-Attribute, weshalb es bei Bedarf per transform-Attribut mit der translate-Funktion verschoben werden muss.

Asynchroner Zugriff und Promise

Wie bereits erwähnt erfolgt der Zugriff über die Bibliothek D3js asynchron, sodass die Programmlogik umgestaltet werden muss. So darf erst dann mit einem select-Aufruf auf die DOM-Knoten zugegriffen werden, wenn die externen Daten erfolgreich eingelesen worden sind. Da JavaScript aus Sicherheitsgründen nicht einfach auf lokale Dateien zugreifen darf, muss die zu importierende GeoJSON-Datei auf einem Webserver liegen (extern oder lokal). Weil die Antwortzeiten externer Server schlecht vorhersagbar sind, stellen die D3js-Entwickler den Zugriff auf die Datei als sogenanntes Promise zur Verfügung. In JavaScript ist ein Promise ein Stellvertreter-Objekt für einen Wert, der noch nicht bekannt ist. Das Ergebnis ist über Callback-Funktionen abrufbar, welche über die then- und catch-Methoden des Promise-Objekts registriert werden. Listing 1 zeigt den erforderlichen JavaScript-Code ausschnittsweise.
Listing 1: GeoJSON-Daten einlesen und verarbeiten
&lt;span class="hljs-keyword"&gt;const&lt;/span&gt; g = svg.append(&lt;span class="hljs-string"&gt;'g'&lt;/span&gt;); &lt;br/&gt;&lt;span class="hljs-built_in"&gt;Promise&lt;/span&gt;(d3.json(&lt;span class="hljs-string"&gt;'http://www.cssenior.de/D3jsMap/&lt;/span&gt;&lt;br/&gt;&lt;span class="hljs-string"&gt;  4_niedrig.geo.json'&lt;/span&gt;) &lt;br/&gt;.then(&lt;span class="hljs-function"&gt;&lt;span class="hljs-params"&gt;data&lt;/span&gt; =&amp;gt;&lt;/span&gt; { &lt;br/&gt;  &lt;span class="hljs-keyword"&gt;const&lt;/span&gt; paths = g.selectAll(&lt;span class="hljs-string"&gt;'path'&lt;/span&gt;) &lt;br/&gt;    .data(data.features) &lt;br/&gt;    .join(&lt;span class="hljs-string"&gt;'path'&lt;/span&gt;) &lt;br/&gt;      .attr(&lt;span class="hljs-string"&gt;'class'&lt;/span&gt;, &lt;span class="hljs-string"&gt;'country'&lt;/span&gt;) &lt;br/&gt;      .attr(&lt;span class="hljs-string"&gt;'d'&lt;/span&gt;, d =&amp;gt; pathGenerator(d)) &lt;br/&gt;      .style(&lt;span class="hljs-string"&gt;'fill'&lt;/span&gt;, &lt;span class="hljs-string"&gt;'GhostWhite'&lt;/span&gt;) &lt;br/&gt;      .append(&lt;span class="hljs-string"&gt;'title'&lt;/span&gt;) &lt;br/&gt;        .text(&lt;span class="hljs-function"&gt;&lt;span class="hljs-params"&gt;d&lt;/span&gt; =&amp;gt;&lt;/span&gt; d.properties.name) &lt;br/&gt;&lt;br/&gt;}).catch(&lt;span class="hljs-function"&gt;&lt;span class="hljs-params"&gt;onRejected&lt;/span&gt; =&amp;gt;&lt;/span&gt; { &lt;br/&gt;  alert(&lt;span class="hljs-string"&gt;'GeoJSON-Datei fehlt.'&lt;/span&gt;); &lt;br/&gt;  &lt;span class="hljs-built_in"&gt;console&lt;/span&gt;.error(onRejected.message); &lt;br/&gt;}));  
Anders als im Beispiel zu Beginn wird hier – sofern das asynchrone Laden der GeoJSON-Datei erfolgreich verläuft – die Verknüpfung zu den Daten über das Objekt data.features hergestellt. In Verbindung mit dem join-Aufruf werden für jedes Land die SVG-Path-Elemente erstellt. Jedes der 16 Länder wird durch ein Objekt vom Typ feature repräsentiert und enthält die in Bild 5 gezeigten Unterobjekte. Die wichtigsten Daten in Form der GPS-Koordinaten der einzelnen Landesgrenzen werden dann mit nachfolgender Anweisung per Method Chaining der bereits erwähnten Pfad-Generatorfunk­tion zugeführt:

.attr(<span class="hljs-string">'d'</span>, d =&gt; pathGenerator(d)) 
 
Damit auf mausgesteuerten Endgeräten die Landesbezeichnung als Tooltipp-Text angezeigt werden kann, wenn der Mauszeiger über einem Land schwebt, muss dem jeweiligen path-Element noch ein title-Element mit dem Landesnamen als Elementinhalt übermittelt werden.

Städte als Orientierung

Zur besseren Orientierung gibt es noch ein Array aus Objekten mit Namen und Koordinaten der vier deutschen Millionenstädte, welches dafür verwendet wird deren Position auf der Karte zu markieren. Die GPS-Koordinaten der Millionenstädte liegen innerhalb der vier Objekte als Array mit zwei Elementen für den Längen- und Breitengrad vor. Auch hier hilft die Instanz der Projektionsfunktion projection sehr, da sie die x- und y-Koordinaten für die Position auf dem SVG-Element als Array mit zwei Elementen zurückliefert. Listing 2 zeigt den dafür nötigen Quellcode, der am Ende der then-Methode des Promise-Objekts eingefügt werden kann.
Listing 2: Orientierung anhand der Millionenstädte
&lt;span class="hljs-type"&gt;MillionCities&lt;/span&gt; = [ &lt;br/&gt;  {&lt;span class="hljs-type"&gt;Stadt&lt;/span&gt;: &lt;span class="hljs-string"&gt;"Berlin"&lt;/span&gt;, &lt;span class="hljs-type"&gt;Koordinaten&lt;/span&gt;: &lt;br/&gt;    [&lt;span class="hljs-number"&gt;13.408333&lt;/span&gt;,&lt;span class="hljs-number"&gt;52.518611&lt;/span&gt;], &lt;span class="hljs-type"&gt;Radius&lt;/span&gt;: &lt;span class="hljs-number"&gt;4&lt;/span&gt;}, &lt;br/&gt;  {&lt;span class="hljs-type"&gt;Stadt&lt;/span&gt;: &lt;span class="hljs-string"&gt;"Hamburg"&lt;/span&gt;, &lt;span class="hljs-type"&gt;Koordinaten&lt;/span&gt;: &lt;br/&gt;    [&lt;span class="hljs-number"&gt;9.986079&lt;/span&gt;,&lt;span class="hljs-number"&gt;53.554038&lt;/span&gt;], &lt;span class="hljs-type"&gt;Radius&lt;/span&gt;: &lt;span class="hljs-number"&gt;3&lt;/span&gt;}, &lt;br/&gt;  {&lt;span class="hljs-type"&gt;Stadt&lt;/span&gt;: &lt;span class="hljs-string"&gt;"München"&lt;/span&gt;, &lt;span class="hljs-type"&gt;Koordinaten&lt;/span&gt;: &lt;br/&gt;    [&lt;span class="hljs-number"&gt;11.575556&lt;/span&gt;,&lt;span class="hljs-number"&gt;48.137222&lt;/span&gt;], &lt;span class="hljs-type"&gt;Radius&lt;/span&gt;: &lt;span class="hljs-number"&gt;3&lt;/span&gt;}, &lt;br/&gt;  {&lt;span class="hljs-type"&gt;Stadt&lt;/span&gt;: &lt;span class="hljs-string"&gt;"Köln"&lt;/span&gt;, &lt;span class="hljs-type"&gt;Koordinaten&lt;/span&gt;: &lt;br/&gt;    [&lt;span class="hljs-number"&gt;6.956944&lt;/span&gt;,&lt;span class="hljs-number"&gt;50.938056&lt;/span&gt;], &lt;span class="hljs-type"&gt;Radius&lt;/span&gt;: &lt;span class="hljs-number"&gt;2&lt;/span&gt;} &lt;br/&gt;]; &lt;br/&gt;g.selectAll('circle') &lt;br/&gt;  .&lt;span class="hljs-class"&gt;&lt;span class="hljs-keyword"&gt;data&lt;/span&gt;(&lt;span class="hljs-type"&gt;MillionCities&lt;/span&gt;) &lt;/span&gt;&lt;br/&gt;&lt;span class="hljs-class"&gt;  .join('&lt;span class="hljs-title"&gt;circle'&lt;/span&gt;) &lt;/span&gt;&lt;br/&gt;&lt;span class="hljs-class"&gt;    .attr('&lt;span class="hljs-title"&gt;class'&lt;/span&gt;, '&lt;span class="hljs-type"&gt;MillionCity&lt;/span&gt;').attr(&lt;/span&gt;&lt;br/&gt;&lt;span class="hljs-class"&gt;      '&lt;span class="hljs-title"&gt;r'&lt;/span&gt;, &lt;span class="hljs-title"&gt;d&lt;/span&gt; =&amp;gt; &lt;span class="hljs-title"&gt;d&lt;/span&gt;.&lt;span class="hljs-type"&gt;Radius&lt;/span&gt;) &lt;/span&gt;&lt;br/&gt;&lt;span class="hljs-class"&gt;    .attr('&lt;span class="hljs-title"&gt;cx'&lt;/span&gt;, &lt;span class="hljs-title"&gt;d&lt;/span&gt; =&amp;gt; &lt;span class="hljs-title"&gt;projection&lt;/span&gt;(&lt;/span&gt;&lt;br/&gt;&lt;span class="hljs-class"&gt;      &lt;span class="hljs-title"&gt;d&lt;/span&gt;.&lt;span class="hljs-type"&gt;Koordinaten&lt;/span&gt;)[0]).attr(&lt;/span&gt;&lt;br/&gt;&lt;span class="hljs-class"&gt;      '&lt;span class="hljs-title"&gt;cy'&lt;/span&gt;, &lt;span class="hljs-title"&gt;d&lt;/span&gt; =&amp;gt; &lt;span class="hljs-title"&gt;projection&lt;/span&gt;(&lt;span class="hljs-title"&gt;d&lt;/span&gt;.&lt;span class="hljs-type"&gt;Koordinaten&lt;/span&gt;)[1]) &lt;/span&gt;&lt;br/&gt;&lt;span class="hljs-class"&gt;    .append('&lt;span class="hljs-title"&gt;title'&lt;/span&gt;).text(&lt;span class="hljs-title"&gt;d&lt;/span&gt; =&amp;gt; &lt;span class="hljs-title"&gt;d&lt;/span&gt;.&lt;span class="hljs-type"&gt;Stadt&lt;/span&gt;); &lt;/span&gt;&lt;br/&gt;&lt;br/&gt;&lt;span class="hljs-class"&gt;g.selectAll('&lt;span class="hljs-title"&gt;text'&lt;/span&gt;) &lt;/span&gt;&lt;br/&gt;&lt;span class="hljs-class"&gt;  .&lt;span class="hljs-keyword"&gt;data&lt;/span&gt;(&lt;span class="hljs-type"&gt;MillionCities&lt;/span&gt;) &lt;/span&gt;&lt;br/&gt;&lt;span class="hljs-class"&gt;  .join('&lt;span class="hljs-title"&gt;text'&lt;/span&gt;) &lt;/span&gt;&lt;br/&gt;&lt;span class="hljs-class"&gt;    .attr('&lt;span class="hljs-title"&gt;x'&lt;/span&gt;, &lt;span class="hljs-title"&gt;d&lt;/span&gt; =&amp;gt; &lt;span class="hljs-title"&gt;projection&lt;/span&gt;(&lt;/span&gt;&lt;br/&gt;&lt;span class="hljs-class"&gt;      &lt;span class="hljs-title"&gt;d&lt;/span&gt;.&lt;span class="hljs-type"&gt;Koordinaten&lt;/span&gt;)[0]).attr(&lt;/span&gt;&lt;br/&gt;&lt;span class="hljs-class"&gt;      '&lt;span class="hljs-title"&gt;y'&lt;/span&gt;, &lt;span class="hljs-title"&gt;d&lt;/span&gt; =&amp;gt; &lt;span class="hljs-title"&gt;projection&lt;/span&gt;(&lt;span class="hljs-title"&gt;d&lt;/span&gt;.&lt;span class="hljs-type"&gt;Koordinaten&lt;/span&gt;)[1]) &lt;/span&gt;&lt;br/&gt;&lt;span class="hljs-class"&gt;    .attr('&lt;span class="hljs-title"&gt;font&lt;/span&gt;-&lt;span class="hljs-title"&gt;family'&lt;/span&gt;, '&lt;span class="hljs-type"&gt;Arial&lt;/span&gt; &lt;span class="hljs-type"&gt;Narrow&lt;/span&gt;').attr(&lt;/span&gt;&lt;br/&gt;&lt;span class="hljs-class"&gt;      '&lt;span class="hljs-title"&gt;font&lt;/span&gt;-&lt;span class="hljs-title"&gt;size'&lt;/span&gt;,'0.8&lt;span class="hljs-title"&gt;em'&lt;/span&gt;).text(&lt;span class="hljs-title"&gt;d&lt;/span&gt; =&amp;gt; &lt;span class="hljs-title"&gt;d&lt;/span&gt;.&lt;span class="hljs-type"&gt;Stadt&lt;/span&gt;); &lt;/span&gt; 
Was jetzt noch fehlt, sind die Arbeitslosenquoten der Bundesländer. Diese sollen von einer TSV-Datei geliefert werden, also einer schlichten Textdatei mit durch Tabulatoren getrennten Spalten für die einzelnen Felder. Die ersten Zeilen der Datei AlQuoten.tsv lauten daher wie folgt:

nr  name              quote 
<span class="hljs-number">0</span>  Baden-Württemberg  <span class="hljs-number">4.4</span> 
<span class="hljs-number">1</span>  Bayern            <span class="hljs-number">3.9</span> 
<span class="hljs-number">2</span>  Berlin            <span class="hljs-number">10.8</span> 
 
Wichtig hierbei ist, dass die TSV-Datei alphabetisch nach Bundesländernamen sortiert ist, weil auch die GeoJSON-Datei, wie aus Bild 5 ersichtlich ist, die Eigenschaften und Geometriedaten der Länder in derselben Reihenfolge auflistet. Selbstverständlich sollte bei der Pflege der Datei AlQuoten.tsv streng auf deren referenzielle Integrität geachtet werden, damit zu jedem Bundesland die richtige Quote zugeordnet werden kann.Die referenzielle Integrität ist auch der Grund dafür, dass das für das Einlesen der GeoJSON-Datei verwendete Promise-Objekt in seinem Aufruf etwas verändert werden muss. Eine Weiterverarbeitung der asynchron gelieferten Daten mit dem Promise-Objekt ergibt erst dann Sinn, wenn beide Datenströme vollständig beim Client angekommen sind. Das Promise-Objekt stellt dies sicher, indem die Methode Promise.all mit einem Array auch den TSV- und JSON-Funktionsaufrufen als Parameter übergeben wird. Logischerweise benötigt auch die then-Methode ein Array mit zwei Ergebnisobjekten als Parameter. Weil die Arbeitslosenquoten aber ebenso wie die Geometriedaten alphabetisch sortiert ankommen, genügt für deren Weiterverarbeitung gleich zu Beginn der then-Methode ein einfaches eindimensionales Array, das mit den Programmzeilen aus Listing 3 gefüllt wird.
Listing 3: Programmstruktur und Promise-Aufrufe
Promise.all([ &lt;br/&gt;  d3.tsv(&lt;span class="hljs-string"&gt;'http://www.cssenior.de/D3jsMap/&lt;/span&gt;&lt;br/&gt;&lt;span class="hljs-string"&gt;    AlQuoten.tsv'&lt;/span&gt;), &lt;br/&gt;  d3.json(&lt;span class="hljs-string"&gt;'http://www.cssenior.de/D3jsMap/&lt;/span&gt;&lt;br/&gt;&lt;span class="hljs-string"&gt;    4_niedrig.geo.json'&lt;/span&gt;) &lt;br/&gt;]).&lt;span class="hljs-keyword"&gt;then&lt;/span&gt;(&lt;span class="hljs-function"&gt;&lt;span class="hljs-params"&gt;([quoten,data])&lt;/span&gt; =&amp;gt;&lt;/span&gt; { &lt;br/&gt;  quoten.forEach(d =&amp;gt; { &lt;br/&gt;    d.nr = +d.nr; &lt;br/&gt;    d.quote = +(d.quote.replace(&lt;span class="hljs-string"&gt;","&lt;/span&gt;, &lt;span class="hljs-string"&gt;"."&lt;/span&gt;)); &lt;br/&gt;    AlQuoten.push(d.quote); &lt;br/&gt;  }); &lt;br/&gt;  &lt;span class="hljs-regexp"&gt;//&lt;/span&gt; ... &lt;br/&gt;  &lt;span class="hljs-regexp"&gt;//&lt;/span&gt; ... weiter wie bekannt &lt;br/&gt;  &lt;span class="hljs-regexp"&gt;//&lt;/span&gt; ... &lt;br/&gt;}).&lt;span class="hljs-keyword"&gt;catch&lt;/span&gt;(onRejected =&amp;gt; { &lt;br/&gt;  &lt;span class="hljs-keyword"&gt;if&lt;/span&gt; (onRejected.message === &lt;span class="hljs-string"&gt;"404 Not Found"&lt;/span&gt;) { &lt;br/&gt;    alert(&lt;span class="hljs-string"&gt;"TSV- oder GeoJSON-Datei fehlt."&lt;/span&gt;); &lt;br/&gt;  } &lt;span class="hljs-keyword"&gt;else&lt;/span&gt; { &lt;br/&gt;    &lt;span class="hljs-built_in"&gt;console&lt;/span&gt;.error(onRejected.sourceURL + &lt;br/&gt;      &lt;span class="hljs-string"&gt;" Zeile: "&lt;/span&gt; + onRejected.line + &lt;br/&gt;      &lt;span class="hljs-string"&gt;" Spalte: "&lt;/span&gt; + onRejected.column + &lt;span class="hljs-string"&gt;": "&lt;/span&gt; + &lt;br/&gt;      onRejected.message); &lt;br/&gt;    alert(&lt;span class="hljs-string"&gt;"Allgemeiner Fehler, bitte in der &lt;/span&gt;&lt;br/&gt;&lt;span class="hljs-string"&gt;      JavaScript-Konsole nachsehen."&lt;/span&gt;); &lt;br/&gt;  } &lt;br/&gt;}); &lt;span class="hljs-regexp"&gt;//&lt;/span&gt; Ende Promise.all  

Bundesländer einfärben

Um die einzelnen Bundesländer abhängig von ihrer Arbeitslosenquote zu färben, sollte man sich darüber klar werden, dass die Arbeitslosenquote quasi die x-Achse eines (fiktiven) Balkendiagramms darstellt, auch wenn sie in der Karte nicht sofort als solche erkennbar ist. Jede Achse hat auch eine Skalierung. Die wichtigste Aufgabe der Skalierung ist daher folgende: Abstrakte Dimensionen von Daten werden auf eine sichtbare Variable wie eine bestimmte Balkenlänge oder Färbung abgebildet. Typischerweise denken wir bei Dimensionen an räumliche Dinge, etwa eine Position im dreidimensionalen Raum. Aber eine abstrakte Dimension muss nicht räumlich sein, sie kann auch nominell sein, wie die Ländernamen. Trotzdem muss man sich im Beispiel eines Balkendiagramms Gedanken um die Skalierung der y-Achse machen, da eine größere Anzahl an Balken auf der y-Achse niedrigere Balkenhöhen und -abstände zur Folge haben muss. Auch wenn es bei einer farblichen Codierung der x-Achse nicht direkt um räumliche Dimensionen geht, so ist dies indirekt dennoch so: Alle Farben eines Farbmodells, die durch eine farbgebende Methode tatsächlich ausgegeben werden können, werden im dreidimensionalen Farbraum dargestellt. Jede farbgebende Methode (Drucker, Monitore, Lackierung) hat ihren eigenen Farbraum.Der Farbraum einer farbgebenden Methode umfasst möglichst alle Farben, die innerhalb des Farbmodells darstellbar sind. Zwangsläufig hat jede farbgebende Methode nur begrenzte Möglichkeiten: So hat jeder Monitor eine maximale Helligkeit. Computerspieler kommen beispielsweise damit in Berührung, wenn sie zu Beginn des Spiels gebeten werden, eine Kalibrierung durchzuführen, indem sie ihr System so einstellen, dass sie möglichst alle abgebildeten Grautöne des eingeblendeten Farbbalkens noch unterscheiden können.Deshalb gliedert sich die Skalierung jeder Achse in zwei Bestandteile:
  • die Domain, welche alle darzustellenden abstrakten Daten (quantitativ oder namentlich) enthält,
  • die Range, welche die visuelle Variable (Position auf der Achse, Balkenlänge, verfügbarer Farbraum) enthält.
Das Balkendiagramm in Bild 6 verdeutlicht diesen Zusammenhang, indem die Balken mit der höchsten Arbeitslosenquote die größte Balkenlänge und die stärkste Blaufärbung beziehungsweise Farbsättigung aufweisen.
Arbeitslosenquotenals quotenabhängig gefärbtes Balkendiagramm(Bild 6) © Autor
Die Bibliothek D3js hat für diese Zwecke mehrere Farbschemata definiert, welche die darzustellende Range repräsentieren; in diesem Fall wurde d3.schemeBlues verwendet, welches neun Abstufungen bietet.Zulässige Werte für die Farbabstufungen liegen im Bereich zwischen drei und neun. Die Funktion zur Erzeugung der Farbskalierungs-Instanz muss auch mit den Minimal- und Maximalwerten der auf der y-Achse befindlichen nominellen Dimension versorgt werden, also der niedrigsten und höchsten Arbeitslosenquote aller Bundesländer, welche die Domain bilden:

<span class="hljs-symbol">const</span> color = 
  <span class="hljs-built_in">d3</span>.scaleQuantize( 
    [<span class="hljs-built_in">d3</span>.min(AlQuoten), <span class="hljs-built_in">d3</span>.max(AlQuoten)], 
    <span class="hljs-built_in">d3</span>.schemeBlues[<span class="hljs-number">9</span>])<span class="hljs-comment">; </span>
 
Nun kann die Funktion der Farbskalierungs-Instanz an der entsprechenden Stelle in Listing 1 mit der jeweiligen Arbeitslosenquote aus dem bereits gefüllten eindimensionalen Array AlQuoten verwendet werden, weil das Stil-Attribut fill der SVG-Pfad-Elemente nicht nur statische Farbnamen als Attributwerte empfangen, sondern auch Funktionen als Parameter verwerten kann:

.style(<span class="hljs-string">'fill'</span>, <span class="hljs-function"><span class="hljs-params">(d,i)</span> =&gt;</span> color(AlQuoten[i])) 
 
Um dem Tooltipp mit dem Bundesland-Namen noch die Arbeitslosenquote mitzugeben, ist dort noch die nachfolgende Zeile erforderlich:

.append(<span class="hljs-string">'title'</span>).text(<span class="hljs-function"><span class="hljs-params">(d,i)</span> =&gt;</span> 
  d.properties.name + <span class="hljs-string">': '</span> + 
  AlQuoten[i].toLocaleString() + <span class="hljs-string">' %'</span>) 
Jetzt fehlt noch die Legende, welche die zu den Farben passenden Arbeitslosenquoten aufschlüsselt. Freundlicherweise stellt Mike Bostock auf [5] die Datei legend.js zur Verfügung, welche die Legende mit einem relativ einfachen Funktionsaufruf erstellt, sofern man die Datei mit <script src=“./legend.js“></script> ins Projektverzeichnis kopiert und eingebunden hat.Das Erstellen der Legende erledigt dann der folgende Funktionsaufruf, der am Ende der then-Methode des Promise-Objekts eingefügt wird:

<span class="hljs-selector-tag">g</span><span class="hljs-selector-class">.append</span>(<span class="hljs-string">"g"</span>)<span class="hljs-selector-class">.attr</span>( 
  <span class="hljs-string">"transform"</span>, <span class="hljs-string">"translate(660,20)"</span>) 
  <span class="hljs-selector-class">.append</span>(() =&gt; legend({
    color, 
    <span class="hljs-attribute">title</span>: <span class="hljs-string">"Juli 2020: Arbeitslosenquote in %"</span>, 
    <span class="hljs-attribute">width</span>: <span class="hljs-number">260</span>, <span class="hljs-attribute">tickFormat</span>: <span class="hljs-string">".1f"</span>
})); 
 
Last, but not least gibt es noch den Zoom-Aufruf, welcher ein neues Zoom-Verhalten erstellt. Das zurückgelieferte Verhalten zoom ist sowohl ein Objekt als auch eine Funktion und wird üblicherweise mittels selection.call auf selektierte Elemente angewendet:

svg.call(d3.zoom().scaleExtent( 
  [<span class="hljs-number">1</span>, <span class="hljs-number">128</span>]).<span class="hljs-literal">on</span>(<span class="hljs-string">'zoom'</span>, <span class="hljs-function"><span class="hljs-params">()</span> =&gt;</span> 
  { 
    g.attr(<span class="hljs-string">'transform'</span>, d3.event.transform); 
})); 

Fazit und Ausblick

Mit dem bisher erworbenen Wissen sind Sie grundsätzlich dafür gerüstet, mit D3js und JavaScript eigene Karten mit besonderer Aussagekraft im Webbrowser darzustellen und an Ihre Bedürfnisse anzupassen, beispielsweise durch das Einblenden spezifischer Daten als Tooltipp und durch das Erzeugen von SVG-Elementen wie Rechtecken, Kreisen, Linien, Textelementen oder gar Firmenlogos.
Projektdateien herunterladen

Neueste Beiträge

DWX hakt nach: Wie stellt man Daten besonders lesbar dar?
Dass das Design von Websites maßgeblich für die Lesbarkeit der Inhalte verantwortlich ist, ist klar. Das gleiche gilt aber auch für die Aufbereitung von Daten für Berichte. Worauf besonders zu achten ist, erklären Dr. Ina Humpert und Dr. Julia Norget.
3 Minuten
27. Jun 2025
DWX hakt nach: Wie gestaltet man intuitive User Experiences?
DWX hakt nach: Wie gestaltet man intuitive User Experiences? Intuitive Bedienbarkeit klingt gut – doch wie gelingt sie in der Praxis? UX-Expertin Vicky Pirker verrät auf der Developer Week, worauf es wirklich ankommt. Hier gibt sie vorab einen Einblick in ihre Session.
4 Minuten
27. Jun 2025
„Sieh die KI als Juniorentwickler“
CTO Christian Weyer fühlt sich jung wie schon lange nicht mehr. Woran das liegt und warum er keine Angst um seinen Job hat, erzählt er im dotnetpro-Interview.
15 Minuten
27. Jun 2025
Miscellaneous

Das könnte Dich auch interessieren

UIs für Linux - Bedienoberflächen entwickeln mithilfe von C#, .NET und Avalonia
Es gibt viele UI-Frameworks für .NET, doch nur sehr wenige davon unterstützen Linux. Avalonia schafft als etabliertes Open-Source-Projekt Abhilfe.
16 Minuten
16. Jun 2025
Mythos Motivation - Teamentwicklung
Entwickler bringen Arbeitsfreude und Engagement meist schon von Haus aus mit. Diesen inneren Antrieb zu erhalten sollte für Führungskräfte im Fokus stehen.
13 Minuten
19. Jan 2017
Bausteine guter Architektur - Entwurf und Entwicklung wartbarer Softwaresysteme, Teil 2
Code sauberer gestalten anhand von wenigen Patterns und Grundhaltungen.
6 Minuten
Anzeige
Anzeige
Anzeige
Anzeige
Anzeige