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

Chart Js Integration Laravel Package

coroowicaksono/chart-js-integration

A Laravel Nova package that integrates Chart.js into dashboards, providing ready-to-use chart cards and widgets for building responsive line, bar, pie, and doughnut visualizations with simple PHP configuration and customizable datasets.

View on GitHub
Deep Wiki
Context7

Getting Started

Install and register

composer require coroowicaksono/chart-js-integration

No migrations, no config file, no publish step required for the default install — the service provider registers the compiled Vue card (dist/js/card.js) with Nova and exposes the PHP chart classes.

Drop a card into app/Providers/NovaServiceProvider.php::cards() (or a custom App\Nova\Dashboards\* class):

use Coroowicaksono\ChartJsIntegration\BarChart;

public function cards()
{
    return [
        (new BarChart())
            ->title('Orders per Month')
            ->model(\App\Models\Order::class)
            ->series([
                ['label' => 'Orders'],
            ])
            ->options([
                'xaxis'      => ['categories' => ['Jan','Feb','Mar','Apr','May','Jun']],
                'showTotal'  => true,
            ])
            ->width('1/2'),
    ];
}

Where to look first

  1. The README on GitHub for the option table — it is the only real documentation and it lags the code.
  2. vendor/coroowicaksono/chart-js-integration/src/ — each chart type (BarChart, LineChart, StackedChart, AreaChart, DoughnutChart, PieChart) is a thin subclass of a shared base card, so you can read all six in ten minutes.
  3. dist/js/card.js (or the resources/js source if present) — the options([...]) array is passed almost verbatim into the Chart.js config plus a few package-specific keys (showTotal, sum, uom, dataLabels, queryFilter, xaxis.categories, barType). When a key "does nothing", this file is the source of truth for what is actually consumed.

First use case that pays off: replace a hand-rolled Nova metric with a one-series bar chart of a filtered model count over time. It is a two-minute change and gives you the Chart.js look without writing a Vue component.

Implementation Patterns

Filtered multi-series (the 80% case) — each entry in series() is its own aggregate query:

(new BarChart())
    ->title('Revenue: Paid vs Pending')
    ->model(\App\Models\Order::class)
    ->series([
        ['label' => 'Paid',    'filter' => ['key' => 'status', 'value' => 'paid']],
        ['label' => 'Pending', 'filter' => ['key' => 'status', 'value' => 'pending']],
    ])
    ->options([
        'sum'       => 'total',        // sum this column instead of counting rows
        'uom'       => 'USD',
        'showTotal' => true,
    ])
    ->width('1/2'),

Scoping per user / tenant — filter only does an exact-match where(). Anything richer goes through a closure in options:

->options([
    'queryFilter' => function ($query) {
        return $query->where('team_id', auth()->user()->current_team_id)
                     ->where('created_at', '>=', now()->subMonths(6));
    },
])

Keep this closure cheap and index-friendly; it runs on every dashboard hit.

Stacked status breakdown — StackedChart with one series per status and a status/date grouping is the classic "ops board" card. Pair it with a DoughnutChart of the same data at width('1/3') for a current-slice view.

Doughnut / Pie — model these as "one series per slice", each with a filter; the legend labels come from label. If you already have precomputed counts (a nightly rollup table), point ->model() at the rollup model rather than the transactional table — it turns a 20-query dashboard into 20 indexed SUMs.

Custom dashboard + authorization — group cards in a Dashboard class and gate expensive ones with canSee(fn ($request) => $request->user()->can('viewRevenue')). Nova evaluates canSee server-side, which means blocked cards don't run their queries.

Caching pattern — the package does not cache. Two options that work well in practice:

  • Precompute aggregates into a dashboard_snapshots table on a schedule and have charts read that model.
  • Subclass the chart type you need and override the data-building method to wrap it in Cache::remember("chart:{$key}", 300, ...). The classes are small, plain PHP, so this is a ~20-line subclass rather than a fork.

Composition tip — the width fractions ('1/3', '1/2', '1/1') should be consistent per row; mixing 1/2 and 1/3 in the same dashboard row leaves awkward gaps in Nova's grid.

