Symfony UX Chart.js: Daten ohne Custom-JS visualisieren
AI generated
SF
{ }
Symfony · UX · Chart.js · Dashboard · PHP
Symfony UX Chart.js:
Daten ohne Custom-JS visualisieren

Dashboards und Diagramme in Symfony erfordern normalerweise eigenes JavaScript: Chart.js initialisieren, Daten per AJAX laden, Optionen konfigurieren. Das Symfony UX Chart.js-Paket übernimmt das vollständig — Charts entstehen aus PHP-Objekten, werden als Twig-Komponenten eingebettet und von Symfony UX automatisch initialisiert.

15 Min. Lesezeit ChartBuilder · Datasets · Bar · Line · Doughnut · Turbo Symfony 7.x · symfony/ux-chartjs · Chart.js 4.x

1. Warum Symfony UX Chart.js statt manuellem JavaScript

Wer in Symfony ein Dashboard mit Diagrammen baut, beginnt typischerweise mit einem API-Endpunkt, der Daten als JSON liefert, einem eigenen JavaScript-Bundle, das Chart.js initialisiert und die Daten lädt, und einer Konfigurationsdatei für Farben, Achsen und Tooltips. Das sind mindestens drei separate Bausteine, die synchron gehalten werden müssen. Wenn sich das Datenschema ändert, muss die API angepasst werden, das JavaScript muss aktualisiert werden, und Tests müssen beide Seiten abdecken. Symfony UX Chart.js eliminiert diesen Dualismus: Das Diagramm wird vollständig in PHP konfiguriert und direkt als Twig-Komponente gerendert.

Das Paket symfony/ux-chartjs ist Teil des Symfony UX-Ökosystems und integriert Chart.js 4 als Symfony-nativen Baustein. Der ChartBuilder-Service erstellt Chart-Objekte mit PHP-API, die Twig-Funktion render_chart(chart) rendert den Canvas und übergibt die Konfiguration als JSON-Attribute. Das Stimulus-Controller-JavaScript, das Chart.js initialisiert, kommt vom Paket — es muss kein eigenes JavaScript geschrieben werden. Das Ergebnis: Ein Balken-Diagramm mit Doctrine-Daten in unter 30 Zeilen PHP und einer Zeile Twig. Für PHP-Teams, die Symfony kennen, ist das der direkteste Weg zu einem produktionsreifen Dashboard.

2. symfony/ux-chartjs installieren

Die Installation erfolgt über Composer, das Flex-Recipe übernimmt die Einrichtung automatisch. Das Paket registriert sich in config/bundles.php, das Stimulus-Controller-JavaScript wird über AssetMapper bereitgestellt, und der ChartBuilder-Service ist sofort im Container verfügbar. Mit AssetMapper (empfohlen) wird kein npm-Build-Schritt benötigt — das @symfony/ux-chartjs-Paket inklusive Chart.js wird über importmap ausgeliefert. Wer Webpack Encore nutzt, installiert zusätzlich das npm-Paket @symfony/ux-chartjs und führt yarn install aus.

Nach der Installation prüft man, ob der Stimulus-Controller im importmap registriert ist: bin/console debug:asset sollte @symfony/ux-chartjs auflisten. Der ChartBuilder-Service wird automatisch über Dependency Injection in Controller, ViewModel oder Service injiziert. Die Chart.js-Version, die das Paket mitbringt, ist fest verankert und wird mit dem Symfony-Paket getest — ein manuelles Versions-Management für Chart.js ist nicht nötig. Das ist ein signifikanter Unterschied zu einer manuellen Chart.js-Integration, bei der npm-Versionen manuell aktuell gehalten werden müssen.


<?php

declare(strict_types=1);

namespace App\Controller;

use App\Repository\OrderRepository;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Attribute\Route;
use Symfony\UX\Chartjs\Builder\ChartBuilderInterface;
use Symfony\UX\Chartjs\Model\Chart;

