x-data
Alpine
Alpine.js · SVG · Datenvisualisierung · Kein Chart.js
Daten visualisieren mit Alpine.js
ohne Chart.js und ohne D3.js

Chart.js bringt 200 KB in euer JavaScript-Bundle. D3.js ist eine eigene Lernkurve. Für die meisten Dashboard-Widgets in Hyvä-Themes reichen Alpine.js und natives SVG – mit Tooltips, Animationen und vollständiger Barrierefreiheit.

15 Min. Lesezeit SVG · x-data · Balken · Linien · Donut · Tooltips Alpine.js 3.x · Tailwind CSS · Hyvä

1. Warum Alpine.js statt Chart.js für einfache Charts

Chart.js ist eine hervorragende Bibliothek – für Anwendungen, die komplexe, interaktive Visualisierungen benötigen. Für ein Dashboard-Widget mit fünf Balken oder einen Donut-Chart, der den Lagerbestand eines Produkts anzeigt, ist Chart.js jedoch massiv überdimensioniert. Die unkomprimierte Größe liegt bei rund 200 KB, der Render-Prozess nutzt Canvas statt SVG (was Skalierung auf hochauflösenden Displays erschwert) und die Barrierefreiheit ist ein bekanntes Problem – Canvas-Inhalte sind für Screenreader ohne zusätzliche aria-Beschreibungen unsichtbar.

SVG hingegen ist nativ im Browser, vektorbasiert, beliebig skalierbar ohne Schärfeverlust und vollständig im DOM – was bedeutet, dass Alpine.js direkt mit den SVG-Elementen interagieren kann wie mit jedem anderen HTML-Element. Ein rect-Element mit :height="calculateHeight(value)" ist ein gültiges Alpine-Binding. Ein polyline mit :points="dataToPoints(data)" ebenso. Kein Canvas-API, kein separater Render-Zyklus – Alpine's reaktives System kümmert sich um das Re-Rendering bei Datenänderungen automatisch. Für alle Charts, die kein spezifisches Canvas-Feature benötigen, ist Alpine.js mit SVG die überlegene Lösung auf Hyvä-Seiten.

2. SVG-Koordinatensystem und Viewbox verstehen

Das SVG-Koordinatensystem beginnt oben links mit (0,0) und wächst nach rechts (x) und nach unten (y). Das ist der häufigste Stolperstein beim Zeichnen von Charts: Balken wachsen von unten nach oben im visuellen Raum, aber im SVG-Koordinatensystem müssen sie von einem festen Y-Wert nach oben gezeichnet werden. Ein Balken mit dem Wert 80% bei einer Gesamthöhe von 200px hat eine SVG-Höhe von 160px (200 * 0.8), aber seinen Y-Startpunkt bei 40px (200 - 160). Die Formel: y = viewboxHeight - (value / maxValue) * viewboxHeight.

Das viewBox-Attribut (viewBox="0 0 600 300") definiert das interne Koordinatensystem unabhängig von der tatsächlichen Darstellungsgröße. Das SVG-Element selbst kann width="100%" haben und sich responsiv an den Container anpassen – die viewBox skaliert den Inhalt automatisch mit. Für Berechnungen in Alpine.js arbeitet man immer mit den viewBox-Koordinaten, nie mit den Pixeln des Containers. Das macht die Logik unabhängig von der tatsächlichen Bildschirmgröße.


// Alpine.js bar chart — SVG coordinate calculations
function barChart(data) {
  const W = 600, H = 300, PADDING = 40;
  const chartH = H - PADDING * 2;
  const chartW = W - PADDING * 2;

  return {
    data,
    hoveredIndex: -1,

    get maxValue() {
      return Math.max(...this.data.map(d => d.value));
    },

    // Calculate bar dimensions in SVG coordinates
    barRect(item, index) {
      const barW = (chartW / this.data.length) * 0.6;
      const gap  = chartW / this.data.length;
      const barH = (item.value / this.maxValue) * chartH;
      return {
        x: PADDING + index * gap + (gap - barW) / 2,
        y: PADDING + chartH - barH,
        width: barW,
        height: barH
      };
    },

    // Y-axis labels (5 ticks)
    get yTicks() {
      return Array.from({ length: 6 }, (_, i) => {
        const val = (this.maxValue / 5) * i;
        const y = PADDING + chartH - (val / this.maxValue) * chartH;
        return { val: Math.round(val), y };
      });
    }
  };
}

