Für mein aktuelles Projekt geofy.de, musste ich eine große Anzahl von Charts generieren, die ich als Marker auf Landkarten einsetzen wollte. Dieser Beitrag zeigt Euch wie.
geofy.de ist ein Service, der Informationen über Regionen in Deutschland sammelt und über Indikatoren leicht zugänglich macht. Es gibt unter anderem auch ein Tool, dass Euch beim Umzug nach Berlin, Hamburg oder München unterstützt. Ein zur Zeit in Arbeit befindlicher Teil des Service, versucht statistische Kennzahlen als sogenannte Marker auf einer Karte darzustellen.
Ich hatte mich für mein Projekt entschieden, die angezeigten Marker mit zusätzlicher visueller Information auszustatten. So konnte ich komplexe Sachverhalte in einer einfachen Grafik verpacken und im Kontext der jeweiligen Region anzeigen.
Die folgende Karte zeigt Euch ein Beispiel:
Jeder Marker zeigt drei Indikatoren an, die Auskunft über die Wohnqualität einer bestimmten Region geben. Je "geschlossener" ein Kreis im Marker ist, umso höher der Wert des Indikators.
Die in der Karte angezeigten Miniatur-Charts wurden mit den in diesem Beitrag vorgestellten Tools generiert. Ich werde Euch Schritt für Schritt zeigen, wie ich das bewerkstelligt habe.
tl;dr
Dieser Beitrag zeigt eine Lösung, die in einer Node.js Laufzeitumgebung mit Hilfe von D3.js and Radial Progress Chart SVG Charts generiert. Anschließend werden die SVG Grafiken in PNG Grafiken konvertiert.
Den Source Code dazu findet Ihr bei github.
Den Source Code dazu findet Ihr bei github.
Voraussetzungen
Dieser Beitrag geht davon aus, dass Ihr bereits über eine funktionierende Installation von Node.js verfügt. Die Installationsversion könnt ihr hier herunterladen. Ich würde empfehlen, zunächst die Installation von Node.js abzuschließen, um anschließend hier weiter zu machen.
Die Werkzeuge
Wir werden gemeinsam eine Lösung entwickeln, die in einer Node.js Laufzeitumgebung mit Hilfe von D3.js and Radial Progress Chart SVG Charts generiert. Anschließend verwenden wir sharp, um den SVG Code in PNG Grafiken zu konvertieren.
D3.js
Ich hatte mich während des Projektes aus verschiedenen Gründen sehr früh für die Verwendung von D3.js entschieden. D3.js ist ein sehr mächtiges Werkzeug für die Erstellung von interaktiven Charts. Es existieren zudem zahllose Tutorials, Anleitungen und How-Tos, die Dir helfen, D3.js zu meistern.Node.js
Da ich die Marker serverseitig vorgenerieren musste, war es notwendig eine JavaScript Laufzeitumgebung auf dem Server zu installieren. Ich habe ich mich für Node.js entschieden.Radial Progress Charts
Für die Generierung der eigentlichen Charts verwenden wir eine leicht angepasste Version von Pablo Molnars Radial Progress Chart, die wunderbare Radial Bar Charts erstellt. Hier ein Beispiel:
Der Code für das oben dargestellte Ergebnis ist erstaunlich simpel:
let radialColors = ['#051F20', '#235347', '#8EB69B'];
let radialIndicator = new RadialProgressChart('#radialIndicator', {
diameter: 130,
min: 0,
max: 100,
series: [
{value: 87, color: {solid: radialColors[0], background: 'white'}},
{value: 35, color: {solid: radialColors[1], background: 'white'}},
{value: 57, color: {solid: radialColors[2], background: 'white'}},
]
});
Sharp
Für die Konvertierung des generierten SVG Codes verwenden wir das Projekt sharp, eine "High performance Node.js image processing" Bibliothek. Und "high performance" ist sharp in der Tat: es ist wirklich extrem fix!Los geht's...
Jetzt aber "Butter bei die Fische", wie man in meiner alten Heimat Hamburg sagt. Zunächst gilt es, mit Hilfe des Node.js Paketmanagers npm, die D3.js, JSDOM und sharp Bibliotheken zu installieren:npm install --save d3 jsdom sharp
Anschließend legen wir uns eine neue JavaScript Datei an. Ich nenne sie generate-markers.js.
Als erstes werden wir die benötigten Bibliotheken laden und ein paar Initialisierungen vornehmen:
const fs = require('fs');
const d3 = require('d3');
const jsdom = require('jsdom');
const sharp = require('sharp');
const chart = require('./js/radial-progress-chart');
const { JSDOM } = jsdom;
const { window } = new JSDOM('<!DOCTYPE html><html><body></body></html>');
// make document object directly accessible
document = window.document;
// Append our SVG Container
let body = d3.select(window.document).select('body');
body.append('div').attr('id', 'radialContainer');
Die Bibliothek fs benötigen wir, um die Ergebnisse unserer Generierung in das Dateisystem schreiben zu können. Die Bibliothek d3 ist das Node.js Paket, für D3.js und die Bibliothek jsdom wird benötigt, um für D3 ein Domain Object Model (DOM) zur Verfügung zu stellen. Ohne DOM hat D3.js keine Basis, um SVG Grafiken generieren zu können. Die Bibliothek sharp schließlich, hilft uns die SVG Dateien in PNG zu konvertieren.
Nachdem das Lager bereitet ist, kann die eigentliche Arbeit beginnen. Wir definieren die Farben, die für die einzelnen Ringe verwendet werden sollen (von innen nach außen) und initialisieren das Chart Objekt. Zunächst noch für jeden Ring mit dem Wert 0:
// define the colors
let radialColors = ['#051F20', '#235347', '#8EB69B'];
// initialize the chart object
let radialIndicator = new chart('#radialContainer', {
diameter: 130,
min: 0,
max: 10,
stroke: {
gap: 2
},
series: [
{value: 0, color: {solid: radialColors[0], background: 'white'}},
{value: 0, color: {solid: radialColors[1], background: 'white'}},
{value: 0, color: {solid: radialColors[2], background: 'white'}},
]
});
Zu guter Letzt iterieren wir für alle Ringe über die Werte 0 bis 10. Für jede Kombination der Werte generieren wir eine SVG Datei und konvertieren sie in ein PNG:
// iterate through the radial bar values
for(let radialBarOneValue = 0; radialBarOneValue <= 10; ++radialBarOneValue) {
for(let radialBarTwoValue = 0; radialBarTwoValue <= 10; ++radialBarTwoValue) {
for(let radialBarThreeValue = 0; radialBarThreeValue <= 10; ++radialBarThreeValue) {
// create folder
fs.mkdirSync('./radial/' + radialBarOneValue + '/' + radialBarTwoValue, { recursive: true });
let fileName = './radial/' + radialBarOneValue + '/' + radialBarTwoValue + '/' + radialBarThreeValue;
console.log('generating ' + fileName + '.svg');
// update chart
radialIndicator.update([radialBarOneValue, radialBarTwoValue, radialBarThreeValue]);
// read SVG code from HTML div
let svgBuffer = body.select('#radialContainer').html();
// store SVG code to file system
fs.writeFileSync(fileName + '.svg', svgBuffer);
// convert SVG into PNG file and store into filesystem
console.log('converting into ' + fileName + '.png');
sharp(fileName + '.svg')
.png()
.resize(35, 35)
.toFile(fileName + '.png')
.catch(function(err) {
console.log(err)
});
}
}
}
Als Ergebnis erhalten wir eine Verzeichnisstruktur mit den generierten SVG und PNG Dateien:
└───radial
└───0
├───0
│ 0.png
│ 0.svg
│ 1.png
│ 1.svg
│ 10.png
│ 10.svg
│ 2.png
│ 2.svg
│ 3.png
│ 3.svg
│ 4.png
│ 4.svg
│ 5.png
│ 5.svg
│ 6.png
│ 6.svg
│ 7.png
│ 7.svg
│ 8.png
│ 8.svg
│ 9.png
│ 9.svg
│
├───1
│ 0.png
│ 0.svg
│ 1.png
│ 1.svg
│ 10.png
│ 10.svg
│ 2.png
│ 2.svg
│ 3.png
│ 3.svg
│ 4.png
│ 4.svg
│ 5.png
[...]
Das fertige Skript hat von mir noch ein paar Tweaks bekommen, beispielsweise mehrere Farbthemen und die Möglichkeit, Farben als Argument zu übergeben. Die grundlegende Funktionalität bleibt davon aber unberührt.
Kommentare
Kommentar veröffentlichen