arbermustafa/filament-google-charts-widgets
A Laravel/Filament package for adding interactive Google Charts widgets to admin dashboards. Create configurable line, bar, pie, area, and other charts with a simple PHP API, making reports and analytics easy to embed and customize.
Install the package alongside Filament v3:
composer require arbermustafa/filament-google-charts-widgets
Register the plugin (if the package ships a plugin class — check src/ for a *Plugin.php — otherwise this step may be optional and only the widget base class is provided) in your PanelProvider:
use ArberMustafa\FilamentGoogleChartsWidgets\FilamentGoogleChartsWidgetsPlugin;
public function panel(Panel $panel): Panel
{
return $panel
->plugin(FilamentGoogleChartsWidgetsPlugin::make());
}
Where to look first: open the package's README.md and then the widget base class in vendor/arbermustafa/filament-google-charts-widgets/src/. Since this is a thin wrapper (14 stars, ~small surface area), you can read the entire source in minutes. Confirm whether it extends Filament\Widgets\Widget or Filament\Widgets\ChartWidget — this determines whether you get Filament's built-in chart plumbing or Google Charts' JS API.
First use case: a dashboard KPI chart. Create a widget:
php artisan make:filament-widget RevenueChart
Then swap the base class to the package's Google Charts widget and define data:
namespace App\Filament\Widgets;
use ArberMustafa\FilamentGoogleChartsWidgets\Widgets\GoogleChartWidget;
class RevenueChart extends GoogleChartWidget
{
protected static ?string $heading = 'Revenue (last 6 months)';
protected function getData(): array
{
return [
['Month', 'Revenue'],
['Jan', 4200],
['Feb', 5100],
['Mar', 4700],
['Apr', 6300],
['May', 7100],
['Jun', 8400],
];
}
protected function getOptions(): array
{
return [
'chartArea' => ['width' => '90%'],
'legend' => ['position' => 'bottom'],
];
}
}
Register it on the panel with ->widgets([RevenueChart::class]) or expose it via ->discoverWidgets().
Dashboard composition. Build a dashboard page by registering several small, single-purpose widgets (RevenueChart, UsersByPlanChart, OrdersPieChart) rather than one mega-widget. Each widget keeps its own query, its own heading, and its own getOptions(). This makes it trivial to reorder or A/B tweak layout later.
Data shape discipline. Google Charts is strict about the first column and column types. Whatever getData() returns must be [headerRow, ...dataRows], where the header row length matches each data row length. For time series, use ['Date', 'Value'] with Y-m-d date strings or DateTime objects — mixing strings and numbers in the first column silently renders blank charts. For pie charts use ['Label', 'Value'].
Filtering / dynamic data. Wire Livewire properties into getData() so a form filter (date range, tenant, segment) re-queries and re-renders the chart. Keep the query inside getData() rather than a property, so a Livewire re-render re-runs it:
public ?string $range = '30d';
protected function getData(): array
{
$rows = Order::where('created_at', '>=', now()->sub($this->range))
->selectRaw('DATE(created_at) as day, SUM(total) as total')
->groupBy('day')->orderBy('day')->get();
return [
['Day', 'Revenue'],
...$rows->map(fn ($r) => [$r->day, (float) $r->total])->all(),
];
}
Multi-series charts. Columns 2..n are independent series. Add a series column per line and pass matching colors via getOptions()['colors'] so lines stay consistent across re-renders.
Polling instead of AJAX. Let Filament's protected static ?string $pollingInterval = '30s'; drive refreshes rather than building custom fetch logic — the widget re-mounts and Google Charts redraws through Livewire's DOM diffing. Cheaper and less code than hooking google.visualization.events.
Custom options as the escape hatch. Anything Google Charts supports goes in getOptions(): vAxis.format, hAxis.slantedText, ChartArea, curveType, is3D, annotations, trendlines. Treat this as your primary extension point — you rarely need to touch the package internals.
Theme integration. For Filament dark mode, override colors and axis text styles from the filament()->getCurrentPanel()->hasDarkMode() check, or rely on Google Charts' theme: 'material' plus CSS variable-driven palette:
$dark = filament()->getCurrentPanel()?->hasDarkMode() && /* user pref */;
return [
'backgroundColor' => 'transparent',
'hAxis' => ['textStyle' => ['color' => $dark ? '#9ca3af' : '#4b5563']],
'legend' => ['textStyle' => ['color' => $dark ? '#e5e7eb' : '#111827']],
];
Reusable widget factory. If you have many similar charts (e.g. per-department metrics), create a base abstract class extending the package widget that takes departmentId as a constructor arg, and instantiate subclasses or use Filament's getWidgets() on a dashboard page.
The Google Charts loader is a CDN dependency. The upstream package almost certainly loads https://www.gstatic.com/charts/loader.js. That means: no working charts in air-gapped CI, on-prem deployments, or behind strict CSP. Test in your target environment early; if you need offline support, host the loader yourself and override the widget view, or accept a fallback to Filament's native ChartWidget (Chart.js) for those deployments.
Empty or malformed data crashes silently. Google Charts throws a JS error in the browser console, not PHP. getData() returning [] or [['Label','Value']] (header only) is your most common bug. Always guard:
$rows = $query->get();
if ($rows->isEmpty()) {
return [['Label', 'Value'], ['No data', 0]];
}
Livewire re-renders need to re-init the chart. The wrapper should handle this in its view/JS, but verify: chart containers need the google.visualization.* object to be recreated on every Livewire DOM morph. If your pollingInterval is set and the chart goes blank after the first refresh, patch the widget's Blade view to call drawChart() on livewire:navigated / Livewire.hook('morph.updated', ...).
Fixed height container. Google Charts needs an explicitly sized parent. Filament widgets can collapse to zero height if you don't set ->contentHeight() or a wrapper style="height:320px". Set it per widget rather than globally so pie charts can be shorter than line charts.
Widget lazy loading changes timing. Filament v3 lazy-loads widgets by default. If the loader runs before the container is in the DOM, the first draw is discarded. Either disable lazy on this widget (public static bool $isLazy = false;) or ensure the chart-initialize logic is re-run on the Livewire after-load event.
First-column type mismatches are the #1 subtle bug. Google Charts decides the column type from the first row. '2024-01-01' is inferred as string (category axis), not a date — pass 'line' + Date type explicitly by using Y-m-d with a type: 'date' cast, or accept a string category axis. For numeric X (scatter/bubble), the first column must be numeric.
Escaping and XSS in labels. If chart labels come from user data (names, titles), the wrapper's Blade template must JSON-encode with @json() / json_encode(..., JSON_HEX_TAG | JSON_HEX_APOS). Otherwise a tag in a product name breaks the chart init script.
Version pinning matters. With only ~14 stars and low maintenance signal, pin the exact version in composer.json ("arbermustafa/filament-google-charts-widgets": "1.0.*") and be ready to fork. The package is small enough that a fork is low-cost; the widget base + Blade view is typically a few hundred lines.
Don't override getData() signature blindly. Check whether the base class expects a plain array, a Collection, or a specific ChartData DTO. Read the base class before extending — the package may have a different method name (getChartData(), getDataset()) than the sibling ChartWidget in Filament core, and copy-pasting docs across the two will silently fall through to an empty dataset.
Cache the query, not the render. For slow aggregates, wrap the query in Cache::remember(..., 60) inside getData() — but leave the Google Charts render uncached so filter changes and polling still redraw.
How can I help you explore Laravel packages today?