elemind/filament-echarts
Filament ECharts integrates Apache ECharts into Laravel Filament panels, making it easy to add beautiful, interactive, customizable charts and widgets with a simple PHP API. Ideal for dashboards, reports, and responsive data visualizations.
Install and register. Pull the package in via Composer and let Filament's asset pipeline pick up the ECharts JS bundle:
composer require elemind/filament-echarts
php artisan filament:assets
If the package ships a config file (config/filament-echarts.php), publish it only when you actually need to change defaults — CDN vs. bundled assets, default theme, height. Check the README first; the package is small (15 stars, single maintainer), so the source itself is the most reliable documentation and is short enough to read end-to-end in ten minutes.
Where to look first.
src/ — the widget base class you extend and the Blade view that mounts the chart. This tells you the exact property/method names (getOption(), $heading, $chartHeight, etc.).README.md for the Filament version constraint (v3 vs v4 matters a lot here).First use case: a dashboard KPI chart. The lowest-friction win is a line or bar chart on a Filament dashboard page. Create a widget:
php artisan make:filament-widget RevenueChart --chart
Then swap the base class for the package's ECharts widget and return a raw ECharts option array:
use Elemind\FilamentEcharts\Widgets\EChartsWidget;
class RevenueChart extends EChartsWidget
{
protected static ?string $heading = 'Revenue (30d)';
protected int|string|array $columnSpan = 'full';
protected function getOption(): array
{
return [
'tooltip' => ['trigger' => 'axis'],
'xAxis' => ['type' => 'category', 'data' => $this->labels()],
'yAxis' => ['type' => 'value'],
'series' => [[
'type' => 'line',
'smooth' => true,
'data' => $this->values(),
]],
];
}
}
Register it on a page via getWidgets() or getHeaderWidgets(). If the chart renders at zero height, you've hit the most common onboarding snag — set an explicit height either in the widget or the config.
Treat the option array as the contract. The whole value proposition is that you hand Apache ECharts a PHP array and it becomes a chart. Keep that boundary clean:
ChartOption builder class, not inline in the widget.dataset + encode over parallel xAxis.data/series.data arrays — it maps naturally to an Eloquent collection and is far less code.Data flow pattern: query → transform → option. The most maintainable shape is a thin widget that delegates:
protected function getOption(): array
{
return [
'dataset' => ['source' => $this->orders()->map(fn ($o) => [
'date' => $o->date->format('Y-m-d'),
'total' => (float) $o->total,
])->all()],
'xAxis' => ['type' => 'category'],
'yAxis' => ['type' => 'value'],
'series' => [['type' => 'bar', 'encode' => ['x' => 'date', 'y' => 'total']]],
];
}
Multi-widget dashboards. Because each widget is a normal Filament widget, you get $columnSpan, $pollingInterval, filters, and lazy loading for free. Use polling sparingly — every tick re-serialises the whole option array and re-creates the ECharts instance unless the package diffs options (check the view; many ECharts bridges just call setOption which is cheap, but a full re-init is not).
Interactive filtering. The idiomatic Filament approach is to put a Select/DatePicker form on the page or widget, store the value in a public Livewire property, and recompute getOption() from it. The widget re-renders and the chart updates. Avoid client-side JS event wiring into Livewire unless the package explicitly documents a bridge — it's the fastest way to create stale-state bugs.
Composable chart types. Standard patterns that pay off repeatedly:
type: 'line' widget with axes hidden, embedded in a custom dashboard view.stack: 'total' on each series plus legend.click events only if the package exposes a JS hook; otherwise link out from a table beneath the chart.tooltip.trigger = 'axis'.Reuse across resources. Extract per-domain option builders (SalesChartOptions, UserGrowthChartOptions) and inject them, so a dashboard widget and a resource-page widget share one definition. This is the single biggest maintainability lever on a package this thin.
Dark mode. Filament themes toggle a dark class on <html>. ECharts needs its text/axis colours set explicitly or it will render dark text on a dark card. Either register an ECharts theme and have the widget pick it based on Filament::getCurrentPanel()->hasDarkMode(), or set textStyle.color from a CSS variable read server-side. Test both modes before shipping.
Version-matrix risk. With 15 stars and zero dependents, this is a single-maintainer package. Pin the version in composer.json and confirm the Filament major version in the README before adopting. Filament's widget API churned between v3 and v4 (getStats, panel asset registration, FilamentView), so a package that supports both will have conditional code paths worth reading.
Zero-height / blank chart. By far the most common failure. ECharts requires the container to have a resolved height; a Filament widget card with height: 100% inside an auto-height parent collapses. Fix by setting an explicit pixel or rem height on the chart container (usually a package config or widget property) and, if inside a grid, ensuring the parent grid row has a definite height.
Asset loading and ordering. ECharts must load before the init script. If you're using Filament's asset registration, verify with the browser network tab that echarts.min.js loads once and not per-widget. On slow connections, a race between Livewire's morph and echarts.init produces "instance already initialized" console warnings — the symptom is a chart that draws once and then dies on the next poll. Look for an echarts.getInstanceByDom(el)?.dispose() guard in the package's JS; if absent, that's your patch point.
Livewire re-render behaviour. Options returned from PHP get serialised into the DOM and re-applied on morph. Large datasets (10k+ points) will balloon the Livewire payload and make every poll expensive. For big series, downsample server-side (LTTB or simple bucketing) or send a data URL and fetch it from JS. Also beware: PHP json_encode turns floats into strings in some configs — cast monetary values to (float) explicitly or axes will mis-scale.
Debugging. When the chart is blank, in order: (1) view source — is the option JSON present and valid? (2) console — does echarts exist? (3) does echarts.init(el).setOption(JSON.parse(...)) work manually in the console with the same payload? That three-step isolates PHP-side vs JS-side vs CSS-layout issues in under a minute. Note that ECharts fails silently on malformed options in many cases — a console.warn is often the only signal.
Extension points. The package is deliberately thin, so extend by wrapping, not forking: subclass the widget to add shared defaults (grid margins, palette, animation), publish the Blade view to inject echarts.registerTheme() calls or a resize observer, and keep the fork surface as small as possible. If you need a feature the package doesn't have (e.g. server-side image export, WebGL series), it's usually faster to emit the raw option array from your own Blade partial than to wait on upstream.
Timezone and locale. ECharts formats dates client-side using browser locale; if you pass pre-formatted Y-m-d strings from PHP you control this, but if you pass timestamps and rely on axisLabel.formatter, server and user timezones will disagree. Format server-side for anything audit-facing.
Accessibility and print. Add 'aria' => ['enabled' => true] to the option for screen readers. For PDF/print dashboards, ECharts canvases don't print reliably — either enable the SVG renderer ('renderer' => 'svg' if the package exposes it) or provide getDataAsTable() fallbacks.
Budget a fallback. Given the dependency count is zero and the star count is low, keep a short internal note on how to drop to plain echarts + a Blade partial if the package stalls on a future Filament release. Because the option array is ECharts' native format, that migration is a couple of hours, not a rewrite — which is the strongest argument for adopting this package in the first place.
How can I help you explore Laravel packages today?