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

Lavacharts Laravel Package

khill/lavacharts

Lavacharts is a Laravel wrapper for Google Charts that lets you build interactive charts with a fluent PHP API. Define data tables, render charts and dashboards, and add controls or events without writing JavaScript.

View on GitHub
Deep Wiki
Context7

Getting Started

Lavacharts is a server-side builder for Google Charts: you construct a DataTable in PHP with a fluent API, register a chart under a label, and render() emits the container <div> plus the inline google.visualization draw script. It does not replace Google Charts JS — it generates and validates the configuration for it.

Install

composer require khill/lavacharts

Laravel auto-discovery registers Khill\Lavacharts\Laravel\LavachartsServiceProvider on supported versions; older apps add it manually. Publish the package config if your version ships one:

php artisan vendor:publish --provider="Khill\Lavacharts\Laravel\LavachartsServiceProvider"

Composer 2 note: if you are pinned to the 3.x line, require ^3.1.14 at minimum. Earlier 3.x tags fail to install under Composer 2 because of a RenderableTrait autoload/constraint problem (issue #322); 3.1.14 is the patch that unblocks it. This is an install-time fix only — no API change.

Where to look first

  1. The README “Quick Start” example — it is essentially the whole mental model.
  2. src/DataTables/ — DataTable column/row methods and Formats/NumberFormat, DateFormat.
  3. src/Charts/ and src/Options/ — the valid chart types and the exact option keys each one accepts.
  4. Google’s Chart Gallery / interactive docs — the real reference for option semantics. Lavacharts only validates and serializes what Google defines.

First use case: one server-rendered dashboard chart

use Khill\Lavacharts\Lavacharts;

$lava = app(Lavacharts::class); // or new Lavacharts in plain PHP

$reasons = $lava->DataTable();
$reasons->addStringColumn('Reason')
        ->addNumberColumn('Percent')
        ->addRows([
            ['Check reviews', 5],
            ['Watch trailers', 2],
            ['Settle the argument', 89],
        ]);

$lava->PieChart('IMDB', $reasons, [
    'title' => 'Why I visit IMDB',
    'is3D'  => true,
]);

return view('dashboard', ['lava' => $lava]);
{{-- Include the Google loader exactly once, ideally in the layout head. --}}
@lavacharts
{{-- If your version has no Blade directive: --}}
{{-- <script src="https://www.gstatic.com/charts/loader.js"></script> --}}

{!! $lava->render('PieChart', 'IMDB', 'imdb-chart') !!}

render() returns HTML, so it must be output unescaped. It creates the chart container for the given element ID — don’t add a second <div> with the same ID.


Implementation Patterns

Put chart construction in a service, not the controller

Controllers should return data; a dedicated builder keeps chart config testable and reusable.

final class SalesChartBuilder
{
    public function __construct(private Lavacharts $lava) {}

    public function build(): Lavacharts
    {
        $data = $this->lava->DataTable();
        $data->addDateColumn('Day')
             ->addNumberColumn('Sales', new NumberFormat(['prefix' => '$', 'fractionDigits' => 0]))
             ->addNumberColumn('Expenses');

        Order::query()
            ->where('created_at', '>=', now()->subDays(30))
            ->orderBy('created_at')
            ->get()
            ->each(fn ($o) => $data->addRow([$o->created_at, (float) $o->sales, (float) $o->expenses]));

        $this->lava->LineChart('Sales', $data, [
            'legend'    => ['position' => 'bottom'],
            'hAxis'     => ['title' => 'Day'],
            'curveType' => 'function',
        ]);

        return $this->lava;
    }
}

Inject the builder into the controller, pass the Lavacharts instance (or the rendered HTML) to the view.

Multiple charts on one page

Use one Lavacharts instance per request, create every chart, then render each one with a unique label and element ID:

$lava = app(Lavacharts::class);
$lava->ColumnChart('Revenue', $revenueTable, $revenueOptions);
$lava->PieChart('Channels', $channelTable, $channelOptions);
{!! $lava->render('ColumnChart', 'Revenue', 'revenue-chart') !!}
{!! $lava->render('PieChart', 'Channels', 'channel-chart') !!}

Keep labels ('Revenue') and DOM IDs ('revenue-chart') unique. The label is the lookup key used by render(); the ID is what Google Charts binds to.

Formatting, roles, and richer tables

Use NumberFormat / DateFormat on columns rather than pre-formatting strings — Google needs numeric/date types for axes and tooltips.

$data->addNumberColumn('Sales', new NumberFormat(['prefix' => '$']));
$data->addDateColumn('Month', new DateFormat(['formatType' => 'medium']));

Column roles (annotations, tooltips, styles) are set when adding the column; check your version’s addColumn signature for the role argument. This lets you push annotation text alongside a value without a second table.

Options are validated arrays

Options mirror Google’s camelCase keys (hAxis, vAxis, chartArea, legend, pieHole). Nested arrays are fine. Build a base options array and merge per chart:

$base = ['chartArea' => ['width' => '80%', 'height' => '70%']];
$lava->ColumnChart('Revenue', $table, array_replace_recursive($base, [
    'legend' => ['position' => 'none'],
]));

Large datasets

Inline JSON in the page does not scale past a few thousand rows. For larger tables, serve JSON from a controller and draw with Google’s JS DataTable / Query wrapper instead of embedding it; keep Lavacharts for the chart options and small/medium tables. Cache aggregated rows (not the DataTable object itself — cache the arrays and rebuild).

Testing

render() returns deterministic HTML, so assert on it:

$html = $lava->render('PieChart', 'IMDB', 'imdb-chart');
$this->assertStringContainsString('id="imdb-chart"', $html);
$this->assertStringContainsString('google.visualization.PieChart', $html);

Test the data builders separately (row counts, column order, computed values) — they are the part most likely to break.

Livewire / Turbo / SPA shells

Lavacharts only emits the initial draw script. Any DOM swap that replaces the container requires re-running the draw. Wrap the chart area in wire:ignore and trigger a redraw on navigation/DOM patch, or render the chart server-side on full page loads only.


Gotchas and Tips

  • Strict option validation. A typo or a Google option not in the package’s whitelist throws Khill\Lavacharts\Exceptions\InvalidOption, usually with a list of valid keys. Options are also chart-specific — pieHole belongs to PieChart, not ColumnChart. When an option “doesn’t work,” check the package’s Options class for that chart before blaming Google.
  • Camel case, not snake case. hAxis, vAxis, titleTextStyle, backgroundColor. Snake-case keys will fail validation.
  • Column order is the data contract. The first column is the domain (X axis / label); subsequent columns are series. Swapping two numeric columns silently changes the chart, not the row count.
  • Date handling. Pass DateTime objects (or the date strings your version accepts) for date/datetime columns. Pre-formatting dates as strings turns them into categorical labels and breaks time axes.
  • Numeric strings. Cast database decimals to float/int in rows where possible; mixed types can produce unexpected formatting or NaN tooltips.
  • Loader included once. google is not defined in the console means the loader script is missing or loaded after the inline draw script. google.charts.load called twice with different packages can warn or override; consolidate the loader config in your layout.
  • Hidden containers render at zero width. Charts inside collapsed tabs, modals, or display:none panels draw blank. Draw on “shown” or call chart.draw() again after making the container visible.
  • {!! !!} is required but risky with user data. Labels and row values are JSON-encoded, but HTML in tooltips or option strings is not sanitized by Google. Escape or strip user-generated HTML before it reaches a chart label.
  • Singleton state in long-running workers. With Octane or queue workers, the container-resolved Lavacharts instance can accumulate charts across requests. Use a fresh instance per request/job, or resolve it request-scoped.
  • Debug order: browser console (google is not defined, “Container element not found”, “Table has no columns”) → view-source the emitted <script> to confirm the data and options actually serialized → then inspect the PHP-side DataTable rows/columns.
  • Composer 2 / dependency resolution. 3.1.14 is a compatibility patch: it fixes the RenderableTrait blocker (issue #322) that prevented earlier 3.x versions from installing under Composer 2. If composer require fails with an autoload/trait or constraint error on the 3.x line, bump to ^3.1.14 before debugging anything else. No public API changed in this patch, so existing chart code needs no edits.
  • Version churn. The 3.x → 4.x jump changed namespaces and some methods; examples found online may target the wrong major version. Pin a major (^3.1.14 or ^4.x, whichever matches your PHP/Laravel versions) and read the changelog before upgrading. Don’t assume a 3.1.x patch release implies the 4.x line received the same fix — verify against the tag you actually install.
  • Maintenance risk. The project is effectively dormant: releases are rare, patch-level, and driven by install-blocking bugs rather than features. Expect friction on PHP 8.2+ (dynamic properties/deprecations) and newer Laravel versions. If charts are core to the product, wrap Lavacharts behind your own chart-builder interface so you can swap to a JS chart library (Chart.js, ApexCharts) without rewriting controllers. Verify the license file directly — GitHub reports NOASSERTION.
  • Extension points. You can subclass chart/option classes to add or override whitelisted options, and the package’s service provider/config can hold defaults (locale, etc.). Keep chart configuration in plain PHP arrays so it remains diffable and testable even if you later replace the renderer.
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.
middlewares/firewall
qubiqx/montapacking
nyoncode/laravel-package-console
pnscripts/filament-broadquill-lite
pnscripts/laravel-sentfold-lite
vleap/mx-sdk-laravel-internal
twidpay/utils
htetmyathlaing/scout-elasticsearch-driver
artisan-toolbox/front-awesome
ls/client-assistant
users-au/laravel-client
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