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

digital-creative/chartjs-widget

A Filament widget for rendering interactive Chart.js charts in your admin panel. Define datasets and options in PHP, then display responsive line, bar, pie, and other charts without writing JavaScript.

View on GitHub
Deep Wiki
Context7

Getting Started

Install and confirm the Nova baseline first. The package is a Nova card, so it only works on top of Laravel Nova 4.x (check the composer.json require block before you commit — last tagged release 2023-10-28 predates Nova 5, so treat Nova 5 as untested).

composer require digital-creative/chartjs-widget
php artisan nova:publish   # only if you touch the package's compiled assets

Look here first, in order:

  1. vendor/digital-creative/chartjs-widget/src/ — the base widget class. Its public properties ($type, $width, $height) and the abstract/stub data() method define your entire contract.
  2. vendor/digital-creative/chartjs-widget/resources/js/ — the Vue component. You need to know its registered component name to debug a blank canvas, and you need to know whether it mutates or recreates the Chart instance.
  3. The README examples — the API surface is small enough that the examples are effectively the docs.

First use case — a static "Users per month" line chart on your main dashboard:

namespace App\Nova\Dashboards\Widgets;

use DigitalCreative\ChartJsWidget\ChartJsWidget;

class UsersPerMonth extends ChartJsWidget
{
    public $type = 'line';
    public $width = '1/2';

    public function data(): array
    {
        return [
            'labels' => ['Jan', 'Feb', 'Mar'],
            'datasets' => [[
                'label' => 'New users',
                'data' => [12, 45, 31],
                'borderColor' => '#4f46e5',
                'backgroundColor' => 'rgba(79,70,229,0.15)',
                'fill' => true,
            ]],
        ];
    }
}

Register it on the dashboard and load the page. If the card renders with a header but no canvas, you have an asset/registration problem, not a data problem — see Gotchas.

Implementation Patterns

Treat data() as a view-model, not a query method. It is invoked per request, synchronously, inside the Nova request lifecycle. Keep it pure: resolve and shape the payload in a service class, call it from data(), and keep the widget class thin so you can unit test the shaping without booting Nova.

public function data(): array
{
    return app(MonthlySignupsReport::class)->toChartJs();
}

Always cache the aggregate. A dashboard with six widgets fires six queries on every poll/refresh. Use Cache::remember keyed by the tenant/user and the reporting window:

$rows = Cache::remember(
    "signups:{$this->tenantId()}:".now()->format('Y-m-d-H'),
    now()->addMinutes(30),
    fn () => DB::table('users')
        ->selectRaw("DATE_FORMAT(created_at, '%Y-%m') as period, COUNT(*) as total")
        ->groupBy('period')->orderBy('period')
        ->pluck('total', 'period'),
);

Split the contract into labels() + datasets() in a parent class when you ship more than three widgets. Every dashboard ends up with the same ['labels' => ..., 'datasets' => [...]] shape; centralise the mapping and let subclasses supply only the series.

Multiple series / mixed types are just extra entries in datasets, each carrying its own Chart.js type:

'datasets' => [
    ['type' => 'bar',  'label' => 'Signups', 'data' => $signups,  'yAxisID' => 'y'],
    ['type' => 'line', 'label' => 'Churn %', 'data' => $churnPct, 'yAxisID' => 'y1'],
]

Chart.js options come from an overridable method. Keep everything you'd put in new Chart(ctx, {...}).options — legends, stacked scales, formatters — in one place so widgets stay declarative:

public function options(): array
{
    return [
        'plugins' => ['legend' => ['display' => false]],
        'scales' => ['y' => ['beginAtZero' => true, 'ticks' => ['precision' => 0]]],
    ];
}

Format values in PHP, not in JS callbacks. Currency symbols, thousands separators, and timezone-adjusted labels behave differently depending on the viewer's machine if you do it client-side. Return finished strings from data().

