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.
Inhaltsverzeichnis
- 1. Warum Alpine.js statt Chart.js für einfache Charts
- 2. SVG-Koordinatensystem und Viewbox verstehen
- 3. Balkendiagramm mit Alpine.js und SVG-Rect
- 4. Liniendiagramm mit SVG-Polyline und Bezier-Kurven
- 5. Donut-Chart mit SVG-Stroke-Dashoffset
- 6. Tooltips mit Alpine und SVG-Foreignobject
- 7. CSS-Animationen für Chart-Einblendung
- 8. Barrierefreiheit: ARIA-Tabelle als Chart-Alternative
- 9. Alpine SVG-Charts vs. Chart.js im Vergleich
- 10. Zusammenfassung
- 11. FAQ
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.