Gotchas and Tips

  • Nova version compatibility is the biggest risk. Releases are infrequent — the previous tag was November 2023, and v0.4.3 is a one-line bugfix rather than a compatibility refresh — and the card ships a Vue component built for Nova 3/4. Nova 5 moved to Vue 3 and older custom cards generally break (blank card, console error about the component). Pin laravel/nova or verify on a branch before upgrading; if you're on Nova 5, plan to fork the JS component or use Nova's native metrics instead.
  • TypeError: ... must be of type Countable, array, stdClass given is the error fixed in v0.4.3 — it fires when the series/summary data handed to the card (or to a subclass overriding the data method) comes back as an object rather than an array, e.g. a DB::select()-style result or a cached JSON payload that was never ->toArray()ed. If you hit it, either upgrade to 0.4.3 or cast to array at the boundary; upgrading is the safer fix since the card internals now tolerate the object form.
  • Queries run on every dashboard render, synchronously. There is no caching layer. Four charts × three series = twelve aggregate queries each time someone opens the dashboard. Index the filter columns and the timestamp column you group by, or move to a snapshot table as soon as a table passes a few hundred thousand rows.
  • filter is exact-match only. No operators, no IN, no ranges. Ranges, whereIn, and joins belong in queryFilter.
  • Date grouping assumes MySQL-flavoured SQL. The month bucketing uses raw date functions; SQLite (your test suite) and Postgres can error or silently return different buckets. If you test dashboards, test the aggregate on the same driver as production.
  • Timezones shift the buckets. Month grouping on created_at uses the database session timezone while xaxis.categories are your hand-written labels — for users near month boundaries the last bucket can look wrong. Normalise with ->options(['queryFilter' => fn ($q) => $q->whereBetween(...)]).
  • Bad option keys fail silently. A typo in options([...]) (e.g. sumTotal instead of sum) produces a default-looking chart, not an exception. If a chart renders but ignores your configuration, inspect dist/js/card.js for the key names it actually reads.
  • Blank card / no chart area: check the browser console first. A 404 on the compiled asset or a missing chart-js-integration component means the provider assets weren't published or Nova's asset version is stale — re-run your publish step and php artisan nova:publish, then hard-refresh. Note that a patch release also bumps the asset version hash, so a hard refresh is mandatory after updating even for a one-line PHP fix.
  • Empty result sets render an empty canvas with no message. Use showTotal while developing so you can distinguish "no data" from "broken chart".
  • Global scopes and soft deletes apply. A chart on Order excludes soft-deleted rows automatically — usually what you want, occasionally not. Use queryFilter with withTrashed() if a "total ever" card is required.
  • auth() inside queryFilter is request-context only. Fine on dashboard load, but these cards cannot be rendered from a console command or a queued export. Extract the closure body into a query scope so both paths share logic.
  • Two Chart.js instances on one page (this package plus another chart card or a custom tool) can fight over the global Chart object and cause legend/tooltip weirdness. Standardise on one charting approach per dashboard.
  • Extension point: every chart class is a thin subclass; overriding the data method to inject cached or precomputed data is the cheapest way to make this package production-safe without forking the Vue side. Return arrays from that override rather than query-builder/stdClass results — pre-0.4.3 it would throw, and array-normalised output keeps you compatible with both paths.
  • Alternatives worth knowing: Nova's built-in Trend, Value, and Partition metrics cover line, big-number, and pie/doughnut respectively, are maintained by Laravel, and cache well. Reach for this package specifically for stacked bars and area charts — those are the gaps in Nova's native metrics. With zero dependents and infrequent, bugfix-only releases, budget for the possibility that you maintain your fork.
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.
robertmarney/lara-hierarchial-collections
simonschaufi/rule-doc-generator
emissaoja/emissaoja
blackbricksoftware/gravityforms-civicrm-form-processor
al-rahabi/fancy-crud
jinas/dhivatar-component
mltstephane/laravel-quality
bernskiold/laravel-talentlms
bernskioldmedia/laravel-form-architect
youbar/easy-crud
c-point-labs/laravel-hostyourai
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