final class DashboardController extends AbstractController
{
    public function __construct(
        private readonly ChartBuilderInterface $chartBuilder,
        private readonly OrderRepository $orderRepository,
    ) {}

    #[Route('/admin/dashboard', name: 'admin_dashboard')]
    public function index(): Response
    {
        // Build a bar chart from Doctrine data — no JavaScript needed
        $chart = $this->chartBuilder->createChart(Chart::TYPE_BAR);

        // Load monthly revenue data from the database
        $monthlyRevenue = $this->orderRepository->getMonthlyRevenue(months: 12);

        $chart->setData([
            'labels' => array_column($monthlyRevenue, 'month'),
            'datasets' => [
                [
                    'label'           => 'Umsatz (EUR)',
                    'data'            => array_column($monthlyRevenue, 'revenue'),
                    'backgroundColor' => 'rgba(59, 130, 246, 0.7)',
                    'borderColor'     => 'rgba(59, 130, 246, 1)',
                    'borderWidth'     => 1,
                ],
            ],
        ]);

        $chart->setOptions(['responsive' => true, 'maintainAspectRatio' => false]);

        return $this->render('admin/dashboard.html.twig', ['revenueChart' => $chart]);
    }
}

3. ChartBuilder: Charts aus PHP-Objekten erstellen

Der ChartBuilderInterface ist der zentrale Einstiegspunkt in Symfony UX Chart.js. Die Methode createChart() akzeptiert einen der Chart.js-Typen als Konstante der Chart-Klasse: Chart::TYPE_BAR, Chart::TYPE_LINE, Chart::TYPE_PIE, Chart::TYPE_DOUGHNUT, Chart::TYPE_POLAR_AREA, Chart::TYPE_RADAR und Chart::TYPE_BUBBLE. Das zurückgegebene Chart-Objekt hat drei Haupt-Methoden: setData() für Labels und Datasets, setOptions() für alle Chart.js-Optionen, und setPlugins() für Chart.js-Plugin-Konfiguration.

Die Datenstruktur, die man setData() übergibt, entspricht exakt der Chart.js-JavaScript-API — alle Chart.js-Dokumentationsbeispiele lassen sich direkt in PHP-Arrays übersetzen. Das ist ein wichtiger Vorteil: Team-Mitglieder, die Chart.js bereits kennen, können die Konfiguration ohne zusätzliches Lernaufwand übersetzen. Symfony UX Chart.js serialisiert das PHP-Array zu JSON und übergibt es als data-model-value-Attribut an den Canvas — der Stimulus-Controller liest dieses Attribut und initialisiert Chart.js. Der PHP-Code sieht und bearbeitet immer typsichere PHP-Strukturen, JavaScript erhält am Ende saubes JSON.

4. Datasets konfigurieren und aus Doctrine befüllen

Mehrere Datasets in einem Chart.js-Diagramm ermöglichen den Vergleich von Datenreihen — z. B. Umsatz und Bestellanzahl über denselben Zeitraum. Jedes Dataset ist ein assoziatives Array mit label, data und Styling-Eigenschaften. Für Liniendiagramme kommen fill, tension und pointRadius hinzu. Die data-Array-Einträge können einfache Zahlen oder Objekt-Strukturen für Scatter- und Bubble-Charts sein.

Doctrine-Queries für Dashboard-Daten folgen einem klaren Muster: Eine Repository-Methode aggregiert die Daten nach Zeitraum und gibt ein Array von assoziativen Arrays zurück. array_column() extrahiert dann die Label- und Datenspalten für das Chart.js-Dataset. Für komplexere Aggregationen — z. B. mehrere Datenreihen aus einem einzigen Query — transformiert man das Ergebnis mit array_map() oder einer eigenen Transformer-Methode. Performance ist wichtig: Dashboard-Queries sollten mit Doctrine-Caching oder einem separaten Cache-Layer (z. B. Symfony Cache) abgesichert werden, weil komplexe Aggregationen über große Datensätze teuer sein können.


