Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Chartjs Bundle Laravel Package

mukadi/chartjs-bundle

A Laravel bundle for rendering Chart.js charts from PHP. Build chart datasets and options server-side, then output them to Chart.js in your views, keeping chart configuration in your controllers and blade templates instead of hand-written JavaScript.

View on GitHub
Deep Wiki
Context7

Getting Started

Read this first: this is a Symfony bundle, not a Laravel package. It ships a DependencyInjection extension, a bundle class and a Twig extension, and its headline feature — "build charts directly from your ORM Entities" — is built around Doctrine ORM. In a Laravel/Eloquent app, the bundle registration and the Doctrine data provider are dead weight. What is actually reusable is the small, framework-agnostic core library it depends on: Mukadi\Chart\Chart, Mukadi\Chart\Dataset and Mukadi\Chart\ChartBuilder (serialize to the Chart.js data/options shape) plus the DataProviderInterface extension point.

So the realistic Laravel play is: use the core classes as a typed chart-config builder, and write your own Eloquent-backed provider.

Minimal steps:

composer require mukadi/chartjs-bundle

In Laravel, do not register the bundle in config/app.php providers — there is no service provider. Pull the classes directly:

use Mukadi\Chart\Chart;
use Mukadi\Chart\Dataset;

$chart = new Chart(Chart::TYPE_LINE);
$chart->setData([
    'labels' => ['Jan', 'Feb', 'Mar'],
    'datasets' => [
        [
            'label' => 'Revenue',
            'data'  => [1200, 1900, 3000],
            'backgroundColor' => 'rgba(54, 162, 235, .5)',
        ],
    ],
]);

// What you hand to the browser:
$payload = $chart->getData();      // Chart.js `data` object
$options = $chart->getOptions();   // Chart.js `options` object

Where to look in the repo, in order:

  1. src/Chart.php / src/Dataset.php (or the mukadi/chart vendor dir) — the entire public surface is here; it is only a few hundred lines.
  2. src/DataProvider/DoctrineORMDataProvider.php — read it as a design template for the provider you will write for Eloquent.
  3. src/Resources/views/ and the Twig extension (Mukadi\ChartJSBundle\Twig\...) — you will re-implement these as a Blade component / @push('scripts') block.
  4. src/ChartBuilder.php — trivial factory; useful only if you want a container binding.

First use case to try: take an existing dashboard where you hand-write a Chart.js JSON blob inside a Blade file, and replace it with a Chart object built in an invokable controller or an App\Support\Charts\* class. If that doesn't feel cleaner than an array literal, you have your answer about adoption.

Implementation Patterns

1. Controller-builds-chart, Blade-echoes-JSON. Keep the wiring dumb; the chart object is just data.

// app/Http/Controllers/DashboardController.php
public function __invoke(): View
{
    $chart = (new Chart(Chart::TYPE_BAR))
        ->setData([
            'labels'   => Order::query()->distinct()->pluck('status'),
            'datasets' => [[ /* ... */ ]],
        ]);

    return view('dashboard', ['chart' => $chart]);
}
<canvas id="orders"></canvas>

@push('scripts')
<script>
new Chart(document.getElementById('orders'), @json([
    'type' => $chart->getType(),
    'data' => $chart->getData(),
    'options' => $chart->getOptions(),
]));
</script>
@endpush

@json is doing the same job as the bundle's Twig renderer. You get XSS-safe escaping and zero Symfony coupling.

2. A Blade component instead of the Twig extension. This is the closest 1:1 port of the bundle's rendering API.

{{-- resources/views/components/chart.blade.php --}}
@props(['chart', 'id'])
<canvas id="{{ $id }}"></canvas>
@push('scripts')
<script>
new Chart(document.getElementById(@js($id)), @js([
    'type' => $chart->getType(),
    'data' => $chart->getData(),
    'options' => $chart->getOptions(),
]));
</script>
@endpush

Usage: <x-chart :chart="$chart" id="orders" />.

3. Custom Eloquent data provider — the real value of the package. The bundle's DataProviderInterface is a tiny contract (roughly: given a chart/dataset config, return labels + numeric series). Implement it once against the query builder and reuse everywhere:

namespace App\Charts;

use Mukadi\Chart\DataProvider\DataProviderInterface;

final class EloquentAggregateProvider implements DataProviderInterface
{
    public function provide(array $config): array
    {
        $q = $config['model']::query()
            ->selectRaw("{$config['groupBy']} as label, {$config['aggregate']} as value")
            ->groupBy('label')
            ->orderBy('label');

        foreach ($config['constraints'] ?? [] as $col => $val) {
            $q->where($col, $val);
        }

        return $q->pluck('value', 'label')->all(); // ['label' => value]
    }
}

