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:
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:
<?= $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
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
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>// 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();
},
};
});
});<?= $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.