<?php

declare(strict_types=1);

namespace App\Repository;

use App\Entity\Order;
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
use Doctrine\Persistence\ManagerRegistry;

/**
 * Provides aggregated order data for dashboard charts.
 */
final class OrderRepository extends ServiceEntityRepository
{
    public function __construct(ManagerRegistry $registry)
    {
        parent::__construct($registry, Order::class);
    }

    /**
     * Returns monthly revenue and order count for the last N months.
     *
     * @return array<int, array{month: string, revenue: float, orders: int}>
     */
    public function getMonthlyRevenue(int $months = 12): array
    {
        return $this->createQueryBuilder('o')
            ->select(
                "DATE_FORMAT(o.createdAt, '%Y-%m') AS month",
                'SUM(o.total) AS revenue',
                'COUNT(o.id) AS orders',
            )
            ->where('o.createdAt >= :from')
            ->setParameter('from', new \DateTimeImmutable("-{$months} months"))
            ->groupBy('month')
            ->orderBy('month', 'ASC')
            ->getQuery()
            ->getArrayResult();
    }

    /**
     * Returns per-category revenue for a doughnut chart.
     *
     * @return array<int, array{category: string, revenue: float}>
     */
    public function getRevenueByCategory(): array
    {
        return $this->createQueryBuilder('o')
            ->join('o.items', 'i')
            ->join('i.product', 'p')
            ->join('p.category', 'c')
            ->select('c.name AS category', 'SUM(i.price * i.quantity) AS revenue')
            ->groupBy('c.name')
            ->orderBy('revenue', 'DESC')
            ->getQuery()
            ->getArrayResult();
    }
}

5. Chart-Typen: Bar, Line, Doughnut und mehr

Die verschiedenen Chart.js-Typen decken unterschiedliche Visualisierungsanforderungen ab. Balkendiagramme (TYPE_BAR) eignen sich für Kategorievergleiche und Zeitreihen mit klaren Datenpunkten. Liniendiagramme (TYPE_LINE) zeigen Trends über Zeit besser, besonders mit fill: true für Area-Charts. Doughnut- und Pie-Charts (TYPE_DOUGHNUT, TYPE_PIE) visualisieren Anteile an einem Ganzen — z. B. Umsatzverteilung nach Kategorie. Radar-Charts (TYPE_RADAR) eignen sich für Mehrfach-Attribut-Vergleiche, etwa Performance-Metriken über mehrere Dimensionen.

Horizontale Balkendiagramme erreicht man in Chart.js 4 nicht mehr über einen eigenen Typ, sondern über die Option indexAxis: 'y' im TYPE_BAR-Chart. Gemischte Chart-Typen — z. B. Balken für absolute Werte und Linie für einen Durchschnitt im selben Diagramm — sind über das type-Feld im einzelnen Dataset möglich: 'type' => 'line' in einem Dataset eines Balkendiagramms erzeugt die Überlagerung. Symfony UX Chart.js übergibt alle diese Optionen transparent an Chart.js — es gibt keine PHP-spezifische Einschränkung gegenüber der nativen Chart.js-API.

6. Chart-Optionen und Styling konfigurieren

Die setOptions()-Methode akzeptiert das vollständige Chart.js-Options-Objekt als PHP-Array. Responsive-Design aktiviert man mit 'responsive' => true und 'maintainAspectRatio' => false — letzteres erlaubt es dem Chart, die volle Höhe seines Containers zu nutzen, was für Dashboard-Layouts wichtig ist. Achsenbeschriftungen, Gridlinien, Tick-Formatierung und Tooltip-Formatting werden alle über verschachtelte PHP-Arrays konfiguriert. Für Callback-Funktionen — etwa ein Tooltip, der Währungsformatierung benötigt — übergibt man JavaScript-Code-Strings direkt als Werte; Symfony UX Chart.js markiert diese beim Serialisieren als rohe JavaScript-Ausdrücke.

