SVG-Diagramme selbst erstellen und mit Alpine.js pimpen
Zwei Wege zu einem Diagramm
In meinen Beiträgen über Radtouren und Segelflüge stehen Diagramme: Höhe über den Nachmittag, Kilometer über den Tag, Tempo gegen Wind. Für so etwas gibt es einen üblichen Weg und einen weniger üblichen.
Der übliche Weg heißt Chart.js und ist die verbreitetste Diagramm-Bibliothek für Webseiten. Man lädt sie in die Seite, übergibt ihr die Zahlen, und sie zeichnet das Diagramm im Browser des Lesers. Das ist bequem und in den meisten Fällen genau richtig.
Der andere Weg: Die Zeichnung entsteht schon auf meinem Server, und der Browser bekommt sie fertig geliefert. Ich habe zuerst den üblichen Weg genommen und ihn wieder verlassen. Warum, lässt sich in zwei Punkten sagen, und der zweite wog schwerer.
Erstens: das Gewicht
Die Bibliothek allein wog 51 Kilobyte, komprimiert. Das ist die Datei, die jeder Besucher herunterlädt, bevor überhaupt eine Linie zu sehen ist. Das fertige Barogramm des Segelflugs kostet heute 8,1 Kilobyte, und darin stecken schon alle 335 Messpunkte, die Zahlentabelle zum Aufklappen und die komplette Bedienung.
Bei einem Blogbeitrag entscheidet das nichts. Ich führe es an, weil die Richtung überrascht: Der Weg, der nach mehr Handarbeit klingt, liefert die kleinere Seite.
Zweitens: die Bedienung
Das Diagramm der Radtour zeigt zwei Größen mit ganz verschiedenen Maßstäben, Tagesschnitt in Stundenkilometern und Windgeschwindigkeit, in zwei getrennten Feldern übereinander. Ich wollte ein Fadenkreuz, das über beide Felder läuft, und einen Hinweis, der beide Werte zur selben Stelle nebeneinander zeigt.
Die Bibliothek machte daraus zwei getrennte Anzeigen, eine pro Feld. Der Leser hätte zweimal hinschauen und selbst zusammensetzen müssen, was zusammengehört. An dieser Stelle stand das fertige Werkzeug meinem Ziel im Weg, und alles Weitere wäre ein Kampf gegen seine Voreinstellungen gewesen.
Was Blade damit zu tun hat
Diese Seite läuft auf Laravel, einem Baukasten für Webseiten in der Programmiersprache PHP. Blade ist dessen Vorlagensystem: eine HTML-Datei, in die man rechnende Ausdrücke einsetzen kann. Wo {{ $titel }} steht, setzt Blade beim Ausliefern den Titel ein. Die Rechnung passiert auf dem Server, beim Leser kommt fertiges HTML an.
Das Entscheidende daran: Eine Zeichnung im Web ist ebenfalls nichts anderes als HTML. Das Format heißt SVG und beschreibt Bilder nicht als Raster aus Bildpunkten, sondern als Anweisungen. „Zeichne eine Linie durch diese Punkte.“ Ein SVG steht als Text mitten im HTML einer Seite, und Blade kann diesen Text erzeugen wie jeden anderen.
Damit fällt der Umweg weg. Ich muss die Zahlen nicht zum Browser schicken, damit dort ein Programm daraus eine Zeichnung baut. Ich kann die Zeichnung gleich hinschreiben.
Ein Liniendiagramm ist eine Liste von Punkten
Das klingt nach mehr, als es ist. Ein SVG-Linienzug ist eine Aufzählung von Koordinatenpaaren:
<polyline points="62,180 74,166 86,151 98,143 …"/>
Die ganze Aufgabe besteht also darin, aus Messwerten solche Zahlenpaare zu machen. Ein Messpunkt ist eine Sekunde nach dem Start und eine Höhe in Metern. Ein Bildpunkt ist eine Position in der Zeichenfläche. Zwischen beidem liegt eine Umrechnung pro Achse, ein Dreisatz:
$px = fn (int $sekunde): float => $x0 + $sekunde / $dauer * ($x1 - $x0);
$py = fn (float $meter): float => $bot - $meter / 2000 * ($bot - $top);
$x0 und $x1 sind der linke und rechte Rand der Zeichenfläche, $top und $bot oben und unten. Die Höhe wird von unten gerechnet, weil in einem Bild die Y-Achse nach unten zählt und in einem Diagramm nach oben.
Damit wird aus der Messreihe die Linie, in einer Zeile: jeder Punkt umgerechnet, alle mit Leerzeichen aneinandergehängt.
Drei Stufen am gleichen Flug
Reden hilft hier weniger als hinsehen. Die nächsten drei Diagramme zeigen dieselben Messwerte, den Segelflug vom Frühjahr 2025, in drei Ausbaustufen. Alle drei sind echt und stehen genau so in dieser Seite, wie du sie siehst.
Stufe 1: nur die Linie
Acht Werte, einer je Stunde plus der Landepunkt, und der Linienzug von oben. Das ist das komplette Diagramm.
Die Form ist da: steiler Anstieg nach dem Windenstart, ein langer Nachmittag oben, der Abstieg zur Landung. Ablesen kann man nichts, aber für eine Trendlinie neben einer Zahl, oft „Sparkline“ genannt, reicht genau das. Der Aufwand dafür sind fünf Zeilen.
Stufe 2: Achsen, Raster und Gelände
Jetzt lohnt die volle Auflösung, also alle 335 Messpunkte statt acht. Dazu kommen ein Raster mit Zwischenlinien, beschriftete Höhen, die Uhrzeiten unten und eine zweite Fläche.
Zwei Dinge sind hier wichtiger, als sie aussehen. Die Zwischenlinien bei 500, 1.000 und 1.500 Metern: Ohne sie lässt sich ein Wert in der Mitte nur raten. Und die graue Fläche, das Gelände unter der Kurve. Sie ist derselbe Handgriff wie die Linie, nur mit drei zusätzlichen Punkten, die die Form nach unten schließen. Erst dadurch wird sichtbar, was die Höhe über dem Meer allein verschweigt: 1.500 Meter über dem flachen Emsland sind etwas anderes als 1.500 Meter über dem Weserbergland.
Bis hierher ist keine Zeile JavaScript im Spiel. Das Diagramm steht fertig im HTML.
Stufe 3: das Fadenkreuz
Und das ist die Stufe, an der die Bibliothek nicht mehr mitkam. Fahr mit der Maus über das Diagramm, auf dem Telefon tippst du hinein.
Der Zeiger liest beide Höhen zur selben Uhrzeit ab, über dem Meer und über Grund, und zeigt sie in einem Kästchen nebeneinander. Genau das war mit dem fertigen Werkzeug nicht zu bekommen.
Dafür läuft hier nun doch Code im Browser mit. Er rechnet die Mausposition in eine Uhrzeit um, sucht den nächstgelegenen Messpunkt und setzt Kreis und Kästchen dorthin.
Etwas ganz anderes: eine Woche im Shop
Drei Stufen an einer Kurve könnten den Eindruck hinterlassen, das Verfahren tauge für Liniendiagramme und sonst nichts. Deshalb noch ein viertes Beispiel, in einem völlig anderen Szenario und mit einer völlig anderen Darstellung.
Aus meiner Arbeit als Shopware-Entwickler kenne ich kaum eine Auswertung, die einem Shopbetreiber schneller etwas sagt als diese: Wann wird eigentlich gekauft? Nicht als Kurve über die Woche, sondern als Raster aus Wochentag und Stunde. 168 Felder, je dunkler, desto mehr Bestellungen.
Bestellungen nach Wochentag und Stunde, 3.194 Stück in einer Woche. Beispieldaten, kein echter Shop. Zum Ablesen auf ein Feld zeigen.
Zeig mit der Maus auf ein Feld, dann steht die Zahl dazu darüber. Und hier ist die Interaktion eine andere als beim Barogramm: Beim Flug war ein Fadenkreuz über der ganzen Spalte richtig, weil dort zwei Kurven zur selben Uhrzeit zusammengehören. Hier ist die Frage eine andere, nämlich „was war in diesem einen Feld", also bekommt jedes Feld seinen eigenen Hinweis.
Das Muster liest sich sofort, und genau darum baut man solche Bilder: Die Nacht ist so gut wie leer, zwischen 1 und 5 Uhr kommen über die ganze Woche 46 Bestellungen zusammen, und sechs einzelne Stunden bleiben ganz ohne. Der dunkelste Block liegt an jedem Tag abends zwischen 18 und 21 Uhr. Sonntag ist die stärkste Zeile, Samstag mit deutlichem Abstand die schwächste. Wer daraus etwas macht, verschickt seinen Newsletter nicht mehr am Samstagvormittag und legt das Wartungsfenster in die Nacht statt in den Sonntagabend.
Ein Hinweis, der hier wichtig ist: Diese Zahlen sind Beispieldaten, kein echter Shop. Die Verteilung ist einem typischen Muster nachgebaut, ein kleines Skript erzeugt sie ohne Zufall, damit die Grafik reproduzierbar bleibt. Bestellzahlen aus einem Kundenshop gehören nicht in einen öffentlichen Blogbeitrag, und alle anderen Diagramme hier tragen echte Messwerte. Deshalb steht der Hinweis auch in der Grafik selbst und nicht nur an dieser Stelle.
Technisch ist daran nichts Neues gegenüber Stufe 3, und das ist der Punkt. Statt einer polyline sind es 168 Rechtecke, statt einer Höhenachse eine einfarbige Farbskala, statt eines Fadenkreuzes ein Rahmen um das aktive Feld. Die Farbskala habe ich wieder rechnen lassen statt geraten, und sie hat mich korrigiert: Mein erster hellster Blauton war gegen den Seitenhintergrund nicht mehr als Feld zu erkennen, er fiel bei der Prüfung durch. Der jetzige ist kräftiger, als ich ihn selbst gewählt hätte.
Ein Feld ohne Bestellung ist übrigens kein Schritt der Skala, sondern neutral grau: die sechs vereinzelten Felder in der Tiefnacht. Der hellste Blauton würde sonst „ganz wenig" heißen und „gar nichts" gleichzeitig, und das sind zwei verschiedene Aussagen.
Was ohne JavaScript übrig bleibt
Hier liegt für mich der eigentliche Gewinn, und die drei Stufen zeigen ihn genau. Weil die Zeichnung schon fertig im HTML steht, ist sie unabhängig davon, ob im Browser Programmcode läuft. Wer JavaScript abschaltet, sieht von Stufe 3 immer noch Stufe 2: Achsen, Kurve, Gelände, Uhrzeiten und Zahlen. Es fehlt einzig das Fadenkreuz zum Ablesen. Beim üblichen Weg bliebe an dieser Stelle eine leere Fläche.
Dazu kommen zwei Dinge, die eine Bibliothek nicht liefern kann, weil sie den Inhalt nicht kennt. Die Bildbeschreibung für Screenreader sagt nicht „Diagramm“, sondern beschreibt den Verlauf in Worten: dass die Höhe in Sägezähnen ansteigt, den Nachmittag über oben bleibt und erst vor der Landung abfällt. Und im Flugbeitrag sitzt unter der Grafik dieselbe Zahlenreihe als aufklappbare Tabelle, für Screenreader und für den Ausdruck.
Ganz ohne JavaScript ist das Diagramm trotzdem nicht, und das will ich nicht verschweigen. Für das Fadenkreuz läuft ein kleines Stück Code mit, knapp 40 Zeilen, die aus der Mausposition den nächstgelegenen Messpunkt suchen und einen Kreis sowie zwei Zahlen dorthin setzen. Der Unterschied zur Bibliothek ist nicht „kein JavaScript“, sondern: nur das, was ich wirklich brauche, und der Rest funktioniert auch ohne.
Zwei Regeln, die mir dabei wichtig geworden sind
Nie zwei Maßstäbe in einer Zeichenfläche. Man sieht solche Diagramme oft: links eine Achse, rechts eine zweite, zwei Kurven, die sich eindrucksvoll ähneln. Das Problem ist, dass sich diese Ähnlichkeit herbeiskalieren lässt. Wer beide Achsen frei wählen darf, bekommt fast jeden gewünschten Zusammenhang hin. Zwei getrennte Felder und ein Fadenkreuz darüber zeigen dasselbe, ohne zu schummeln.
Farben nicht nach Gefühl wählen. Für die Grafik weiter oben habe ich die beiden Blautöne durch ein Prüfprogramm geschickt, das Helligkeit, Sättigung und den Abstand bei Farbsehschwäche nachrechnet. Der erste Versuch fiel durch. Zwei Töne, die für einen Menschen mit Rotschwäche zusammenfallen, sieht man selbst nämlich nicht, und geraten hätte ich es nie.
Ob sich das lohnt
Ehrlich gerechnet: gut 160 Zeilen für ein Diagramm, das Auswertungsskript für die Rohdaten noch nicht mitgezählt. Das ist kein Nachmittag.
Für vier Diagramme in vier Beiträgen hat es sich gelohnt, weil ich jedes Detail in der Hand habe, vom Fadenkreuz über die Bildbeschreibung bis zur Tabelle darunter. Wer dagegen dreißig Diagrammtypen braucht oder eine Redaktion, die sich ihre Auswertungen selbst zusammenklickt, ist mit einer Bibliothek klar besser dran. Das ist keine Glaubensfrage, sondern eine Rechnung, und sie fällt je nach Projekt anders aus.
Wer so etwas für ein Shopware- oder Laravel-Projekt braucht, findet mich bei com:werft.
Auch interessant
Entwicklung
Was ein stillgelegtes WordPress an Angriffen anzieht
Zwei alte WordPress-Seiten, seit Jahren inhaltlich eingefroren, bekamen in vier Wochen fast 53.000 Anfragen auf Login- und Schnittstellen-Pfade. Zwei Drittel davon wurden beantwortet. Nach der Umstellung auf statische Seiten: keine einzige mehr.
Entwicklung
Zwei WordPress-Seiten an einem Nachmittag abgelöst
mageconsult.de und magelounge.de liefen seit Jahren auf WordPress, ohne dass dort noch etwas passierte. An einem Nachmittag im August habe ich beide durch statische Seiten ersetzt, mit Claude Code als Werkzeug. Was dabei schnell ging, was nicht, und warum die Code-Menge nicht der Engpass war.
Entwicklung
WordPress absichern: Scanner-Anfragen mit Status 410 abfangen
Vulnerability-Scanner klopfen pausenlos WordPress-Pfade ab, und jede Antwort ist eine große Fehlerseite. Ein kurzer Block in der .htaccess liefert stattdessen HTTP-Status-Code 410 (Gone) mit 282 Byte und füttert nebenbei fail2ban. So läuft das hier seit Monaten auf sechs Seiten.