Skip to content

Chart updates ​

This chart reads server-owned data and updates its existing Chart.js instance after a PHP action.

Complete installation first. Copy the files below to the indicated application locations.

Assets ​

Install Chart.js in your application and copy its browser bundle:

sh
npm install chart.js
mkdir -p webroot/vendor/chart
cp node_modules/chart.js/dist/chart.umd.js webroot/vendor/chart/

Load the library and registration before Interact's runtime:

php
<?= $this->Html->script('/vendor/chart/chart.umd.js', ['defer' => true]) ?>
<?= $this->Html->script('SalesChart', ['defer' => true]) ?>
<?= $this->fetch('script') ?>

Keep automatic script loading enabled. No extra Alpine instance or runtime build is required.

Files ​

php
<?php
declare(strict_types=1);

namespace App\Island;

use Interact\Attribute\Action;
use Interact\Attribute\State;
use Interact\Island;

/**
 * Supplies server-owned totals to an application-managed chart.
 *
 * Save as src/Island/SalesChartIsland.php.
 */
class SalesChartIsland extends Island
{
    /**
     * Chart.js data, exposed for display without accepting browser edits.
     *
     * @var array{labels: list<string>, datasets: list<array{label: string, data: list<int>}>}
     */
    #[State]
    public array $chartData = [
        'labels' => ['Monday', 'Tuesday'],
        'datasets' => [
            ['label' => 'Orders', 'data' => [12, 18]],
        ],
    ];

    /**
     * Simulate refreshed totals for this small example.
     */
    #[Action]
    public function refreshChart(): void
    {
        $this->chartData['datasets'][0]['data'][0]++;
        $this->chartData['datasets'][0]['data'][1]++;
    }
}
php
<?php
declare(strict_types=1);

/**
 * templates/island/sales_chart.php
 *
 * @var \Interact\View\TemplateView $this
 */
?>
<div x-data="salesChart">
    <div x-interact:ignore>
        <canvas x-ref="canvas" role="img" aria-label="Daily orders"></canvas>
    </div>
    <button type="button" @click="$server.call('refreshChart')">Refresh chart</button>
</div>
js
// webroot/js/sales-chart.js; load after Chart.js and before Interact's runtime.
document.addEventListener('alpine:init', () => {
  window.Alpine.data('salesChart', () => {
    let chart;
    return {
      init() {
        const copy = (data) => JSON.parse(JSON.stringify(data));
        chart = new window.Chart(this.$refs.canvas, {
          type: 'bar',
          data: copy(this.$server.state.chartData),
        });
        this.$watch('$server.state.chartData', (data) => {
          chart.data = copy(data);
          chart.update();
        });
      },
      destroy() {
        chart?.destroy();
      },
    };
  });
});
php
<?= $this->Interact->render('SalesChart') ?>

Behavior ​

PHP declares chartData with #[State], so JavaScript can read it through $server.state.chartData. The watcher copies accepted data into the chart and calls update(). Copying prevents the library from mutating reactive island state.

x-interact:ignore preserves the canvas; the library can still update it. destroy() releases the chart. The sample action increments fixed totals; replace that operation with application data loaded through Table finders. Display-only charts need no x-modelable. See Alpine for scope and polling for periodic refreshes.