3. Balkendiagramm mit Alpine.js und SVG-Rect

Ein SVG-Balkendiagramm besteht aus wenigen Grundelementen: <rect>-Elemente für die Balken, <text>-Elemente für Beschriftungen und <line>-Elemente für Gitterlinien. Alpine.js iteriert über die Datenpunkte mit x-for und berechnet für jeden Datenpunkt die SVG-Koordinaten in Echtzeit. Wenn sich die Daten ändern – etwa weil der Nutzer einen Zeitraum-Filter ändert – aktualisiert Alpine alle rect-Attribute automatisch.

Das Highlight-Verhalten beim Hover ist in Alpine direkt umsetzbar: @mouseenter="hoveredIndex = index" und @mouseleave="hoveredIndex = -1" steuern die Farbe des Balkens via :fill="hoveredIndex === index ? '#0d9488' : '#14b8a6'". Wichtig für Barrierefreiheit: Jedes <rect>-Element bekommt tabindex="0", role="graphics-symbol" und aria-label="Kategorie: 450 Euro". Das erlaubt Tastaturnutzern, durch die Balken zu navigieren und die Werte via Screenreader zu hören.

4. Liniendiagramm mit SVG-Polyline und Bezier-Kurven

Für ein Liniendiagramm berechnet Alpine.js aus den Datenpunkten eine SVG-Koordinatenliste und setzt diese als points-Attribut einer <polyline>. Die Umrechnung: x = PADDING + (index / (data.length - 1)) * chartWidth, y = PADDING + chartHeight - (value / maxValue) * chartHeight. Das Ergebnis ist eine gezackte Linie, die die Datenpunkte verbindet. Für eine geglättete Kurve verwendet man <path> mit Bezier-Kurven: Die Kontrollpunkte werden als ein Drittel der Distanz zwischen benachbarten Punkten berechnet.

Der Bereich unterhalb der Linie (Area Chart) wird mit einem <path> mit identischen Koordinaten plus einem Rückweg entlang der X-Achse gezeichnet und mit einem linearen SVG-Gradient von Teal zu transparent gefüllt. Das <linearGradient>-Element sitzt in einem <defs>-Block im SVG. Alpine bindet nur die dynamischen Attribute – der Gradient selbst ist statisches SVG. Diese Aufteilung ist wichtig: Statische SVG-Strukturen in den HTML-Markup, dynamische Werte als Alpine-Bindings.


// Smooth bezier curve path for line chart
function pointsToPath(points) {
  if (points.length < 2) return '';
  let d = `M ${points[0].x} ${points[0].y}`;

  for (let i = 0; i < points.length - 1; i++) {
    const curr = points[i];
    const next = points[i + 1];
    const cpX = (curr.x + next.x) / 2;
    // Cubic bezier — control points at horizontal midpoint
    d += ` C ${cpX} ${curr.y}, ${cpX} ${next.y}, ${next.x} ${next.y}`;
  }
  return d;
}

// Area path: line + return along x-axis
function pointsToAreaPath(points, baseY) {
  const line = pointsToPath(points);
  const last = points[points.length - 1];
  const first = points[0];
  return `${line} L ${last.x} ${baseY} L ${first.x} ${baseY} Z`;
}

5. Donut-Chart mit SVG-Stroke-Dashoffset

