# Verbesserung der Meerjungfrauen-Diagramme mit Pan/Zoom und Export

<!--category-- Mermaid, Javascript, SVG, DaisyUI, Tailwind -->
<datetime class="hidden">2025-11-07T11:15</datetime>

# Einleitung

> **npm-Paket verfügbar:** Diese Umsetzung ist jetzt verfügbar als [@Mostlylucid/Mermaid-Enhancements](https://www.npmjs.com/package/@mostlylucid/mermaid-enhancements) - ein produktionsfertiges npm-Paket. Siehe [Veröffentlichen von Mermaid Enhancements als npm-Paket](/blog/publishingmermaidenhancementsnpm) für Details, wie man es in Ihren Projekten verwendet.

Mermaid ist ein fantastisches Werkzeug zum Erstellen von Diagrammen aus Text, aber die Standard-Rendering kann für komplexe Diagramme begrenzt sein. Benutzer können nicht einfach vergrößern, um Details zu sehen, schwenken um große Diagramme, oder exportieren Sie sie für Dokumentation. In diesem Artikel, Ich werde Ihnen zeigen, wie ich Mermaid Diagramme auf dieser Website mit interaktiven Pan / Zoom-Steuerungen, Vollbild Lightbox-Anzeige und Export-Funktionalitäten (sowohl PNG-und SVG-Formate).

Diese Implementierung ist produktionsbereit, handhabt dunkle Modus schalten anmutig, und ist widerstandsfähig gegen Cloudflare Rocket Loader Interferenzen.

## Wie es aussieht

Eine schöne in Seite (und Popout) mermaid.js Anzeige, die ist irgendwie wie GitHub's aber *besser*. Mittel Diagramme nehmen SCREENS nicht auf, sondern sind immer noch easuu zu lesen.

![mermaid_pan_zoom.png](mermaid_pan_zoom.png)

[TOC]

# Das Problem

Aus dem Kasten, Mermaid Diagramme haben mehrere Einschränkungen:

1. **Feste Größe** - Große Diagramme sind entweder abgeschnitten oder geschrumpft
2. **Keine Interaktivität** - Kann nicht hineinzoomen, um Details zu sehen oder herum zu schwenken
3. **Keine Ausfuhr** - Benutzer können keine Diagramme für die externe Verwendung speichern
4. **Schlechte mobile Erfahrung** - Kleine Bildschirme machen komplexe Diagramme unbrauchbar
5. **Themenwechsel-Probleme** - Diagramme re-render nicht immer korrekt beim Umschalten zwischen Licht- und Dunkelmodus

# Die Lösung

Ich habe ein umfassendes Verbesserungssystem implementiert, das fügt hinzu:

- **Interaktiver Schwenk/Zoom** Verwendung der Bibliothek svg-pan-zoom
- **Drehtasten für die Schwimmsteuerung** zum Ein-/Auszoomen, Zurücksetzen, Schwenken und Exportieren
- **Vollbild-Lightbox** Modus für eine bessere Ansicht
- **Export von PNG und SVG** Funktionstüchtigkeit
- **Auto-Fit bei Last** so ist das gesamte Diagramm standardmäßig sichtbar
- **Anzeige der vollen Breite** Beseitigung künstlicher Größenbeschränkungen

## Überblick über die Architektur

Die Lösung besteht aus drei Hauptkomponenten:

```mermaid
graph TB
    A[mermaid_theme_switch.js] -->|Initializes| B[Mermaid Diagrams]
    B -->|Renders SVG| C[mermaid_enhancements.js]
    C -->|Adds| D[Control Buttons]
    C -->|Initializes| E[svg-pan-zoom]
    D -->|Triggers| F[Pan/Zoom Actions]
    D -->|Triggers| G[Export Functions]
    D -->|Triggers| H[Fullscreen Lightbox]
```

# Durchführung

## Abhängigkeiten installieren

Installieren Sie zunächst die erforderlichen npm-Pakete:

```bash
npm install svg-pan-zoom html-to-image
```

Diese Bibliotheken bieten:

- `svg-pan-zoom` - Interaktive Pan- und Zoom-Funktionalität für SVG-Elemente
- `html-to-image` - Exportieren der SVG/PNG-Funktionalität

## Kernverbesserungsmodul

Das Hauptverbesserungsmodul (`mermaid_enhancements.js`) behandelt alle interaktiven Funktionen.

### Steuerknöpfe erstellen

Jedes Diagramm erhält ein schwebendes Bedienfeld mit Tasten für alle Aktionen:

```javascript
function createControlButtons(container, diagramId) {
    // Check if controls already exist
    if (container.querySelector('.mermaid-controls')) {
        return;
    }

    const controlsDiv = document.createElement('div');
    controlsDiv.className = 'mermaid-controls';

    const buttons = [
        { icon: 'bx-fullscreen', title: 'Fullscreen', action: 'fullscreen' },
        { icon: 'bx-zoom-in', title: 'Zoom In', action: 'zoomIn' },
        { icon: 'bx-zoom-out', title: 'Zoom Out', action: 'zoomOut' },
        { icon: 'bx-reset', title: 'Reset View', action: 'reset' },
        { icon: 'bx-move', title: 'Pan', action: 'pan' },
        { icon: 'bx-image', title: 'Export as PNG', action: 'exportPng' },
        { icon: 'bx-code-alt', title: 'Export as SVG', action: 'exportSvg' }
    ];

    buttons.forEach(btn => {
        const button = document.createElement('button');
        button.className = `mermaid-control-btn bx ${btn.icon}`;
        button.setAttribute('title', btn.title);
        button.setAttribute('aria-label', btn.title);
        button.setAttribute('data-action', btn.action);
        button.setAttribute('data-diagram-id', diagramId);
        controlsDiv.appendChild(button);
    });

    container.appendChild(controlsDiv);
}
```

### Initialisierung von Pfanne/Zoom

Die Bibliothek svg-pan-zoom bietet eine reibungslose, performante Interaktion:

```javascript
function initPanZoom(svgElement, diagramId) {
    // Clean up existing instance if present
    if (panZoomInstances.has(diagramId)) {
        try {
            panZoomInstances.get(diagramId).destroy();
        } catch (e) {
            console.warn('Failed to destroy existing pan-zoom instance:', e);
        }
        panZoomInstances.delete(diagramId);
    }

    try {
        const panZoomInstance = svgPanZoom(svgElement, {
            zoomEnabled: true,
            controlIconsEnabled: false, // We use custom controls
            fit: true,
            center: true,
            minZoom: 0.1,
            maxZoom: 10,
            zoomScaleSensitivity: 0.3,
            dblClickZoomEnabled: true,
            mouseWheelZoomEnabled: true,
            preventMouseEventsDefault: true,
            contain: false
        });

        panZoomInstances.set(diagramId, panZoomInstance);
        return panZoomInstance;
    } catch (error) {
        console.error('Failed to initialize pan-zoom:', error);
        return null;
    }
}
```

### Exportfunktionalität

Das Exportsystem bewahrt die Diagrammqualität und verarbeitet sowohl PNG- als auch SVG-Formate:

```javascript
async function exportDiagram(container, format, diagramId) {
    try {
        const svgElement = container.querySelector('svg');
        if (!svgElement) {
            window.showToast && window.showToast('No diagram found to export', 3000, 'error');
            return;
        }

        // Clone the SVG to avoid modifying the original
        const clonedSvg = svgElement.cloneNode(true);

        // Get the viewBox or calculate from bounding box
        let viewBox = clonedSvg.getAttribute('viewBox');
        if (!viewBox) {
            const bbox = svgElement.getBBox();
            viewBox = `${bbox.x} ${bbox.y} ${bbox.width} ${bbox.height}`;
            clonedSvg.setAttribute('viewBox', viewBox);
        }

        // Parse viewBox to get dimensions
        const [, , vbWidth, vbHeight] = viewBox.split(' ').map(Number);

        // Set explicit dimensions based on viewBox for proper export
        clonedSvg.setAttribute('width', vbWidth);
        clonedSvg.setAttribute('height', vbHeight);

        // Remove inline styles but keep viewBox
        clonedSvg.removeAttribute('style');
        clonedSvg.style.backgroundColor = 'transparent';
        clonedSvg.style.maxWidth = 'none';

        // Create temporary container
        const tempDiv = document.createElement('div');
        tempDiv.style.position = 'absolute';
        tempDiv.style.left = '-9999px';
        tempDiv.appendChild(clonedSvg);
        document.body.appendChild(tempDiv);

        let dataUrl;
        const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
        const filename = `mermaid-diagram-${timestamp}`;

        if (format === 'png') {
            dataUrl = await toPng(clonedSvg, {
                backgroundColor: 'white',
                pixelRatio: 2 // Higher quality
            });
            downloadFile(dataUrl, `${filename}.png`);
        } else {
            dataUrl = await toSvg(clonedSvg, {
                backgroundColor: 'transparent'
            });
            downloadFile(dataUrl, `${filename}.svg`);
        }

        // Clean up
        document.body.removeChild(tempDiv);

        window.showToast && window.showToast(`Diagram exported as ${format.toUpperCase()}`, 3000, 'success');
    } catch (error) {
        console.error('Failed to export diagram:', error);
        window.showToast && window.showToast('Failed to export diagram', 3000, 'error');
    }
}
```

**Wichtigste Ausfuhrüberlegungen:**

1. **Ansicht beibehaltenBox** - Kritisch für die Erfassung des gesamten Diagramms, nicht nur der sichtbare Teil
2. **Abmessungen berechnen** - Explizit eingestellte Breite/Höhe von viewBox für konsistenten Export
3. **Transformationen entfernen** - Streifen Pan-Zoom-Transformationen so Export zeigt das vollständige Diagramm
4. **Umgang mit dem Hintergrund** - Weißer Hintergrund für PNG, transparent für SVG
5. **Hohe Auflösung** - Verwendung `pixelRatio: 2` für knusprige PNG-Exporte

### Vollbild-Lightbox

Die Lightbox bietet ein immersives Seherlebnis:

```javascript
function openFullscreenLightbox(container, diagramId) {
    const svgElement = container.querySelector('svg');
    if (!svgElement) return;

    // Create lightbox overlay
    const lightbox = document.createElement('div');
    lightbox.className = 'mermaid-lightbox';
    lightbox.innerHTML = `
        <div class="mermaid-lightbox-content">
            <button class="mermaid-lightbox-close bx bx-x" aria-label="Close"></button>
            <div class="mermaid-lightbox-diagram-wrapper">
                <div class="mermaid-lightbox-diagram"></div>
            </div>
        </div>
    `;

    // Clone and prepare SVG
    const clonedSvg = svgElement.cloneNode(true);
    clonedSvg.removeAttribute('width');
    clonedSvg.removeAttribute('height');
    clonedSvg.style.width = '100%';
    clonedSvg.style.height = '100%';

    const diagramContainer = lightbox.querySelector('.mermaid-lightbox-diagram');
    diagramContainer.appendChild(clonedSvg);

    // Add controls to lightbox
    const wrapper = lightbox.querySelector('.mermaid-lightbox-diagram-wrapper');
    const lightboxDiagramId = `${diagramId}-lightbox`;
    createControlButtons(wrapper, lightboxDiagramId);

    document.body.appendChild(lightbox);

    // Initialize pan-zoom after layout completes
    setTimeout(() => {
        const panZoom = initPanZoom(clonedSvg, lightboxDiagramId);
        if (panZoom) {
            panZoom.resize();
            panZoom.fit();
            panZoom.center();
        }
    }, 100);

    // Close handlers
    const closeLightbox = () => {
        if (panZoomInstances.has(lightboxDiagramId)) {
            try {
                panZoomInstances.get(lightboxDiagramId).destroy();
            } catch (e) {
                console.warn('Failed to destroy lightbox pan-zoom:', e);
            }
            panZoomInstances.delete(lightboxDiagramId);
        }
        lightbox.remove();
    };

    lightbox.querySelector('.mermaid-lightbox-close').addEventListener('click', closeLightbox);
    lightbox.addEventListener('click', (e) => {
        if (e.target === lightbox) closeLightbox();
    });

    // ESC key to close
    const escHandler = (e) => {
        if (e.key === 'Escape') {
            closeLightbox();
            document.removeEventListener('keydown', escHandler);
        }
    };
    document.addEventListener('keydown', escHandler);
}
```

### Hauptverbesserungsfunktion

Das verbindet alles und wird nach Mermaids Rendern genannt:

```javascript
export function enhanceMermaidDiagrams() {
    const diagrams = document.querySelectorAll('.mermaid[data-processed="true"]');

    diagrams.forEach(diagram => {
        const svgElement = diagram.querySelector('svg');
        if (!svgElement) return;

        // CRITICAL: Remove inline max-width constraint that Mermaid adds
        svgElement.style.maxWidth = 'none';

        // Wrap diagram with controls
        const diagramId = wrapDiagramWithControls(diagram);

        // Initialize pan/zoom and auto-fit
        const panZoom = initPanZoom(svgElement, diagramId);
        if (panZoom) {
            // Fit diagram to container by default
            setTimeout(() => {
                panZoom.resize();
                panZoom.fit();
                panZoom.center();
            }, 100);
        }
    });

    // Set up event delegation for control buttons (only once)
    if (!document.body.hasAttribute('data-mermaid-controls-initialized')) {
        document.body.addEventListener('click', handleControlClick);
        document.body.setAttribute('data-mermaid-controls-initialized', 'true');
    }
}
```

**Kritische Korrektur:** Meerjungfrau gilt eine Inline `style="max-width: 1020px"` zu SVG-Elementen, was die Anzeige in voller Breite verhindert. Das Entfernen ist für ein korrektes responsives Verhalten unerlässlich.

## Themenintegration

Der Themenschalter stellt sicher, dass Diagramme beim Umschalten zwischen hellen und dunklen Modi korrekt re-rendern:

```javascript
import { enhanceMermaidDiagrams } from './mermaid_enhancements';

const loadMermaid = async (theme) => {
    if (!window.mermaid) return;
    try {
        window.mermaid.initialize({
            startOnLoad: false,
            theme,
            themeVariables: {
                background: 'transparent'
            }
        });
        await window.mermaid.run({
            querySelector: elementSelector,
        });

        // Enhance diagrams after rendering completes
        // Use requestAnimationFrame for better timing
        await new Promise(resolve => {
            requestAnimationFrame(() => {
                requestAnimationFrame(() => {
                    enhanceMermaidDiagrams();
                    resolve();
                });
            });
        });
    } catch (err) {
        console.error('Mermaid render error:', err);
    }
};
```

Verwendung `requestAnimationFrame` zweimal stellt sicher, dass der Browser das Malen des SVG abgeschlossen hat, bevor wir versuchen, es zu verbessern.

## Cloudflare Rocket Loader Kompatibilität

Cloudflare Rocket Loader kann die Ausführung von JavaScript verzögern und die Initialisierung unterbrechen. Hier ist die kugelsichere Lösung:

```javascript
// Wait for all dependencies to load with exponential backoff
function waitForDependencies(maxAttempts = 50) {
    return new Promise((resolve) => {
        let attempts = 0;

        const checkDependencies = () => {
            attempts++;

            const depsReady =
                typeof window.hljs !== 'undefined' &&
                typeof window.mermaid !== 'undefined' &&
                typeof window.Alpine !== 'undefined' &&
                typeof window.htmx !== 'undefined';

            if (depsReady) {
                console.log('All dependencies loaded after', attempts, 'attempts');

                // Start Alpine.js now that it's loaded
                if (window.Alpine && !window.Alpine.version) {
                    try {
                        window.Alpine.start();
                        console.log('Alpine.js started');
                    } catch (err) {
                        console.error('Failed to start Alpine:', err);
                    }
                }

                resolve();
            } else if (attempts >= maxAttempts) {
                console.warn('Timeout waiting for dependencies');
                resolve(); // Continue anyway
            } else {
                // Retry with exponential backoff
                const delay = Math.min(50 * Math.pow(1.2, attempts), 500);
                setTimeout(checkDependencies, delay);
            }
        };

        checkDependencies();
    });
}

// Robust initialization
async function safeInitialize() {
    try {
        await waitForDependencies();

        if (document.readyState === 'loading') {
            await new Promise(resolve => {
                document.addEventListener('DOMContentLoaded', resolve, { once: true });
            });
        }

        await initializePage();
    } catch (err) {
        console.error('Failed to initialize page:', err);
        // Retry once after delay
        setTimeout(() => {
            initializePage().catch(e => console.error('Retry failed:', e));
        }, 1000);
    }
}

safeInitialize();
```

Vergewissern Sie sich auch, dass Ihr Hauptskript die `data-cfasync="false"` Attribut, um es von Rocket Loader auszuschließen:

```html
<script src="~/js/dist/main.js" type="module" asp-append-version="true" data-cfasync="false"></script>
```

## Styling mit Tailwind/DaisyUI

Das CSS verwendet Tailwind Utility Klassen und benutzerdefinierte Styling für Polieren:

```css
/* Mermaid diagram wrapper */
.mermaid-wrapper {
    @apply relative rounded-lg overflow-hidden w-full;
    margin: 1rem 0;
}

.mermaid-wrapper .mermaid {
    @apply m-0 w-full;
    min-height: 500px;
    display: flex;
    align-items: center;
    justify-content: center;
    padding: 1rem;
}

.mermaid-wrapper .mermaid svg {
    width: 100% !important;
    height: auto !important;
    min-height: 450px;
}

/* Control buttons */
.mermaid-controls {
    @apply absolute top-2 right-2 flex gap-1 z-10;
    background: rgba(255, 255, 255, 0.9);
    border-radius: 0.5rem;
    padding: 0.25rem;
    box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
}

.dark .mermaid-controls {
    background: rgba(31, 41, 55, 0.95);
    box-shadow: 0 2px 8px rgba(0, 0, 0, 0.3);
}

.mermaid-control-btn {
    @apply p-2 rounded cursor-pointer transition-all duration-200;
    background: transparent;
    border: none;
    color: #4b5563;
    font-size: 1.25rem;
    display: flex;
    align-items: center;
    justify-content: center;
    width: 2rem;
    height: 2rem;
}

.mermaid-control-btn:hover {
    background: rgba(37, 99, 235, 0.1);
    color: #2563eb;
    transform: scale(1.1);
}

.dark .mermaid-control-btn {
    color: #9ca3af;
}

.dark .mermaid-control-btn:hover {
    background: rgba(55, 65, 81, 0.8);
    color: #60a5fa;
}

/* Lightbox */
.mermaid-lightbox {
    @apply fixed inset-0 z-50 flex items-center justify-center;
    background: rgba(0, 0, 0, 0.85);
    backdrop-filter: blur(4px);
    animation: fadeIn 0.2s ease-out;
}

.dark .mermaid-lightbox {
    background: rgba(0, 0, 0, 0.95);
}

.mermaid-lightbox-content {
    @apply relative w-11/12 h-5/6 bg-white rounded-lg shadow-2xl;
    max-width: 1400px;
}

.dark .mermaid-lightbox-content {
    @apply bg-gray-800;
}

.mermaid-lightbox-close {
    @apply absolute top-4 right-4 z-10 p-2 rounded-full cursor-pointer transition-all;
    background: rgba(0, 0, 0, 0.5);
    border: none;
    color: white;
    font-size: 2rem;
    width: 3rem;
    height: 3rem;
    display: flex;
    align-items: center;
    justify-content: center;
}

.mermaid-lightbox-close:hover {
    background: rgba(220, 38, 38, 0.8);
    transform: scale(1.1);
}

@keyframes fadeIn {
    from { opacity: 0; }
    to { opacity: 1; }
}
```

# Testen und Debuggen

Hier ist, wie alles zu überprüfen funktioniert:

## Ausgabe der Konsole

Bei richtiger Initialisierung sollten Sie sehen:

```
All dependencies loaded after 1 attempts
Alpine.js started
Highlight.js copy plugin registered
Highlight.js initialized on page load
Mermaid initialized on page load
Document is ready - all initializations complete
HTMX event listener registered successfully
```

## HTMX Dynamischer Inhalt

Nach HTMX-Swaps sollten Sie sehen:

```
HTMX afterSettle triggered for: contentcontainer
Highlight.js applied after HTMX swap
Mermaid initialized
Mermaid applied after HTMX swap
HTMX afterSettle complete for: contentcontainer
```

## Prüfliste

- [ ] Diagramme rendern auf der ersten Seite laden
- [ ] Pan/Zoom-Kontrollen
- [ ] Vollbild-Lichtkasten öffnet und schließt (X-Taste, klicken Sie draußen, ESC-Taste)
- [ ] PNG-Export erfasst vollständiges Diagramm (nicht nur Ecke)
- [ ] SVG Export bewahrt Vektoren
- [ ] Funktioniert nach HTMX Content Swap
- [ ] Thema Schalten Re-Renders Diagramme richtig
- [ ] Mobile responsive (Steuerungen bleiben sichtbar, Diagrammskala)
- [ ] Dunkel Modus Styling gilt richtig
- [ ] Tastatur-Zugänglichkeit (Tab zu Steuerelementen, Eingabe zum Aktivieren)

# Leistungserwägungen

1. **Faule Initialisierung** - Nur Diagramme verbessern, die auf der Seite vorhanden sind
2. **Bereinigung der Instanz** - Pan-Zoom-Instanzen zerstören, wenn Diagramme entfernt werden
3. **Delegation der Veranstaltung** - Einzelklick-Hörer verarbeitet alle Steuerknöpfe
4. **AnfrageAnimationFrame** - Besseres Timing als willkürliche SetTimeout-Werte
5. **Entschuldete Ausfuhren** - Schnellfeuer Export-Klicks verhindern

# Browser-Kompatibilität

Getestet und bearbeitet an:

- Chrom/Edge 90+
- Firefox 88+
- Safari 14+
- Mobile Browser (iOS Safari, Chrome Mobile)

**IE11 wird nicht unterstützt** durch moderne JavaScript-Funktionen (Const, Pfeilfunktionen, async/await, RequestAnimationFrame).

# Schlußfolgerung

Diese umfassende Erweiterung verwandelt statische Mermaid-Diagramme in interaktive, exportierbare Visualisierungen. Die Implementierung ist produktionsbereit, widerstandsfähig gegen Randfälle und bietet eine hervorragende Benutzererfahrung.

Schlüssel zum Mitnehmen:

- Entfernen Mermaid Inline `max-width` Einschränkungen für Vollbreitendiagramme
- Preserve viewBox beim Exportieren zur Erfassung des gesamten Diagramms
- AnimationFrame für Timing anstelle von willkürlichen Verzögerungen verwenden
- Handle Cloudflare Rocket Loader mit Abhängigkeitsprüfung und retry Logik
- Geben Sie mehrere Close-Methoden für Lightbox (Knopf, klicken Sie draußen, ESC)
- Event-Delegation für Performance mit vielen Diagrammen nutzen

## Verwendung des npm-Pakets

Anstatt Code zu kopieren, können Sie diese Funktionalität nun als npm-Paket installieren:

```bash
npm install @mostlylucid/mermaid-enhancements
```

```typescript
import { init } from '@mostlylucid/mermaid-enhancements';
import '@mostlylucid/mermaid-enhancements/styles.css';

await init();
```

Siehe [Veröffentlichen von Mermaid Enhancements als npm-Paket](/blog/publishingmermaidenhancementsnpm) für vollständige Dokumentation, Beispiele für die Integration von Frameworks und erweiterte Konfigurationsoptionen.

Der komplette Quellcode ist auch im Repository dieses Blogs unter `Mostlylucid/src/js/mermaid_enhancements.js` und als Open-Source npm-Paket bei [meistlucidweb/meistlucid-mermaid](https://github.com/scottgal/mostlylucidweb/tree/main/mostlylucid-mermaid).

## Beispieldiagramm

Hier ist ein komplexes Beispiel, das die Architektur des Content-Systems dieses Blogs zeigt:

```mermaid
graph TB
    subgraph Client["Client Browser"]
        A[User Request] -->|HTMX| B[Blog Controller]
        B -->|Cache Miss| C[Blog Service]
        C -->|File Mode| D[Markdown Service]
        C -->|DB Mode| E[EF Core Context]
        D -->|Parse| F[Markdig Pipeline]
        F -->|Render| G[HTML + Mermaid]
        E -->|Query| H[PostgreSQL]
        H -->|Full-Text Search| I[GIN Index]
        G -->|Enhance| J[mermaid_enhancements.js]
        J -->|Initialize| K[svg-pan-zoom]
        J -->|Add| L[Control Buttons]
        L -->|Export| M[html-to-image]
    end

    subgraph Background["Background Services"]
        N[File Watcher] -->|Change Detected| O[Saves to DB]
        O -->|Trigger| P[Translation Service]
        P -->|Batch| Q[EasyNMT API]
        Q -->|12 Languages| R[Translated Files]
    end 

    style A stroke:#22c55e,stroke-width:3px,color:#4ade80
    style G stroke:#3b82f6,stroke-width:3px,color:#60a5fa
    style J stroke:#f59e0b,stroke-width:3px,color:#f59e0b
    style K stroke:#ec4899,stroke-width:3px,color:#ec4899
    style M stroke:#8b5cf6,stroke-width:3px,color:#8b5cf6
```

Versuchen Sie, die Steuerelemente auf dem Diagramm oben klicken!