Composition pattern for dashboards: build one widget per question, not one widget per table. width controls the Nova grid span — mixing 1/3, 1/2, and 1/4 on a single dashboard is how you avoid the "wall of identical full-width charts" look.

Testing: instantiate the widget directly and assert on data():

$widget = new UsersPerMonth();
$this->assertSame(['Jan','Feb','Mar'], $widget->data()['labels']);

That works because the payload is plain PHP arrays — no Nova HTTP context required. Freeze time with Carbon::setTestNow() for anything using now().

Gotchas and Tips

Zero dependents, six stars, one maintainer. You are adopting this, not consuming it. Before merging, fork it into your own VCS or vendor it with a patch file; expect no upstream fixes for Nova 5, PHP 8.4, or Chart.js v5. Check the pinned Chart.js version in package.json — the v2→v3→v4 API churn (scales, plugins.legend, borderColor defaults) is the most likely source of "options silently ignored".

Blank canvas checklist, in order: (1) network tab for a 404 on the package's JS chunk; (2) php artisan nova:publish --force after any package or Nova upgrade; (3) the card's container has zero height — Chart.js cannot size into a collapsed flex parent, so set an explicit height; (4) a chart instance already bound to that canvas, which happens when the component re-renders without destroying. If your data changes but the chart doesn't, that's the fourth case — force a remount with a :key derived from the payload, or confirm the component calls chart.update().

Canvas memory leaks. Every Vue unmount must call chart.destroy(). Navigate across twenty dashboards in a session and a missing destroy is a real leak. Verify in the package's component before you build anything long-lived.

Dark mode. Hardcoded #333 gridlines and axis labels are invisible in Nova's dark theme. Either read the CSS custom properties (getComputedStyle in the JS layer) or supply two palettes and pick based on document.documentElement.classList. Datasets with only backgroundColor and no borderColor also look washed out on dark backgrounds.

Serialisation trap. Returning Carbon instances or Eloquent collections from data() produces either JSON objects Nova can't render or a 500. Run everything through ->toArray() / ->format('M Y') / (int) casts before returning.

Empty state. An empty labels array with an empty dataset renders a valid but confusing empty grid, and some Chart.js versions throw if datasets is missing entirely. Always return ['labels' => [], 'datasets' => []] rather than null.

Date bucketing belongs in SQL. GROUP BY DATE_FORMAT(...) keeps the row count bounded; pulling 200k created_at values into PHP to bucket them will time out long before Chart.js becomes the bottleneck. If you genuinely need thousands of points, aggregate to hourly/daily and note that Chart.js stays responsive to roughly 10k points per series.

Filters don't reach your widget automatically. Resource-level filters are scoped to resource index requests. If a dashboard needs a date range selector, it must be a widget-level control that persists (query string or session) and invalidates the cache key — otherwise users will report "the chart doesn't respect the filter" forever.

Extension point for custom JS: the clean route is to copy the package's Vue component into your app's resources/js, edit it, register it with Nova::script() in a NovaServiceProvider::boot() or a Nova::cards() bootstrap, and have your PHP widget return the new component name. Do that on day one rather than patching vendor/, because the fork is going to happen anyway.

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.
dbravoan/dbcms-admin-theme-dbcms
dbravoan/dbcms-theme-db-cyber
dbravoan/dbcms-blocks-basic
dbravoan/dbcms-module-content-audit
dbravoan/dbcms-module-ai-assistant
paygate-to/paygate-php
padosoft/askmydocs-connector-base
defstudio/supercharged-carbon
bernskioldmedia/laravel-matomo
bernskioldmedia/laravel-harvest
bernskiold/laravel-data-scrubber
bernskiold/laravel-currency-converter
bernskiold/laravel-campaign-monitor
bernskioldmedia/laravel-autotranslate
siberfx/telegram-bot
roy-sandip/laravel-view-extended
gregsphotography/site-builder-core
roy-sandip/laravel-view
anodyne/blade-fluentui-icons
webx-ui/module-catalog-collections