Ein Donut-Chart besteht aus einem <circle>-Element, dessen Umfang über stroke-dasharray und stroke-dashoffset als Kreisbögen dargestellt wird. Der Trick: stroke-dasharray wird auf den Umfang des Kreises gesetzt (2 * π * r), und stroke-dashoffset bestimmt, wie viel des Kreises sichtbar ist. Ein Offset von 0 zeigt 100%, ein Offset gleich dem Umfang zeigt 0%. Für mehrere Segmente rotiert man jeden Kreis um den kumulierten Offset der vorherigen Segmente via transform="rotate(-90 cx cy)" plus einem zusätzlichen Versatz.

Alpine.js berechnet die Dasharray- und Dashoffset-Werte für jedes Segment in einem Getter: get segments() { ... }. Wenn die Daten sich ändern – etwa weil ein Filter angewendet wird –, aktualisiert Alpine die SVG-Attribute automatisch. Eine CSS-Transition auf stroke-dashoffset erzeugt eine flüssige Animation beim Datenwechsel ohne JavaScript-Animationslogik. Der Donut-Chart-Mittelpunkt zeigt mit <text> den Gesamtwert oder den Prozentwert des ausgewählten Segments an – gesteuert über Alpine's reaktiven State.

6. Tooltips mit Alpine und SVG-Foreignobject

Tooltips in SVG-Charts können entweder als natives SVG-<g>-Element mit <rect>- und <text>-Elementen implementiert werden, oder als HTML über <foreignObject>. Letzteres ist flexibler, weil normales HTML-Markup mit Tailwind-Klassen genutzt werden kann. Das <foreignObject>-Element wird absolut im SVG positioniert und zeigt HTML-Inhalt an. Alpine bindet die Position via :x und :y an den State, der bei @mouseenter der Balken oder Datenpunkte aktualisiert wird.

Die Tooltip-Position muss den SVG-Viewport berücksichtigen: Nahe an den Rändern muss der Tooltip auf die andere Seite des Cursors ausweichen. Alpine berechnet das mit einer einfachen Prüfung: Wenn der X-Wert des Datenpunkts größer als die Hälfte der viewBox-Breite ist, erscheint der Tooltip links statt rechts. Die HTML-Variante über <foreignObject> erlaubt außerdem native Tailwind-Klassen für Schatten, Border-Radius und Schriftformatierung – alles, was natives SVG-Rendering deutlich aufwändiger machen würde.

7. CSS-Animationen für Chart-Einblendung

Balken, die beim Laden der Seite von 0 auf ihre finale Höhe wachsen, machen Dashboards visuell ansprechender und führen den Blick des Nutzers zu den wichtigen Werten. In SVG lässt sich das mit CSS-Animationen auf transform: scaleY() kombiniert mit transform-origin: bottom center realisieren – aber SVG und HTML haben unterschiedliche Transform-Origins, was zu Browser-Inkonsistenzen führt. Die robustere Methode: Die Balken beginnen mit height="0" und werden via CSS-Transition zur finalen Höhe animiert. Alpine setzt die Anfangswerte beim Mount und die finalen Werte nach einem requestAnimationFrame-Delay, der sicherstellt, dass die CSS-Transition greift.

Für Liniendiagramme ist die Einblendanimation eleganter mit dem stroke-dashoffset-Trick: Die Linie wird gezeichnet, als würde man sie malen. Dazu setzt man stroke-dasharray auf die Gesamtlänge des Pfads (ermittelt mit path.getTotalLength()) und animiert stroke-dashoffset von der Gesamtlänge auf 0. Das ergibt einen flüssigen Zeicheneffekt, der ausschließlich CSS verwendet und keine JavaScript-Animationsschleife benötigt.


// Animate line chart on mount using stroke-dashoffset trick
animateLine() {
  this.$nextTick(() => {
    const path = this.$refs.linePath;
    if (!path) return;

    const length = path.getTotalLength();

    // Set initial state — invisible line
    path.style.strokeDasharray = length;
    path.style.strokeDashoffset = length;
    path.style.transition = 'none';

    // Force browser to apply initial state before animating
    path.getBoundingClientRect();

    // Start animation on next frame
    requestAnimationFrame(() => {
      path.style.transition = 'stroke-dashoffset 1.2s cubic-bezier(0.4, 0, 0.2, 1)';
      path.style.strokeDashoffset = '0';
    });
  });
}

