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 Laravel Package

nnnick/chartjs

Chart.js for Laravel: a fluent PHP wrapper for building interactive charts. Create datasets and options with a clean API, render them in Blade, and ship responsive visualizations without writing JavaScript. Includes a Blade directive and support for all Chart.js chart types.

View on GitHub
Deep Wiki
Context7

Getting Started

Chart.js isn't a Laravel package — it's a browser-side JavaScript charting library (nnnick/Chart.js redirects to chartjs/Chart.js; the distributable you actually install is chart.js on npm). So "installing" it means wiring it into your asset pipeline, not adding a service provider.

Minimal path in a modern Laravel app:

npm install chart.js
// resources/js/app.js
import { Chart, registerables } from 'chart.js';
Chart.register(...registerables);
window.Chart = Chart; // only if you're using inline Blade scripts

Then reference it from a Blade view. The first use case worth building is the one you'll repeat forever: a controller returns aggregated data, the view hands it to the chart as JSON.

// DashboardController
return view('dashboard', [
    'revenue' => Order::selectRaw('DATE(created_at) as day, SUM(total) as total')
        ->groupBy('day')->orderBy('day')->pluck('total', 'day'),
]);
<div class="relative h-64">
    <canvas id="revenueChart"></canvas>
</div>

<script>
const el = document.getElementById('revenueChart');
new Chart(el, {
    type: 'line',
    data: {
        labels: @json($revenue->keys()),
        datasets: [{ label: 'Revenue', data: @json($revenue->values()), tension: 0.3 }]
    }
});
</script>

Where to look first: the "Chart Types" and "Configuration" sections of the docs (chartjs.org/docs), then the per-chart-type options pages. Skip the v2-era blog posts entirely — the API changed heavily in v3 and again in v4.

Implementation Patterns

Pass data via data-* attributes, not inline JS. Inline scripts break under strict CSP and get awkward with localization. A reusable Blade component keeps it clean:

{{-- resources/views/components/chart.blade.php --}}
@props(['type' => 'line', 'labels' => [], 'datasets' => [], 'options' => []])
<div {{ $attributes->merge(['class' => 'relative']) }} style="height: 18rem">
    <canvas
        data-chart
        data-type="{{ $type }}"
        data-labels='@json($labels)'
        data-datasets='@json($datasets)'
        data-options='@json($options)'></canvas>
</div>
// resources/js/chart-init.js
import { Chart, registerables } from 'chart.js';
Chart.register(...registerables);

document.querySelectorAll('[data-chart]').forEach((canvas) => {
    const d = canvas.dataset;
    new Chart(canvas, {
        type: d.type,
        data: { labels: JSON.parse(d.labels), datasets: JSON.parse(d.datasets) },
        options: JSON.parse(d.options || '{}'),
    });
});

Alpine.js is the natural pairing. You get reactivity without a SPA:

<div x-data="revenueChart(@js($revenue))" class="relative h-64">
    <canvas x-ref="canvas"></canvas>
</div>
Alpine.data('revenueChart', (initial) => ({
    chart: null,
    init() {
        this.chart = new Chart(this.$refs.canvas, { /* ... */ });
        this.$watch('series', () => { this.chart.data.datasets[0].data = this.series; this.chart.update(); });
    },
    destroy() { this.chart?.destroy(); },
}));

Livewire: always wrap the canvas in wire:ignore. Livewire's DOM diffing will otherwise clone/destroy the canvas element and you'll get blank charts after every poll:

<div wire:ignore class="relative h-64">
    <canvas id="salesChart" wire:ignore></canvas>
</div>

Then update from the Livewire side using a dispatched event rather than re-rendering:

$this->dispatch('chart-updated', data: $this->series);
Livewire.on('chart-updated', ({ data }) => {
    chart.data.datasets[0].data = data;
    chart.update();
});

Inertia/Vue/React: treat the chart as a ref-mounted side effect, destroy on unmount, and re-create when props change identity. Don't try to be clever with partial updates until you've measured a problem.

