Livewire & ChartJs
Tags: laravel, livewire, chartjs
Published: 2022-09-14 18:14:22

Recent works require me to work with Livewire and ChartJs.

Here how the expectation should be:

> Change the filter, will update chart.

Sounds easy right? TLDR;

**Part 1**

Prepare our simple filter:

```bash
php artisan make:livewire Filter
```

The `Filter` class:

```php
<?php

namespace App\Http\Livewire\Dashboard;

use Livewire\Component;

class Filter extends Component
{
    public $selectedOrganization = null;

    public function onOrganizationChange()
    {
        $this->emit('organization-selected', $this->selectedOrganization);
    }

    public function render()
    {
        return view('livewire.filter');
    }
}
```

The Livewire view file:

```php
<div>
    <div class="flex justify-end mt-4 px-4 w-full">
        <div class="w-1/4">
            <x-select wire:model.defer="selectedOrganization" wire:change="onOrganizationChange">
                @foreach (organizations() as $organization)
                    <option value="{{ $organization->uuid }}">{{ $organization->name }}</option>
                @endforeach
            </x-select>
        </div>
    </div>
</div>
``` 

Assuming on `organizations()` helper will do:


```php
<?php

use App\Models\Organization;

if (! function_exists('organizations')) {
    function organizations()
    {
        return Organization::orderBy('name')->get();
    }
}
```

Now you have completed the `Filter` Livewire component.

Use it in default `resources/views/dashboard.blade.php`:

```php
<x-app-layout>
    <div class="min-h-full">
        @livewire('filter')
    </div>
</x-app-layout>
```

**Part 2**

Next, we a component to display the chart.

```bash
php artisan make:livewire Chart
```

Let's update our Livewire chart blade file:

```php
@once
    @push('scripts')
        <script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
    @endpush
@endonce

@push('scripts')
    <script>
        const chart = new Chart(
            document.getElementById('chart'), {
                type: 'line',
                data: {
                    labels: @json($labels),
                    datasets: @json($dataset)
                },
                options: {
                    plugins: {
                        legend: {
                            position: 'bottom'
                        }
                    },
                    responsive: true,
                    scales: {
                        x: {
                            stacked: true,
                        },
                        y: {
                            stacked: true
                        }
                    }
                }
            }
        );
        Livewire.on('updateChart', data => {
            chart.data = data;
            chart.update();
        });
    </script>
@endpush

<x-panel class="bg-white p-4 mx-4 mt-4">
    <div>
        <canvas id="chart"></canvas>
    </div>
</x-panel>
```

Note that, there is few things happened here:

1. The `@once` only use to include the ChartJs once in entire app.
2. The initialisation of the chart object I have `labels` and `datasets` is dynamically set via `@json` blade directive. This just to simplify initial load and configuration of the chart object to be dynamic and configurable at the backend.
3. `Livewire.on` simply listen to the `updateChart` event. On listen, we will update the chart - this is where we want send our data.

Then the `Chart` class:

```php
<?php

namespace App\Http\Livewire\Dashboard;

use App\Models\Organization;
use Livewire\Component;

class Activity extends Component
{
    public array $dataset = [];
    public array $labels = [];
    public Organization $organization;

    protected $listeners = [
        'organization-selected' => 'organizationSelected',
    ];

    public function organizationSelected(string $uuid)
    {
        $this->organization = Organization::whereUuid($uuid)->first();

        $labels = $this->getLabels();

        $dataset = [
            [
                'label' => 'Logged In',
                'backgroundColor' => 'rgba(15,64,97,255)',
                'borderColor' => 'rgba(15,64,97,255)',
                'data' => $this->getRandomData(),
            ],
        ];

        $this->emit('updateChart', [
            'datasets' => $dataset,
            'labels' => $labels,
        ]);
    }

    public function mount()
    {
        $this->labels[] = $this->getLabels();

        $this->dataset = [
            [
                'label' => 'Logged In',
                'backgroundColor' => 'rgba(15,64,97,255)',
                'borderColor' => 'rgba(15,64,97,255)',
                'data' => $this->getRandomData(),
            ],
        ];
    }

    private function getLabels()
    {
        $labels = [];
        for ($i = 0; $i < 12; $i++) {
            $labels[] = now()->subMonths($i)->format('M');
        } 
        return $labels;
    }

    private function getRandomData()
    {
        $data = [];
        for ($i = 0; $i < count($this->getLabels()); $i++) {
            $data[] = rand(10, 100);
        }
        return $data;
    }

    public function render()
    {
        return view('livewire.dashboard.activity');
    }
}
```

The only we going to look at is how the data is populated. On organization selected, an event emitted from our `Filter` and received in `Chart` class in `organizationSelected` method. 

The `Chart` will populate a new set of data. 

In this case we create randomly the data for the pass 12 months. 

Assuming we are looking at 12 months of logged in pattern of data.

Then the last part, the following piece of code will dispatch an event with the dataset and labes.

So that from `Livewire.on()` will receive a new set of data and render a new chart.


```php
$this->emit('updateChart', [
    'datasets' => $dataset,
    'labels' => $labels,
]);
```

**Conclusion**

The key point is the Event & Listener of the Livewire:

Configure the Livewire Listener on frontend side:

```javascript
Livewire.on('updateChart', data => {
    chart.data = data;
    chart.update();
});
```

And emit event from Livewire backend side, to pass new data:

```php
$this->emit('updateChart', $data);
```

With these two key points, hopes to help people work with dynamic charts with Livewire.