Für konsistente Farbpaletten in mehreren Charts empfiehlt sich eine zentrale PHP-Konstante oder ein Service, der die Farb-Arrays liefert. Das vermeidet Inkonsistenzen zwischen Charts und erleichtert Theme-Wechsel. Symfony-spezifisch: Der Dark-Mode des Browsers beeinflusst die Canvas-Darstellung nicht automatisch — man muss explizit Farben für prefers-color-scheme: dark über JavaScript oder einen eigenen Stimulus-Controller konfigurieren. Für die meisten Admin-Dashboards mit kontrolliertem Theme ist das kein Problem.

7. Charts in Twig einbetten und rendern

Das Einbetten eines Symfony UX Chart.js-Diagramms in Twig ist eine einzige Zeile: { { render_chart(revenueChart) } }. Hinter dieser Funktion verbirgt sich das Rendern eines <canvas>-Elements mit dem Stimulus-Controller-Attribut data-controller="symfony--ux-chartjs--chart" und den Chart-Daten als serialisiertes JSON-Attribut. Das Stimulus-JavaScript liest dieses Attribut beim DOM-Connect und initialisiert Chart.js mit der übergebenen Konfiguration. Die Größe des Canvas-Elements steuert man über HTML-Attribute oder CSS auf dem umhüllenden Container.

Für mehrere Charts auf einer Seite übergibt man mehrere Chart-Objekte an das Template und rendert jeden separat. Das funktioniert ohne Konflikte, weil jeder Stimulus-Controller-Instanz einen eigenen Canvas bekommt. Für barrierefreie Diagramme empfiehlt sich ein <title>-Element innerhalb des Canvas und eine tabellarische Alternativdarstellung. Symfony UX bietet dafür keine automatische Lösung — das muss manuell implementiert werden. Im zweiten Parameter von render_chart() können HTML-Attribute übergeben werden: render_chart(chart, {'class': 'w-full h-64'}) setzt Klassen direkt auf dem Canvas-Element.


