DEV Community

Nasrul Hazim Bin Mohamad
Nasrul Hazim Bin Mohamad

Posted on

Livewire & ChartJs

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:

php artisan make:livewire Filter
Enter fullscreen mode Exit fullscreen mode

The Filter class:

<?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');
    }
}
Enter fullscreen mode Exit fullscreen mode

The Livewire view file:

<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>
Enter fullscreen mode Exit fullscreen mode

Assuming on organizations() helper will do:

<?php

use App\Models\Organization;

if (! function_exists('organizations')) {
    function organizations()
    {
        return Organization::orderBy('name')->get();
    }
}
Enter fullscreen mode Exit fullscreen mode

Now you have completed the Filter Livewire component.

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

<x-app-layout>
    <div class="min-h-full">
        @livewire('filter')
    </div>
</x-app-layout>
Enter fullscreen mode Exit fullscreen mode

Part 2

Next, we a component to display the chart.

php artisan make:livewire Chart
Enter fullscreen mode Exit fullscreen mode

Let's update our Livewire chart blade file:

@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>
Enter fullscreen mode Exit fullscreen mode

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

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');
    }
}
Enter fullscreen mode Exit fullscreen mode

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.

$this->emit('updateChart', [
    'datasets' => $dataset,
    'labels' => $labels,
]);
Enter fullscreen mode Exit fullscreen mode

Conclusion

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

Configure the Livewire Listener on frontend side:

Livewire.on('updateChart', data => {
    chart.data = data;
    chart.update();
});
Enter fullscreen mode Exit fullscreen mode

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

$this->emit('updateChart', $data);
Enter fullscreen mode Exit fullscreen mode

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

Top comments (1)

Collapse
 
gabiroman profile image
Gabriel Roman

Thanks!