Wer komplexe Infrastrukturen dokumentiert, stößt mit statischen Diagrammen und MS PowerPoint als Werkzeug schnell an Grenzen. Ein Setup aus mehreren Maschinen, zwei verschiedenen LANs und einer Handvoll Docker-Container lässt sich zwar in Text mit vielen Sätzen beschreiben aber ein animiertes Diagramm, das den Datenfluss tatsächlich zeigt, läßt sich viel schneller für uns Menschen erfassen. Ich wollte genau so eine Lösung die mir ein animiertes Architekturdiagramme aus Text heraus generiert. Das Diagramm will ich direkt im Browser öffnen können, in WordPress einbetten und für LinkedIn & Co auch in unterschiedlichen Formaten und nicht nur SVG exportieren können.
Langfristiges Ziel ist ein kleines Web-Interface, über das ich per LLM-Chat-Dialog Diagramme generieren kann. Die Idee ist, ich beschreibe mein Setup in natürlicher Sprache, das Modell erzeugt den D2-Code, und das Ergebnis wird mir direkt im Browser angezeigt. Das Web-Interface kommt im zweiten Teil dieser kleinen Serie. In diesem ersten Post baue ich die lokale Rendering-Pipeline auf: von der D2-Beschreibung über das animierte SVG bis zum exportierten GIF.
Warum D2?
Es gibt viele Diagramm-Tools, aber die meisten erfüllen mindestens eines dieser Kriterien nicht: entweder sind sie Cloud-abhängig, nicht scriptbar oder produzieren nur statische Ausgaben. D2 trifft alle meine Anforderungen:
- Open Source (Mozilla Public License 2.0)
- Rein text-basiert und damit ideal für LLMs, kein GUI, keine Cloud, keine Abhängigkeiten nach der Installation
- Native animierte Kanten mit
style.animated: true - Automatisches Layout via dagre oder ELK das bedeutet ich muss keine Pixel-Koordinaten angeben
- Läuft vollständig lokal, damit ideal als Backend für einen LLM-gesteuerten lokal eingerichteten Generator
Genau der letzte Punkt ist entscheidend: Weil D2 text-basiert ist, kann ein lokales Sprachmodell den Diagramm-Code erzeugen. Das verwendete Modell muss nur eine einfache deklarative Syntax beherrschen und nicht in Pixel-Rechnen. Allerdings sollte das verwendete LLM dann fähig sein aus einer Prosa aber technischen Beschreibung ein Diagramm ableiten zu können.
Installation auf Ubuntu
D2 wird als Standalone-Binary ausgeliefert. Das offizielle Install-Skript lädt das passende Release von GitHub und installiert es nach ~/.local/bin/:
Befehl: curl -fsSL https://d2lang.com/install.sh | sh -s --
Mit dem jetzt folgenden Befehl kannst Du prüfen ob die Installation funktioniert hat.
Befehl: ~/.local/bin/d2 version
Wer das Skript nicht blind ausführen möchte, kann es vorher mit --dry-run inspizieren:
curl -fsSL https://d2lang.com/install.sh | sh -s -- --dry-run
Wichtig: D2 landet in ~/.local/bin/, nicht in /usr/local/bin/. In Scripts und Agent-Skills immer den vollständigen Pfad ~/.local/bin/d2 verwenden. Denn systemd-Dienste haben ~/.local/bin/ oft nicht im PATH als Variable.
D2-Syntax: Das erste Diagramm
Die Syntax von D2 ist schnell gelernt. Nodes werden einfach deklariert, Container (z.B. für LAN-Zonen) durch geschweifte Klammern geschachtelt, und Verbindungen mit -> oder <-> definiert und auch deren Richtung wie man anhand der <> erkennt. Die Animation aktiviert man pro Kante mit style.animated: true.
Hier ist mein aktuelles Setup als Beispiel. Es geht dabei um drei Maschinen, zwei LANs und meine lokalen Serivces:
direction: down
telegram: "Telegram\nControl channel" {
style.fill: "#f0f0f0"
style.stroke: "#aaaaaa"
}
lanA: LAN 192.168.178.x {
optiplex: "Dell OptiPlex 5040\nNemoClaw + OpenShell egress\n192.168.178.142"
windows: "Windows workstation\n192.168.178.96" {
style.fill: "#f0f0f0"
}
}
lanB: LAN 192.168.2.x {
firecrawl: "Firecrawl (A6000 Ada)\n5 containers · port :3002\n192.168.2.119"
ollama: "Ollama server (A6000)\nqwen3.6:27b + qwen3-embedding:4b\n192.168.2.57:11434" {
shape: cylinder
}
hermes: "Hermes Agent\nTelegram gateway\n192.168.2.119"
}
web: "The open web\nScrape targets" {
style.fill: "#f0f0f0"
style.stroke: "#aaaaaa"
}
telegram <-> lanB.hermes: "Tasks · Replies" {
style.animated: true
}
lanA.windows -> lanA.optiplex: "SSH -L 18789 tunnel" {
style.animated: true
style.stroke-dash: 5
}
lanA.optiplex <-> lanB.firecrawl: "Scrape · :3002" {
style.animated: true
}
lanB.firecrawl <-> lanB.ollama: "/v1 · LLM + embeddings" {
style.animated: true
}
lanB.hermes <-> lanB.firecrawl: "Scrape request" {
style.animated: true
}
lanB.hermes <-> lanB.ollama: "LLM inference" {
style.animated: true
}
lanB.firecrawl <-> web: "Fetch pages" {
style.animated: true
}
Container wie lanA und lanB erzeugt D2 automatisch als visuelle Zonen. Das Layout berechnet D2 selbst – ich gebe nur die logische Struktur vor.
Wenn ihr jetzt selber ein Diagramm erzeugen möchtet dann speichert die Beispiel D2-Syntax in einer Text Datei ab die z. B. diagram.d2 heißt. Diese Datei mit der Beschreibung des Diagramms müssen wir dann für die Erstellung der SVG Datei an D2 übergeben.
SVG rendern und im Browser ansehen
Ein einzelner Befehl reicht aus, um das animierte SVG aus der erstellten diagram.d2 zu erzeugen:
Befehl: ~/.local/bin/d2 diagram.d2 diagram.svg
Wer das Diagramm während der Bearbeitung live sehen möchte, nutzt den Watch-Modus. Er startet einen lokalen Server und lädt den Browser bei jedem Speichern automatisch neu. Bei mir geht das leider nicht, da ich keinen Monitor an dem Rechner angeschlossen habe und nur remote über das Terminal-Fenster arbeite.
Befehl: ~/.local/bin/d2 --watch diagram.d2 diagram.svg
Das SVG kann direkt in WordPress eingebettet werden wie ihr ja hier seht. Die Animation läuft im Browser des Lesers ohne zusätzliche Plugins oder JavaScript.
Animiertes SVG als MP4 aufzeichnen
Für Plattformen, die kein SVG rendern, braucht man ein Video. Ich verwende dafür Playwright mit Headless Chromium: Der Browser lädt das SVG und zeichnet die Animation auf. Playwright speichert die Aufnahme intern als WebM in einem temporären Ordner denn WebM ist Playwrights natives Aufnahmeformat. Das record.js-Script greift diese Zwischendatei sofort ab und konvertiert sie in einem einzigen Durchlauf mit ffmpeg weiter. Als Ergebnis landet ausschließlich die fertige output.mp4 im Arbeitsverzeichnis. Damit ist bei mir die WebM-Datei ist nur ein interner Zwischenschritt den ich nicht aufhebe.
Voraussetzungen installieren
Wenn ihr ffmpeg noch nicht installiert habt dann geht das mit dem folgenden Befehl unter Ubuntu.
Befehl: sudo apt install -y ffmpeg
Jetzt legen wir einen Ordner an in dem wir die Animation aus der SVG Datei in z. B. eine MP4 Datei abspeichern werden. Hier liegt dann auch unser record.js Script das genau das für uns macht.
Befehl:mkdir ~/d2/recorder && cd ~/d2/recorder
Befehl:npm init -y
Befehl:npm install playwright
Befehl:npx playwright install --with-deps chromium
Jetzt habt ihr die einzelnen Komponenten installiert die ihr brauch um die SVG Animation in z. B. ein MP4 format umzuwandeln.
record.js Pipeline
Der letzte Baustein der noch fehlt idas die Pipeline die aus der SVG Datei die WebM Datei erstellt. Dazu legt ihr jetzt eine Java-Skript Datei an mit dem Namen record.js.
Bei mir liegt die Datei im oben bereits Unterordner ~/d2/recorder in dem ich die SVG Datei erstellen lasse und in dem auch die diagram.d2 Datei liegt.
Kopiert den folgenden Inhalt in diese record.js Datei.
const { chromium } = require('playwright');
const path = require('path');
const fs = require('fs');
const { execSync } = require('child_process');
(async () => {
const svgPath = path.resolve(process.argv[2] || '../diagram.svg');
const outFile = process.argv[3] || 'output.mp4';
const SECONDS = 6;
const W = 1200, H = 800;
fs.mkdirSync('_videos', { recursive: true });
const browser = await chromium.launch();
const ctx = await browser.newContext({
viewport: { width: W, height: H },
recordVideo: { dir: '_videos/', size: { width: W, height: H } }
});
const page = await ctx.newPage();
await page.goto('file://' + svgPath);
await page.waitForTimeout(SECONDS * 1000);
await ctx.close();
await browser.close();
const webm = '_videos/' + fs.readdirSync('_videos/').find(f => f.endsWith('.webm'));
execSync(
`ffmpeg -y -i ${webm} ` +
`-vf "scale=trunc(iw/2)*2:trunc(ih/2)*2" ` +
`-c:v libx264 -movflags +faststart -pix_fmt yuv420p ${outFile}`
);
console.log('Done:', outFile);
})();
Wichtig ist das ihr euch das Skript genau anschaut und mit den Variablen vertraut macht. Denn hier könnt ihr auf die Länge oder auch Auflösung einwirken.
Ausführen
Nach dem die record.js Datei gespeichert ist und bereits eine diagram.svg Datei vorliegt könnt ihr die Pipeline einmal ausführen.
node record.js ../diagram.svg output.mp4 Als Ergebnis erhaltet ihr eine *.mp4 Datei.
MP4 in ein animiertes GIF konvertieren
LinkedIn-Kommentare akzeptieren kein MP4 sonder viele andere Formate wie z.B. GIF. Auch für andere Plattformen ist ein animiertes GIF oft praktischer als ein Video. Die Konvertierung erfolgt in zwei Schritten: zuerst eine optimierte Farbpalette erzeugen, dann das GIF damit rendern.
Schritt 1: Farbpalette generieren
Befehl: ffmpeg -i output.mp4 -vf "fps=15,scale=900:-1:flags=lanczos,palettegen" palette.png
Schritt 2: GIF mit Palette rendern
Befehl: ffmpeg -i output.mp4 -i palette.png -filter_complex "fps=15,scale=900:-1:flags=lanczos[x];[x][1:v]paletteuse" output.gif
Wichtig: Schritt 2 benötigt zwingend -filter_complex statt -vf, weil zwei Eingabedateien verarbeitet werden. Mit -vf bricht ffmpeg mit einem Fehler ab.
Das Ergebnis ist ein GIF mit rund 3–4 MB bei 900 Pixel Breite und 15 fps. Das ist klein genug für LinkedIn-Kommentare, scharf genug für den Blog.
Was als Nächstes kommt
Die Pipeline steht: D2 erzeugt aus einer text-basierten Beschreibung ein animiertes SVG, Playwright nimmt es als MP4 auf, ffmpeg exportiert es als GIF. Alle Schritte laufen vollständig lokal kein Cloud-Dienst, keine externe API.
Im nächsten Post baue ich das Web-Interface darum herum: ein LLM-Chat-Dialog, in dem ich mein Setup in natürlicher Sprache beschreibe, das lokale Modell daraus den D2-Code generiert und das fertige Diagramm direkt im Browser erscheint. Die Export-Buttons für SVG, MP4 und GIF kommen mit dazu.







Ein toller Guide der leicht zugänglich und verständlich ist. Perfekt für ein kleines Side-Project geeignet. Aktuell half mir noch mein…
Thank you for this great tutorial, could you share n8n workflow and comfyui workflow please?
Hallo Anton, die Meldung besagt das in meinem Beisiel Methoden verwendet werden die veraltet (deprecated) sind. Also müsstest Du die…
Danke für das Tool! Ich habe erst kürzlich angefangen mich mit der Thematik zu beschäftigen und bin für meine Erwartungen…
Hallo, ich habe ihre Anleitung befolgt und bekomme im letzten Schritt leider immer folgende Meldung im Terminal: bash <(wget -qO-…