{# templates/admin/dashboard.html.twig #}
{% extends 'admin/base.html.twig' %}

{% block content %}
<div class="grid grid-cols-1 lg:grid-cols-2 gap-6 p-6">

  {# Revenue bar chart — height controlled by the wrapper div #}
  <div class="bg-white rounded-2xl shadow p-6">
    <h2 class="text-lg font-bold text-slate-800 mb-4">Umsatz letzte 12 Monate</h2>
    <div style="height: 300px; position: relative;">
      { { render_chart(revenueChart, {'class': 'w-full h-full'}) } }
    </div>
  </div>

  {# Category doughnut chart #}
  <div class="bg-white rounded-2xl shadow p-6">
    <h2 class="text-lg font-bold text-slate-800 mb-4">Umsatz nach Kategorie</h2>
    <div style="height: 300px; position: relative;">
      { { render_chart(categoryChart, {'class': 'w-full h-full'}) } }
    </div>
  </div>

  {# Turbo Frame for a live-updating chart — reloads on user action #}
  <turbo-frame id="live-orders-chart" src="{ { path('admin_live_chart') } }">
    <div class="bg-white rounded-2xl shadow p-6">
      <h2 class="text-lg font-bold text-slate-800 mb-4">Bestellungen heute (live)</h2>
      {# Content loaded asynchronously via Turbo Frame #}
    </div>
  </turbo-frame>

</div>
{% endblock %}

{# The chart JavaScript is initialized automatically by the Stimulus controller.
   No manual new Chart() call is needed — Symfony UX handles initialization. #}

8. Live-Charts mit Turbo Frames aktualisieren

Die Kombination von Symfony UX Chart.js mit Turbo Frames ermöglicht Charts, die sich ohne Seitenreload aktualisieren. Ein Turbo Frame umhüllt den Chart-Container, ein Link oder Timer löst einen Fetch-Request aus, und Turbo ersetzt den Frame mit dem neuen Chart. Das funktioniert, weil render_chart() einen Canvas mit dem Stimulus-Controller rendert — nach dem Frame-Update initialisiert Stimulus den neuen Canvas automatisch. Es ist kein eigenes JavaScript nötig, um den alten Chart zu zerstören und einen neuen zu erstellen.

Für Zeitraum-Selektoren — der Nutzer wählt „letzte 7 Tage" und der Chart aktualisiert sich — platziert man den Selector und den Chart in einem gemeinsamen Turbo Frame. Der Selector-Link trägt den gewählten Zeitraum als Query-Parameter, der Turbo-Fetch lädt den Controller-Endpunkt mit dem neuen Parameter, und der Controller baut einen neuen Chart mit den aktuellen Daten. Das Pattern ist klar und wartbar: Ein Controller, ein Twig-Template, ein Frame. Kein AJAX-Handler, kein JavaScript-Fetch, kein State in JavaScript. Symfony UX Chart.js und Symfony Turbo zusammen lösen dieses häufige Dashboard-Muster vollständig in PHP und Twig.

9. Symfony UX Chart.js vs. manuelle Implementierung

Der Vergleich macht den Produktivitätsunterschied zwischen Symfony UX Chart.js und einer manuellen Chart.js-Integration deutlich.

Aufgabe Manuelle Integration Symfony UX Chart.js Vorteil
Chart initialisieren new Chart(canvas, config) in JS render_chart(chart) in Twig Kein JavaScript nötig
Daten laden API-Endpunkt + fetch() in JS Direkt aus PHP/Doctrine Keine separate API nötig
Chart-Update (Zeitraum) AJAX + chart.update() in JS Turbo Frame Reload Kein JavaScript-State
Chart.js-Version Manuell via npm gepflegt Mit Symfony-Paket verwaltet Kein npm-Versionsmanagement
Barrierefreiheit Manuell implementiert Manuell implementiert Beide auf gleichem Stand

Die manuelle Chart.js-Integration ist sinnvoll, wenn hochkomplexe Custom-Chart-Typen, eigene Plugins oder Animationen benötigt werden, die über die PHP-API nicht konfigurierbar sind. Für Standard-Dashboard-Charts mit Doctrine-Daten ist Symfony UX Chart.js die klarere Lösung — weniger Code, weniger Schichten, einfacheres Debugging.

Mironsoft

Symfony-Dashboard-Entwicklung, Datenvisualisierung und Symfony UX-Integration

Symfony-Dashboard mit Charts ohne JavaScript-Overhead?

Wir entwickeln Symfony-Dashboards mit Symfony UX Chart.js — von der Doctrine-Datenaggregation über Chart-Konfiguration bis zur Turbo-Frame-Integration für Live-Updates ohne eigenes JavaScript.

Dashboard-Architektur

Chart-Typen, Datenaggregation und Performance-Optimierung für Symfony-Admin-Dashboards

Live-Charts

Turbo Frame-Integration für dynamische Chart-Updates ohne AJAX-Endpoints oder JavaScript-State

Symfony UX Setup

symfony/ux-chartjs, AssetMapper und Stimulus-Controller-Integration für bestehende Symfony-Projekte

10. Zusammenfassung

Symfony UX Chart.js macht Datenvisualisierung in Symfony-Projekten zu einer reinen PHP-Aufgabe. Der ChartBuilder-Service erstellt Chart-Objekte aus PHP-Arrays, die exakt der Chart.js-API entsprechen. render_chart() in Twig rendert den Canvas und initialisiert Chart.js automatisch über den Stimulus-Controller — kein eigenes JavaScript, kein API-Endpunkt, kein new Chart(). Alle Chart.js-Typen, Datasets, Optionen und Plugins sind über die PHP-API zugänglich. Doctrine-Daten fließen direkt in die Chart-Konfiguration, ohne eine zwischengeschaltete JSON-API.

Der größte Produktivitätsgewinn liegt in der Turbo-Frame-Integration: Zeitraum-Selektoren, Filter und Live-Updates funktionieren ohne JavaScript-State-Management. Ein Frame-Reload, ein neuer Chart, kein manueller chart.update()-Aufruf. Für PHP-Teams, die Symfony-Dashboards bauen, ist Symfony UX Chart.js die direkteste Route zu wartbaren, performanten Datenvisualisierungen — vollständig im vertrauten PHP-Symfony-Ökosystem.

Symfony UX Chart.js — Das Wichtigste auf einen Blick

ChartBuilder

$this->chartBuilder->createChart(Chart::TYPE_BAR) erstellt ein Chart-Objekt. setData() und setOptions() konfigurieren alle Chart.js-Eigenschaften als PHP-Array.

Twig-Rendering

{ { render_chart(chart) } } rendert den Canvas, übergibt JSON-Konfiguration und initialisiert Chart.js automatisch via Stimulus — eine Zeile Twig reicht.

Doctrine-Integration

Repository-Methoden aggregieren Daten, array_column() extrahiert Labels und Datenwerte. Keine separate JSON-API zwischen Doctrine und Chart.js nötig.

Live-Updates

Turbo Frame umhüllt den Chart-Container — Zeitraum-Filter lösen Frame-Reloads aus, Stimulus initialisiert den neuen Chart automatisch. Kein JavaScript-State.

11. FAQ: Symfony UX Chart.js und Datenvisualisierung

1Was ist symfony/ux-chartjs?
Symfony UX-Paket für Chart.js 4. Charts via PHP-API konfigurieren, per Twig rendern — kein eigenes JavaScript. Stimulus-Controller übernimmt Initialisierung automatisch.
2Chart.js separat installieren?
Nein. Chart.js ist Dependency des Pakets. Mit AssetMapper über importmap bereitgestellt — kein npm install oder Versionsmanagement nötig.
3Welche Chart-Typen?
Bar, Line, Pie, Doughnut, PolarArea, Radar, Bubble. Gemischte Typen per Dataset-type-Feld. Horizontale Balken via indexAxis: 'y'.
4Doctrine-Daten in Chart laden?
Repository gibt Array zurück, array_column() extrahiert Labels und Werte, direkt an setData() übergeben. Keine JSON-API oder AJAX nötig.
5Vollständige Chart.js-Optionen nutzbar?
Ja. setOptions() akzeptiert das vollständige Chart.js-Options-Objekt als PHP-Array. Alle Dokumentationsbeispiele direkt übersetzbar.
6Chart ohne Seitenreload aktualisieren?
Turbo Frame um Chart-Container. Selektor-Link im Frame löst Fetch aus. Turbo ersetzt Frame, Stimulus initialisiert Chart neu — kein JavaScript-State.
7Größe des Canvas steuern?
Container mit fester Höhe: style="height: 300px; position: relative;". Canvas mit w-full h-full. Optionen: responsive: true + maintainAspectRatio: false.
8Webpack Encore oder AssetMapper?
AssetMapper empfohlen: kein Build-Schritt, kein npm. Webpack Encore: zusätzlich npm install @symfony/ux-chartjs und Import in Encore-Entry nötig.
9Mehrere Charts auf einer Seite?
Ja. Mehrere Chart-Objekte als Template-Variablen, separate render_chart()-Aufrufe. Jeder Chart bekommt eigenen Canvas, Stimulus-Instanzen sind unabhängig.
10Controller mit Charts testen?
Chart-Objekt ist einfaches PHP — unit-testbar mit gemocktem ChartBuilderInterface. Integrationstests via WebTestCase können das Canvas-Element per CSS-Selektor prüfen.