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

Charts Laravel Package

orchid/charts

Chart widgets for the Laravel Orchid admin platform, adding Chart.js-powered visualizations to screens and dashboards via simple layout fields.

View on GitHub
Deep Wiki
Context7

Getting Started

Resolve the identity of the package before you write a line of code. The supplied signals contradict each other and every downstream decision hangs on which one is true:

  • The metadata summary says "PHP-first server-side SVG charts… zero JavaScript runtime."
  • description.md says "brings interactive Chart.js visualizations to Laravel Orchid Platform applications" with Blade components. Chart.js is client-side JavaScript.
  • The v0.0.10 notes describe a Vitest/ESLint/Vite toolchain and a "published package has no runtime dependencies" — the profile of a frontend asset, not a Composer library.

Triage in this order, and stop at the first answer you get:

  1. cat vendor/orchid/charts/composer.json — if require contains orchid/platform or laravel/framework, it is a PHP package you install with Composer. If there is no composer.json, you are looking at a JS asset and belong in package.json/Vite, not here.
  2. find vendor/orchid/charts -maxdepth 3 -type f | head -60 — look for a service provider, a src/ tree with chart classes, and a resources/views directory with Blade components.
  3. Grep the source for the Chart.js type names you actually need: grep -rill "doughnut\|radar\|pie\|bar" vendor/orchid/charts/src. That tells you what is genuinely wired up versus what the prose promises.
  4. Confirm how assets arrive: does the package publish a JS file, assume Chart.js already in the Orchid layout, or ship a CDN reference?

Minimal adoption path, assuming the Orchid/Blade reading holds:

composer require orchid/charts
php artisan vendor:publish --provider="Orchid\Charts\ChartsServiceProvider"   # if the provider offers tags

You need a working Orchid admin panel first — this package has near-zero value outside it. If php artisan orchid:admin has never run in this repo, that is the prerequisite, not this package.

Where to look first: src/ and the tests, not the README. At v0.0.10 with zero dependents, there are no blog posts, no Stack Overflow answers, and no third-party examples. The component signature and the options array shape are the documentation. Read the Blade component class and the view it renders; that is the entire contract.

First use case: one real metric on one real screen. Not a dashboard of eight charts. Pick "orders per day, last 30 days" and put it on the screen where you would otherwise render a numeric list. That exercises the whole chain — query, payload, Blade component, layout registration, asset load — and gives you a concrete artifact to debug. A chart that renders wrong is much easier to fix than eight that render wrong.

Implementation Patterns

Method and component names below are illustrative of the fluent/Blade style Orchid packages use. Verify the real signatures against src/ before you build team conventions on top of them — at v0.0.10 they are still moving.

Keep chart data assembly out of layout(). The single most valuable habit day-to-day is that a chart payload is a named, testable function — not an inline array buried in a screen's layout() method. Build a small class or method per metric:

final class OrdersPerDayChart
{
    public function __construct(
        private CarbonImmutable $from,
        private CarbonImmutable $to,
    ) {}

    /** @return array{labels: array<int,string>, datasets: array<int,array<string,mixed>>} */
    public function payload(): array
    {
        $rows = Order::query()
            ->whereBetween('created_at', [$this->from, $this->to])
            ->selectRaw('DATE(created_at) as d, COUNT(*) as c')
            ->groupBy('d')
            ->orderBy('d')
            ->get();

        return [
            'labels'   => $rows->pluck('d')->all(),
            'datasets' => [[
                'label' => 'Orders',
                'data'  => $rows->pluck('c')->all(),
            ]],
        ];
    }
}

Aggregate in SQL, always. The tempting shortcut — Order::where(...)->get()->groupBy(fn ($o) => $o->created_at->toDateString()) — hydrates every row on every admin page view. Nobody profiles it because "it's just a chart," which is exactly why it survives to production as the slowest thing on the screen.

Inject the window; never hardcode dates. Taking $from/$to (or the Request) through the constructor means the same chart class serves the default dashboard view and a filtered one, and it makes the class trivially testable with a fixed window.

Cache the payload, not the rendered view.

$payload = Cache::remember(
    "chart.orders.{$from->toDateString()}.{$to->toDateString()}",
    now()->addMinutes(5),
    fn () => (new OrdersPerDayChart($from, $to))->payload(),
);