Server-side aggregation is the pattern that matters. Chart.js will happily render 50,000 points, but nobody can read them. Aggregate in SQL (DATE_TRUNC, GROUP BY), bucket into weeks/months for long ranges, and let Chart.js handle presentation only. For genuinely large series, enable the decimation plugin or downsample in PHP.

Dashboard composition: one controller, one view, N chart components, each receiving a small array. Keep chart configs in PHP config or a JS module rather than scattering literals across Blade templates — you'll want consistent brand colors, fonts, and number formatting.

Gotchas and Tips

"Canvas is already in use" is the #1 error. It means you called new Chart() twice on the same canvas — almost always from Livewire re-rendering, Turbo navigation, or a hot-reload loop. Fix with wire:ignore, or store the instance (Chart.getChart(canvas)?.destroy()) before re-creating.

Always give the canvas a sized, position: relative parent and set options.responsive = true, maintainAspectRatio = false. Otherwise the chart grows unboundedly on every resize tick or collapses to zero height inside a flex container.

Prefer mutating chart.data + chart.update() over rebuilding. Rebuilding loses animation state, resets tooltips, and leaks listeners. For high-frequency updates (polling, streaming), pass chart.update('none') to skip animation entirely.

Version drift is brutal. v2 (new Chart(ctx, {...}) with scales: { xAxes: [...] }) is not v3/v4 (scales: { x: {...} }). Most StackOverflow answers you'll find are v2. Pin your major version and check the migration guide before upgrading. v4 also requires explicit Chart.register(...) — if you tree-shake and forget a controller, you get a silent blank canvas or "is not a registered controller".

Time-series axes need a date adapter. type: 'time' fails with a cryptic error unless you install chartjs-adapter-date-fns (or luxon/dayjs) and import it. It's a separate package from Chart.js.

Colors and fonts default to ugly on dark themes. Set Chart.defaults.color, Chart.defaults.borderColor, and Chart.defaults.font.family once at boot, or pass them per-chart. They don't read your CSS variables automatically — you can read them yourself with getComputedStyle if you support a theme toggle.

Tooltip and tick formatting is where polish lives. Use callbacks.label for currency/units and scales.y.ticks.callback for abbreviated numbers ($1.2k). Do this in JS, not by pre-formatting strings in PHP — pre-formatted strings break sorting and the underlying numeric scale.

Exporting to PNG: canvas.toDataURL('image/png') returns a transparent background unless you set backgroundColor in the chart config or draw onto an offscreen canvas first. If you need server-side PDFs with charts, render with a headless browser (Browsershot/Puppeteer) — do not try to reimplement the chart in PHP.

Accessibility: <canvas> has no accessible content. Add role="img" and an aria-label summarizing the trend, and consider a visually-hidden <table> of the same data. Chart.js doesn't do this for you.

Extension points worth knowing: custom plugins (plugins: [{ afterDraw(chart) { ... } }]) for watermarking or annotation lines, chartjs-plugin-annotation and chartjs-plugin-datalabels for common needs, and onClick handlers on the chart or legend for drill-down navigation.

Debugging: if the canvas is blank, check in this order — is Chart registered, is the canvas parent sized, is the data array non-empty, are labels defined, is there a JS error before new Chart(). Chart.getChart(canvas) tells you whether an instance already exists.

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.
vento/laravel-airbrake
wfeller/laravel-batch
maco-studios/mage-os-installer
busarm/armie
siberfx/laravel-logger
kfteam1991/dcat-admin
dashed/dashed-laposta
dashed/dashed-ecommerce-montaportal
dashed/dashed-ecommerce-etsy
neohq/string-blade-compiler
log1x/acf-composer
cgv/localized-resource
noith/settings
kda/laravel-package
kda/filament-dynamic-navigation
bypersoft/signbox
pairus/product-data
fayne/laravel-filament-cms-core
nodisrupt/laravel-agent
sierratecnologia/media-manager