Then a ChartFactory that maps that map into Chart::getData()['labels'] / datasets[0]['data']. This mirrors exactly what the Doctrine provider does (setEntityClass, group-by, aggregate expression) but against Eloquent — and it is where aggregate/date-bucketing logic belongs, not in your controllers.

4. One dataset per series, not per query. For comparison charts (this year vs last year), build the label union once, then array-map each series onto those labels so missing months become 0/null instead of silently shifting the bars. This is the single most common chart bug and the package won't do it for you.

$labels = collect([...$a->keys(), ...$b->keys()])->unique()->sort()->values();

$datasets = [
    ['label' => '2024', 'data' => $labels->map(fn ($l) => $a[$l] ?? 0)->all()],
    ['label' => '2025', 'data' => $labels->map(fn ($l) => $b[$l] ?? 0)->all()],
];

5. JSON endpoint for SPA/AJAX dashboards. Skip the view layer entirely:

return response()->json([
    'type'    => $chart->getType(),
    'data'    => $chart->getData(),
    'options' => $chart->getOptions(),
])->header('Cache-Control', 'private, max-age=60');

This is the only integration point that is genuinely framework-neutral, and it is where I would stop if evaluating the package.

6. Centralise Chart.js defaults once. Chart::defaults()-style global config (fonts, colours, tooltip formatting, plugins.legend.position) belongs in a single config file or a base App\Charts\Chart class, not copied into every chart. Treat getOptions() as the layer you override per chart on top of those defaults.

Gotchas and Tips

  • This is a Symfony bundle. MukadiChartJSBundle extends Symfony\Component\HttpKernel\Bundle\Bundle; there is no Laravel auto-discovery, no service provider, no facade. Composer will pull in symfony/* transitive deps. If you only want the chart builder, depend on the underlying mukadi/chart package rather than the bundle to keep the dependency tree honest.
  • The Doctrine data provider is unusable with Eloquent. Do not try to bend it. Copy the ~50-line pattern into an Eloquent provider (pattern 3 above). Doctrine DQL aggregate expressions and GROUP BY semantics map to selectRaw/groupBy, not to Eloquent's withCount helpers.
  • The Twig extension won't work in Blade. Even with twig/twig + the Symfony Twig bridge installed, the extension is registered by the bundle's DI extension, which Laravel never runs. Re-implement it as the Blade component above — it's ~10 lines.
  • Chart.js itself is not shipped. Confirm in Resources/public/ — you are responsible for loading a Chart.js version (CDN or Vite/npm). Pin the version; the package predates Chart.js v4's ESM-only distribution and any generated options may assume v2/v3 shapes (e.g. scales.xAxes vs scales.x). Test against your installed Chart.js before trusting getOptions().
  • Numbers must be numbers. Aggregate results from MySQL often come back as strings ("1200"), and PDO/Postgres NUMERIC/DECIMAL definitely do. Cast with (float) in your provider or Chart.js silently renders empty. Dates from DATE_TRUNC/DATE_FORMAT need formatting into display labels in PHP, not JS.
  • Empty group-by rows and null labels. GROUP BY over a nullable column yields a null/"" bucket that breaks the label axis. whereNotNull() it or map to "Unknown" explicitly.
  • Watch the queries, not the chart. A dashboard with six charts is six aggregate queries over your biggest tables. Cache each series (Cache::remember("chart:revenue:{$year}", 300, ...)); the chart layer adds no caching of its own.
  • Maturity signal: 4 stars, 0 dependents, score 0.2. Pin the version in composer.json, expect no upstream fixes, and be ready to vendor-copy Mukadi\Chart\* into app/Charts/ if you need to patch it. The classes are small enough that forking is a realistic option — and probably better than a long-lived dependency on an unmaintained Symfony bundle in a Laravel app.
  • Extension points worth using: DataProviderInterface (your own Eloquent/API/cache-backed providers) and subclassing Chart/Dataset to bake in house defaults. Avoid extending the bundle's DI/Twig layer.
  • Debugging tip: dump $chart->getData() and the final HTML source, not the rendered canvas. 90% of "chart is blank" cases are (a) Chart.js not loaded, (b) data containing strings, or (c) the script block running before the canvas exists — @push('scripts') after @stack('scripts') in the layout, or wrap in DOMContentLoaded.
Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
jinas/dhivatar
store-doc/laravel-storage-driver
plus-it/laravel-translation-manager
diephp/perhaps
diephp/telegraph
osmaviation/spreadsheet
microsoft/fluentui-system-icons
mmccook/php-json-canonicalization-scheme
middlewares/firewall
qubiqx/montapacking
nyoncode/laravel-package-console
pnscripts/filament-broadquill-lite
pnscripts/laravel-sentfold-lite
vleap/mx-sdk-laravel-internal
twidpay/utils
htetmyathlaing/scout-elasticsearch-driver
artisan-toolbox/front-awesome
ls/client-assistant
users-au/laravel-client
cgv/localized-resource