Chart data is a pure function of the window, so it caches cleanly and survives Blade edits. Never cache by request()->fullUrl() — the key space explodes with every filter permutation. Invalidate by key on write (new order → forget today's key) or accept five minutes of staleness on an internal ops screen.

Screen integration is the boring part — keep it boring. Collect the payload in query(), place it in layout() where the metric belongs, and use the package's layout/field wrapper rather than hand-rolling the Blade include. The wrapper is what keeps asset and config wiring consistent across the ten screens you'll eventually have.

Standardise a palette once, keyed by meaning. Eight charts with eight ad-hoc colour arrays is what the dashboard looks like in month two. Define a ChartPalette config array — revenue green, refunds amber, errors red — and have every chart pull from it. Semantic consistency matters more than aesthetics on an ops dashboard; an operator should be able to say "green means money" and be right on every screen.

Filters are a server round-trip, not a client concern. A date-range selector should push to the screen's route, re-run the query with the new window, and re-render. Even under a Chart.js reading this is simpler and keeps authorisation and tenant scoping on the server where they already work — you cannot leak another tenant's rows through an axis if the query never ran.

Test the payload, not the pixels. Assert the shape, the label count against a fixture, and that a known seed produces known counts.

Order::factory()->count(3)->create(['created_at' => '2024-01-02 10:00:00']);
$payload = (new OrdersPerDayChart($from, $to))->payload();
$this->assertSame(['2024-01-02'], $payload['labels']);
$this->assertSame([3], $payload['datasets'][0]['data']);

This is the entire payoff of keeping charts as classes — a screen test would require booting Orchid, auth, and layout, and would still only prove the div rendered.

Compose dashboards from small screen methods. Separate methods (orders(), revenue(), errors()) are easier to reason about and let you lazy-load or feature-flag sections later. One thousand-line layout() is the failure mode.

Gotchas and Tips

The identity problem is the top gotcha and it will not resolve itself. If you plan for server-side SVG and get Chart.js, the assets won't load and you'll blame your build. If you plan for Chart.js and get SVG, you'll hunt for a config option that doesn't exist. Settle it in the triage step. If it turns out to be frontend-only, the right move is to hand ownership to whoever owns resources/js — not to write a PHP wrapper around a JS file to satisfy a composer-shaped hole in the roadmap.

"No runtime dependencies" is a real constraint if you assumed a CDN fallback. If charts render client-side and the package ships no bundled Chart.js, you are responsible for having it in the Orchid layout bundle. Check whether the package brings its own copy or expects yours. A chart that renders axes and legend but no data points is almost always Chart.js loaded twice, or loaded after the component script ran.

Pre-1.0 means pin exactly. "orchid/charts": "0.0.10" — not ^0.0.10, not *. The v0.0.10 notes say the tooltip builder's option handling was adapted for an ESLint major "while preserving callback and visibility behavior." The fact that the release notes had to state behaviour was preserved is the tell: tooltip callback drift is user-visible and silent, and a smoke test that only checks the chart draws will never catch it.

Don't gate CI on this package's dev advisories. The braces advisory (GHSA-vfj7-8cjw-p6xm) arrives transitively through Stylelint, is dev-only, and the production audit is clean. Add a scoped, dated exception rather than letting Dependabot fail a build permanently over a package you don't ship. Keep the exception scoped — blanket-suppressing audit noise is how a real finding gets missed.

Timezone bucketing will produce off-by-one charts. DATE(created_at) uses the DB session timezone; Carbon uses config('app.timezone'). On an "orders today" chart the admin sees a count that disagrees with the orders list, and they will file it as a data bug. Pick one boundary — convert at the query boundary with an explicit offset — and use it in every chart payload.

Empty datasets break axes silently. Zero rows often renders a blank canvas or a nonsense scale rather than "no data." Special-case the empty payload in your chart class and let the screen show a placeholder; otherwise you will re-debug this as a rendering issue every time it recurs.

Debug by dumping the built options array. Whatever the fluent API builds ends up as an options hash. dump($chart->payload()) before it reaches the view splits "my query is wrong" from "the chart config is wrong" in one step. Nine times out of ten it's the query.

Beware pluck() on an unbounded relation. A chart over ->pluck('amount') on a million-row table pulls a million values into PHP to draw 200 pixels. Always aggregate; if you must sample, limit() deliberately and label the chart as truncated.

Extension points, in order of safety: subclass the package chart class → override the payload method → override the options array → override the Blade view. Reach into rendering internals last. At v0.0.10 those internals are actively moving; anything you monkey-patch there is one composer update away from breaking. Keep customisation in your own base class so a vendor bump is a one-file change.

Zero dependents cuts both ways. No ecosystem means no answers and no examples — your source-reading is the documentation. Leave a comment block in each chart class recording the component signature and options shape you verified against the installed version. It is the only onboarding material your team will have, and it is the difference between the next developer reading src/ for an hour and reading your comment for a minute.

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.
diephp/sequences
users-au/socialite-provider
twidpay/twid-logger
mrnewport/laravel-plaid
mrnewport/laravel-repo
mrnewport/laravel-docsign
mrnewport/laravel-stow
mrnewport/laravel-flow
mrnewport/laravel-priceable
mrnewport/laravel-nestedterms
fredmeng/laravel-apidoc-generator
itstructure/laravel-multilingual-tools
pdustdar/elasticsearch
markupai/php-sdk
rlacerda83/lumem-microservice-shipment
uni-method/symfony-lib
tacticmedia/scim-server-bundle
neika-ai/symfony-bundle
mattleads/agent-publishing-bundle
glavweb/framework-bundle