MLTimeGraph

Kapitel 5: Auf dem Server zeichnen

go get gitlab.com/mlc0911/mlctimegraph/go-time-graph

Die Go-Seite hat drei Einsatzzwecke, und die Wahl hängt davon ab, wo das Bild entsteht.

Als Bibliothek

Im eigenen Dienst, wenn das Diagramm Teil einer größeren Antwort ist — ein Bericht, eine Seite, ein Anhang.

Zwei Dinge zählen, wenn das SVG neben anderen landet — mehrere Diagramme in einer HTML-Seite oder einem Bericht. Setze idPrefix in den Optionen, wie im Browser, und die eigenen ids des Renderers (Verläufe, Muster, Beschneidungspfade in <defs>) mit SetIDPrefix:

r := renderer.NewSVGRenderer("800", "400")
r.SetIDPrefix(opts.IDPrefix)   // etwa "keller"

Und die Auswertung, die zum Bild gehört, läuft hier ebenfalls: analyze.ComputeLimitExcursions liefert dieselben Episoden und dieselbe Zeit außerhalb der Grenzen wie computeLimitExcursions im Browser — Bildschirm und PDF nennen also dieselbe Dauer.

In Go bekommt eine Wertachse ihr eigenes Zahlenformat im Code, denn eine Funktion reist nicht als JSON. YAxisConfig.Format ist das Gegenstück zu axes.y[].format im Browser:

opts.Axes.Left.Format = func(v float64) string { return fmt.Sprintf("%.1f °C", v) }

Als Kommandozeilenwerkzeug

graph-cli -in config.json -out chart.svg
graph-cli -in config.json -out chart.svg -bg white

Für zeitgesteuerte Aufgaben: der tägliche Bericht um sechs, das wöchentliche Bild am Montag. -bg white setzt einen Hintergrund — nötig, sobald das Bild in ein PDF oder eine E-Mail eingebunden wird, wo Transparenz schnell zu Schwarz auf Schwarz werden kann.

Jede statische Abbildung in diesem Handbuch entstand auf diesem Weg.

Das Wiederkehrende in eine eigene Datei

Sobald mehrere Berichte entstehen, wiederholt sich ein Teil der Beschreibung: Maße, Achsen, Legende, Farben. Diesen Teil in jede Datei zu kopieren heißt, ihn irgendwann an zwölf Stellen zu ändern und an einer zu vergessen. -with legt ihn stattdessen darüber:

graph-cli -in messwerte.json -with haus-theme.json -out chart.svg

Das Flag ist wiederholbar, die spätere Ebene gewinnt — so lässt sich ein Hausstil um Projekt- und Einzelfall-Abweichungen ergänzen:

graph-cli -in messwerte.json -with haus-theme.json -with bericht.json -out chart.svg

Die Regel dahinter ist ein Satz: Objekte verschmelzen, alles andere wird ersetzt. Ein Theme, das nur das Gitter setzt, lässt die Achsenbeschriftung aus den Daten stehen:

{
  "width": 1000,
  "axes": {
    "x":    { "grid": { "major": { "color": "#eeeeee" } } },
    "left": { "grid": { "major": { "color": "#eeeeee" } } }
  },
  "legend": { "show": true, "position": "outside-right" }
}

„Alles andere" schließt Arrays ein, und das ist die Stelle, an der man aufpassen muss: Eine Ebene mit series ersetzt die Messreihen, statt sie zu gestalten. Das ist Absicht — würde angehängt, machte ein Theme mit einem einzigen Linienstil aus einer Kurve zwei, und niemand käme darauf, warum. Die CLI warnt, wenn eine überlagernde Ebene series setzt.

mtg-pdf kennt dasselbe Flag. Dort gilt es nur für Optionen: Zeichenbefehle sind bereits gerechnete Geometrie, über die sich kein Theme legen lässt.

Im Browser leistet mergeOptions dasselbe:

import { mergeOptions, MLTimeGraph } from 'ml-time-graph';

new MLTimeGraph(mergeOptions(hausTheme, messwerte));

Nicht zu verwechseln mit { ...hausTheme, ...messwerte } — der Spread ist flach. Ein axes im zweiten Objekt ersetzt damit das komplette axes des ersten, samt Gitter und Ticks.

Als HTTP-Dienst

graph-server nimmt dieselbe JSON-Beschreibung entgegen und antwortet mit SVG. Sinnvoll, wenn mehrere Anwendungen Diagramme brauchen und keine davon eine Go-Abhängigkeit aufnehmen soll.

Die Beschreibung

Alle drei lesen dieselbe Struktur — genau die, die auch die TypeScript-Seite im Browser akzeptiert. Ein Diagramm, das du interaktiv entworfen hast, wird zu einer JSON-Datei und läuft unverändert durch den Server.

Statistikband mit Minimum, Mittelwert und Maximum — serverseitig gezeichnet.
Einzeln hervorgehobene Messwerte.
Schraffuren — lesbar, wo Farbe nicht überlebt: im Schwarz-Weiß-Druck.

Wenn die Beschreibung falsch ist

Ein Tippfehler in einer JSON-Datei verschwand früher spurlos: "lnie": "dashed" wurde übergangen, der Schwellenwert kam durchgezogen heraus, und niemand wusste, warum. Seit 1.17 prüfen alle drei Zugänge jedes Feld gegen die Beschreibung und lehnen ab, was sie nicht kennen:

invalid options: legnd: unknown field (did you mean "legend"?);
  thresholds[0].lnie: unknown field (did you mean "line"?)

Alle Fehler in einer Meldung, jeder mit seinem Pfad, mit Vorschlag, wo ein bekannter Name nahe liegt. Fehlende Pflichtfelder — eine Reihe ohne name oder data, ein Schwellenwert ohne name — werden genauso gemeldet. Die CLI endet mit Fehler, graph-server antwortet 400, und chart.LoadOptions gibt den Fehler an deinen Code zurück.

Und was kein Fehler ist

Manche Beschreibung ist vollständig und trotzdem nicht gemeint: ein Schwellenname mit Tippfehler in colorByThresholds, eine Flächengrenze, die eine Reihe nennt, die es nicht gibt, side an einer Fläche, die es nicht beachtet. Gezeichnet wird trotzdem, nur ohne die Zone. Beide Bibliotheken melden solche Fälle auf Nachfrage, mit demselben Text:

for _, w := range chart.NewMLTimeGraph(opts).Warnings() {
    log.Println(w) // series 0 colorByThresholds names unknown threshold "mxa"
}

Im Browser heißt es chart.warnings(). Schwellenwerte werden über ihre id gesucht und nur ohne id über den name, genau wie beim Zeichnen: Ein Schwellenwert mit id ist unter seinem Namen nicht zu finden, und der Hinweis sagt das.

Was der Server nicht kann

Alles, was jemand anklickt. Kein Zoom, kein Tooltip, keine Auswahl. Wenn du das brauchst, brauchst du den Browser — und wenn nicht, spart dir der Server eine Runtime, eine Fehlerklasse und einen Seitenaufruf.

Vom selben Autor

Lizenz: CC BY-NC 4.0