8. Barrierefreiheit: ARIA-Tabelle als Chart-Alternative

Ein SVG-Chart ist für Screenreader-Nutzer nur zugänglich, wenn die darin dargestellten Daten auch in Textform vorhanden sind. Die beste Lösung ist eine visuelle versteckte Datentabelle mit denselben Daten, die das Chart visualisiert. Diese Tabelle wird mit class="sr-only" aus dem visuellen Fluss entfernt, bleibt aber im DOM und im Accessibility Tree sichtbar. Das SVG-Element selbst bekommt role="img" und aria-labelledby, das auf eine Überschrift außerhalb des SVG zeigt, die das Chart beschreibt.

Für interaktive Charts – wo Hover-Tooltips Werte anzeigen – müssen diese Werte auch für Tastaturnutzer zugänglich sein. Jeder interaktive Datenpunkt (Kreis auf der Linie, Balken) bekommt tabindex="0" und aria-describedby, das auf ein verstecktes Element mit dem vollständigen Wert zeigt. @focus-Handler zeigen denselben Tooltip wie @mouseenter, sodass Tastaturnutzer dieselben Informationen erhalten wie Mausnutzer. Das ist keine optionale Ergänzung – ohne diese Implementierung scheitert der Chart an WCAG 2.1 Erfolgskriterium 1.1.1 (Nicht-Text-Inhalt).

9. Alpine SVG-Charts vs. Chart.js im Vergleich

Chart.js und D3.js sind die Industriestandards für komplexe Datenvisualisierungen. Für einfache Dashboard-Widgets auf Hyvä-Seiten ist der Overhead aber nicht gerechtfertigt. Der Vergleich zeigt, wo Alpine.js mit SVG die bessere Wahl ist und wo Chart.js trotzdem sinnvoll bleibt.

Kriterium Chart.js / D3.js Alpine.js + SVG Vorteil Alpine
Bundle-Größe 200–500 KB 0 KB extra Alpine.js ohnehin geladen
Skalierbarkeit Canvas (Pixelabhängig) SVG (vektorbasiert) Scharf auf Retina-Displays
Barrierefreiheit Canvas: Screenreader-blind SVG: vollständig im DOM ARIA direkt auf SVG-Elementen
Tailwind-Styling Nur via Konfig-Objekt Native SVG-Attribute Direktes Tailwind-Farbsystem
Komplexe Charts Vollständig (Scatter, Bubble…) Balken, Linie, Donut Chart.js für Nischenfälle

Die Grenze zwischen Alpine.js-SVG und Chart.js liegt bei der Komplexität: Scatter-Plots, Bubble-Charts, statistische Visualisierungen mit Achsenskalierung, Logarithmik oder Regression gehören in Chart.js oder D3.js. Balken, Linien, Donut und einfache Sparklines gehören in Alpine.js mit SVG. Für Hyvä-Shops, wo Dashboard-Widgets für Umsatz, Bestellungen und Conversion-Rate gezeigt werden, ist Alpine.js in 95% der Fälle ausreichend.

Mironsoft

Alpine.js Dashboard · Hyvä Entwicklung · Datenvisualisierung

Dashboard-Widgets ohne externe Chart-Bibliothek?

Wir entwickeln leichtgewichtige Datenvisualisierungen mit Alpine.js und SVG für Hyvä-Shops – schneller geladen, barrierefrei und vollständig im Tailwind-Design.

Chart-Analyse

Prüfung bestehender Chart-Bibliotheken und Migrationspfad zu Alpine SVG

Widget-Entwicklung

Balken, Linien, Donut mit Tooltips, Animationen und ARIA-Barrierefreiheit

Performance

Chart.js-Entfernung, Bundle-Reduktion und Core Web Vitals Optimierung

10. Zusammenfassung

