asantibanez/livewire-charts
asantibanez/livewire-charts lets you render responsive, interactive Chart.js charts directly in Laravel Livewire. Use reusable components for line, bar, pie, doughnut, and more, with easy Blade integration and dynamic data updates.
Install the package and make sure the Livewire major you're on matches the release line you pull in:
composer require asantibanez/livewire-charts
The package is a thin, opinionated wrapper around ApexCharts. Almost everything you do day-to-day lives in two API surfaces:
LivewireCharts::lineChartModel(), multiColumnChartModel(), pieChartModel(), donutChartModel(), areaChartModel(), radarChartModel(), etc. These produce a serializable model that describes the chart (series, labels, colors, axis titles, tooltips, animations).
2a. Blade components — livewire-livewire-line-chart, livewire-multi-line-chart, livewire-column-chart, livewire-pie-chart, … Each takes a matching *-chart-model prop and handles the ApexCharts lifecycle.Where to look first:
README.md — the per-chart-type examples are the real API reference; copy them.src/Facades/LivewireCharts.php — the one-method-per-chart-type entry points.src/Models/*.php — the actual fluent methods available on each chart type (setTitle, setColors, addPoint, addColumn, addSlice, addSeries, setDataLabelsEnabled, setXAxisTitle, axis/legend/grid toggles, setAnimated, setHeight/setWidth, etc.).resources/views/ — the chart blade views. You'll publish these the first time you need to change wrapper markup, add an export button, or inject JS options.Confirm ApexCharts assets are loaded (the package registers its own JS in the Livewire lifecycle; some versions need an explicit script directive in your layout — check the installed version's README). If a chart area renders empty, this is the first thing to check.
Your first useful chart: a single-series line chart on a dashboard.
namespace App\Livewire;
use Asantibanez\LivewireCharts\Facades\LivewireCharts;
use Livewire\Component;
class SalesChart extends Component
{
public function render()
{
$chart = LivewireCharts::lineChartModel()
├>setTitle('Monthly Sales')
└>setColors(['#6366f1'])
├>setDataLabelsEnabled(true)
└>setAnimated(false);
foreach (\App\Models\Order::monthlyTotals() as $month => $total) {
$chart = $chart->addPoint($month, $total);
}
return view('livewire.sales-chart', compact('chart'));
}
}
{{-- resources/views/livewire/sales-chart.blade.php --}}
<div>
<livewire:livewire-line-chart
key="{{ $chart->reactiveKey() }}"
:line-chart-model="$chart"
/>
</div>
That snippet contains the single most important detail in the whole package: the reactiveKey(). Read the next section for why.
One Livewire component per chart, or one dashboard component with several models. Prefer the former as soon as a chart has its own filters or polling. Charts are cheap to compose; a monolithic dashboard component gets re-rendered (and re-queries) every time any filter changes.
Always key the chart component with $model->reactiveKey() (or a key derived from your inputs). ApexCharts is imperative and the wrapper only re-initializes when Livewire sees a new key for that child component. If you omit it, your parent re-renders, the model data changes, and the chart on screen stays stale. Conversely, if the key changes on every keystroke you'll tear down and rebuild the chart constantly — so include only the inputs that genuinely change the chart's shape (series set, chart type, axis type), not the individual values.
Build models from collections with a loop, not a giant chained expression. Most fluent methods return $this, but a few model helpers return clones; reassigning ($chart = $chart->addPoint(...)) is the safe habit and it also keeps loops readable:
$chart = LivewireCharts::multiColumnChartModel()
->setTitle('Revenue by region')
->setHorizontal(false);
foreach ($regions as $region) {
$chart = $chart->addSeries($region->name, $region->color)
->addPoint('Q1', $region->q1)
->addPoint('Q2', $region->q2);
}
Map Eloquent output to scalar arrays before it touches the model. pluck('total', 'day'), map(fn ($r) => ['label' => ..., 'value' => ...]), number_format(). The model is serialized into the Livewire payload; Eloquent models, Carbon objects with timezones, closures, and resources either bloat the payload or fail to hydrate.
Reactive filters. Date range, segment, granularity — all of them rebuild the model in render(). The key must include the filter state that changes the chart structure:
key="{{ $chart->reactiveKey() . '-' . $range . '-' . $granularity }}"
Click-to-drill-down is the highest-leverage integration. The chart components emit a click event when a data point is selected; listen for it in the parent and re-query:
use Livewire\Attributes\On;
#[On('onPointClick')]
public function drillDown(array $payload): void
{
$this->selectedMonth = $payload['label'] ?? null;
// update filter state; render() rebuilds the model
}
Verify the exact event name and payload shape against your installed version's README — it's stable but not documented in the facade.
Styling. setColors() for the palette, setTitle, setXAxisTitle/setYAxisTitle, setLegendEnabled, setDataLabelsEnabled, setSmooth, setHorizontal for bar orientation, setTheme/color options for dark mode, and setHeight/setWidth when the container doesn't match your grid. Numbers should be formatted server-side; the model isn't a place to pass JS callbacks.
Performance. Wrap chart components behind #[Lazy] so they load after the shell, and put wire:poll on the chart component, not the whole page. Disable animation (setAnimated(false)) for any chart that updates on a timer — otherwise each poll visibly redraws from zero.
Testing. Because the model is plain PHP, you can assert on it without any JS:
Livewire::test(SalesChart::class)
->assertSee('Monthly Sales')
->assertSet('selectedMonth', null)
->call('drillDown', ['label' => 'Jan'])
->assertSet('selectedMonth', 'Jan');
Assert the serialized model ($chart->toArray()) for units, and fall back to Dusk/Playwright only when you actually need to prove the chart renders or the click event fires.
Version alignment with Livewire is the number one failure mode. This package's major versions track Livewire majors. If you see Target class [livewire-line-chart] does not exist or an asset error after upgrading Livewire, confirm you're on the release line built for that Livewire version; don't just bump composer update.
Blank or empty chart = missing ApexCharts, not missing data. Inspect the network tab for the ApexCharts bundle. Depending on version, the package either injects scripts or expects a layout directive; the CDN vs. local asset behavior may be configurable. If a chart renders in one environment and not another, it's almost always asset loading behind a CSP or an offline/compiled asset.
reactiveKey() is not optional. Symptom: filters change, the HTTP request returns new data, the chart doesn't move. Fix: add/update the key. The inverse symptom — the chart flickers or loses its zoom/hover state on every poll — means your key is too unique; key on structure, not values.
Multiple charts on one page need distinct keys. Two livewire-pie-chart components with identical keys get confused during the morph and swap data. Append a chart name or UUID to the key.
Serialization surprises. Anything you put in the model travels to the browser. A Collection will JSON-encode fine but wastes bytes; a model instance or closure will break hydration. Strip to strings/ints and pre-format labels.
Dates and timezones. ApexCharts treats string categories as categories unless you configure a datetime axis, and it will not respect config('app.timezone'). Normalize to a fixed format (Y-m-d) and label in the user's timezone before building the model, otherwise a point appears on the wrong day for a subset of users.
Large datasets. Past a few hundred to a thousand points, ApexCharts gets slow regardless of what Livewire does. Aggregate or bucket server-side (daily → weekly → monthly when the range widens), disable markers, and disable animation.
Don't wrap the chart component in wire:ignore. It's a Livewire child component, not an Alpine widget; wire:ignore will freeze it and defeat updates. If you need to preserve something across renders, do it in the model's key and options.
Customize by publishing, not by hacking the vendor directory. Publish the package views to change the wrapper markup, add an export/print button, or inject extra ApexCharts options into the chart config. For reusable behavior (e.g., "build a chart from this Eloquent query"), extend the model classes or write a small service that returns a configured model, rather than forking the package.
Debugging: dump $chart->toArray() in render() to see exactly what the front end receives, and inspect the Livewire request payload for the line-chart-model key. If the payload looks right but the chart doesn't, the problem is the key/asset layer; if the payload is wrong, the problem is your builder code.
Maintenance posture. This is a well-liked, MIT-licensed, single-maintainer package (~900 stars) with a recent release, but its release cadence follows Livewire's. Pin the version in composer.json, keep an eye on the changelog before upgrading Livewire, and budget for publishing views if you need a fix faster than upstream. The ApexCharts options the package doesn't expose are always reachable through published views, so treat that escape hatch as part of your design rather than a last resort.
How can I help you explore Laravel packages today?