Daten mit Alpine.js und SVG zu visualisieren ist für alle gängigen Chart-Typen machbar und in vielen Fällen besser als Chart.js: 0 KB zusätzliches Bundle-Gewicht, vektorbasierte Darstellung ohne Pixelprobleme auf Retina-Displays, ARIA-Attribute direkt auf SVG-Elementen und volle Kontrolle über das Erscheinungsbild mit Tailwind-Farben. Die Berechnung von SVG-Koordinaten in Alpine-Gettern ist das zentrale Pattern – einmal verstanden, lässt sich jeder Chart-Typ damit umsetzen.

Der wichtigste Grundsatz: Barrierefreiheit ist kein Nachgedanke. Eine versteckte sr-only-Tabelle mit denselben Daten, ARIA-Labels auf interaktiven Elementen und Tastaturnavigation durch Datenpunkte machen den Unterschied zwischen einem Chart, der nur visuell kommuniziert, und einem, der alle Nutzer einschließt. Alpine.js macht das einfacher als jede Canvas-basierte Bibliothek, weil SVG vollständig im DOM lebt und ARIA-Attribute sich direkt binden lassen.

Alpine.js SVG-Charts — Das Wichtigste auf einen Blick

SVG-Koordinaten

Y-Achse wächst nach unten. Balken-Y: viewboxHeight - (value/max)*chartH. ViewBox definiert Koordinatensystem unabhängig von der Darstellungsgröße.

Donut-Chart

stroke-dasharray = 2*π*r. stroke-dashoffset steuert sichtbaren Anteil. Segmente werden durch Rotation kumulierter Offsets positioniert.

Animation

Linien mit stroke-dashoffset von Gesamtlänge zu 0. Balken mit CSS-Transition auf height. Kein JavaScript-Animationsloop nötig.

Barrierefreiheit

sr-only Datentabelle, role="img" am SVG, aria-label auf interaktiven Elementen, tabindex="0" für Tastaturnavigation durch Datenpunkte.

11. FAQ: Alpine.js Charts ohne Chart.js

1Kann Alpine.js Chart.js ersetzen?
Für Balken, Linien, Donut, Sparklines: ja. 200 KB gespart, vektorbasiert, ARIA direkt auf SVG-Elementen.
2Warum SVG statt Canvas?
SVG ist DOM – Alpine interagiert direkt. Canvas ist für Screenreader blind. SVG skaliert vektorbasiert auf Retina-Displays.
3Balken-Y-Koordinaten berechnen?
Y = PADDING + chartHeight - (value/max)*chartHeight. SVG wächst nach unten. Höhe = (value/max)*chartHeight.
4stroke-dashoffset Animations-Trick?
dasharray = Pfadlänge (getTotalLength()), dashoffset = Pfadlänge → 0. CSS-Transition erzeugt Zeicheneffekt ohne JS-Loop.
5SVG-Chart barrierefrei machen?
role="img" + aria-labelledby am SVG. sr-only Datentabelle. tabindex="0" + aria-label auf interaktiven Elementen.
6Donut-Chart implementieren?
stroke-dasharray = 2*π*r. Dashoffset = Umfang - (anteil * Umfang). Rotation für Segmente per transform + kumulierter Offset.
7Tailwind-Farben in SVG-Attributen?
class="fill-teal-500" funktioniert in modernen Browsern. Alpine :fill-Bindings mit Hex-Werten sind immer zuverlässig.
8Tooltips in SVG-Charts?
foreignObject für HTML innerhalb SVG – Tailwind-Klassen funktionieren. Position via :x/:y an Alpine-State binden.
9Wann trotzdem Chart.js verwenden?
Scatter, Bubble, Radar, logarithmische Skalierung, statistische Regression. Für alles andere: Alpine.js mit SVG.
10Chart bei neuen Daten aktualisieren?
Alpine's Reaktivität übernimmt das automatisch. this.data aktualisieren – alle Getter und SVG-Attribute werden neu berechnet.