In laravel 10 / livewire 3 app I make chartjs report based on data from db and 4 filters. It work for me ok with blade file :
<div class="editor_field_block_wrapper">
<div class="editor_field_block_device_splitter">
<div class="w-4/12 pb-0 pl-2 md:pt-3 ">
<label for="filterModelType" class="editor_field_block_device_label">Model type: <span
class="editor_form_aria_required" aria-required="true"> * </span></label>
</div>
<div class="p-2 w-full">
<select wire:model.blur="filterModelType" class="editor_form_input" tabindex="20"
id="filterModelType">
<option value=""> - Select all -</option>
@foreach($modelTypeSelectionItems as $key=>$label)
<option value="{{$key}}">{{$label}}</option>
@endforeach
</select>
@error('filterModelType')
<span class="error_text">{{$message}}</span>
@enderror
</div>
</div>
</div>
<div class="editor_field_block_wrapper">
<div class="editor_field_block_device_splitter">
<div class="w-4/12 pb-0 pl-2 md:pt-3 ">
<label for="filterDateFrom" class="editor_field_block_device_label">From date</label>
</div>
<div class="p-2 w-full">
<x-inputs.datepicker id="filterDateFrom" wire:model.lazy="filterDateFrom" tabindex="20"/>
@error('filterDateFrom') <span
class="editor_form_validation_error">{{ $message }}</span> @enderror
</div>
</div>
</div>
<div class="editor_field_block_wrapper">
<div class="editor_field_block_device_splitter">
<div class="w-4/12 pb-0 pl-2 md:pt-3 ">
<label for="filterDateTill" class="editor_field_block_device_label">Till date</label>
</div>
<div class="p-2 w-full">
<x-inputs.datepicker id="filterDateTill" wire:model.lazy="filterDateTill" tabindex="30"/>
@error('filterDateTill') <span
class="editor_form_validation_error">{{ $message }}</span> @enderror
</div>
</div>
</div>
</fieldset>
@if($this->totalReactionCount > 0)
<div class="min-w-full">
<canvas id="reactionsChartStatisticsChart" style="background: #000000"></canvas>
</div>
@else
<div class="m-2 p-2 warning_text">
{!! AppIconFacade::get(IconEnum::Warning ) !!}
There are no data found !
</div>
@endif
@assets
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
@endassets
@script
<script>
const ctx = document.getElementById('reactionsChartStatisticsChart');
const reactions = $wire.reactions;
const labels = reactions.map(item => item.action_label)
const values = reactions.map(item => item.reaction_count)
var chartObj = new Chart(ctx, {
type: 'pie',
data: {
labels: labels,
datasets: [{
label: '# of Reactions',
data: values,
borderWidth: 2
}]
},
options: {
scales: {
y: {
beginAtZero: true
}
},
plugins: {
legend: {
position: 'top',
labels: {
font: {
size: 18
},
fontColor: 'white'
}
},
title: {
display: true,
text: 'Reactions by type with total quality {{ $totalReactionCount }}'
}
}
}
});
</script>
@endscript
console.log(chartObj)
// chartObj.update() // IF TO UNCOMMENT - RAISED ERROR : Uncaught SyntaxError: Unexpected end of input
// chartObj.refresh() // IF TO UNCOMMENT - RAISED ERROR : chartObj.refresh is not a function
</div>
</div>
and when the page is reloaded for the first time I have a valid chart with max width in wrapping html elements :
When I change some of filter parameters data are reloaded and chart is small in size :
for the third filter changed reactionsChartStatisticsChart element lost its chart propertied and I have black block :
Searching in net I found some chartObj.update() methods, but they did not work for me.
In which way can I check/fix chart ?
Data source component code:
In ReactionsChartStatistics.php component class I have :
<?php
namespace App\Livewire\Admin;
use App\Enums\ReactionActionEnum;
use App\Models\Reaction;
...
class ReactionsChartStatistics extends Component
{
public array $reactions = [];
public int $totalReactionCount = 0;
public function mount()
{
// Get data for filtering block
$this->usersSelectionItems = User::get()->pluck('name','id')->toArray();
$this->modelTypeSelectionItems = $this->getModelTypeSelectionItems();
$statisticsDays = ConfigValueEnum::get(ConfigValueEnum::NEWS_REACTIONS_STATISTICS_DAYS);
$this->filterDateFrom = Carbon::now()->addDays(-$statisticsDays)->startOfDay();
$this->filterDateTill = Carbon::now()->endOfDay();
}
public function render(): View
{
$reactionTb = (new Reaction)->getTable();
$this->totalReactionCount = 0;
$this->reactions = Reaction
::getByCreatedAt($this->filterDateFrom, '>=')
->getByCreatedAt($this->filterDateTill, '<')
->groupBy('action')
->orderBy('reaction_count', 'desc')
->select(
$reactionTb . '.action',
DB::raw('count(' . $reactionTb . '.id) as reaction_count'))
->get()->toArray();
}
foreach ($this->reactions as $key => $reaction) {
$this->reactions[$key]['action_label'] = ReactionActionEnum::getLabel($reaction['action']);
$this->totalReactionCount += $reaction['reaction_count'];
}
return view('livewire.admin.reactions-chart-statistics')->layout('components.layouts.admin');
}
}
Add a wire:ignore in the <div> containing the graph to prevent Livewire from updating it:
@if($this->totalReactionCount > 0)
<div wire:ignore class="min-w-full"> {{-- HERE -- }}
To refresh the chart when the inputs are updated we can send an event from the backend.
The class
class ReactionsChartStatistics extends Component
{
public array $reactions = [];
public int $totalReactionCount = 0;
public $filterModelType;
public $filterDateFrom;
public $filterDateTill;
public $modelTypeSelectionItems;
protected function prepareData()
{
// This code was previously found in the render() method
$reactionTb = (new Reaction)->getTable();
$this->totalReactionCount = 0;
$this->reactions = Reaction
::getByCreatedAt($this->filterDateFrom, '>=')
->getByCreatedAt($this->filterDateTill, '<')
->groupBy('action')
->orderBy('reaction_count', 'desc')
->select(
$reactionTb . '.action',
DB::raw('count(' . $reactionTb . '.id) as reaction_count'))
->get()->toArray();
}
foreach ($this->reactions as $key => $reaction) {
$this->reactions[$key]['action_label'] = ReactionActionEnum::getLabel($reaction['action']);
$this->totalReactionCount += $reaction['reaction_count'];
}
}
public function mount()
{
// Get data for filtering block
$this->usersSelectionItems = User::get()->pluck('name','id')->toArray();
$this->modelTypeSelectionItems = $this->getModelTypeSelectionItems();
$statisticsDays = ConfigValueEnum::get(ConfigValueEnum::NEWS_REACTIONS_STATISTICS_DAYS);
$this->filterDateFrom = Carbon::now()->addDays(-$statisticsDays)->startOfDay();
$this->filterDateTill = Carbon::now()->endOfDay();
$this->prepareData(); // <~~~ Added
}
// Raises an event when the parameters are updated
public function updated($property, $value)
{
$this->prepareData();
$this->dispatch('refresh-chart');
}
public function render()
{
return view('livewire.admin.reactions-chart-statistics')->layout('components.layouts.admin');
}
}
The javascript in the view
@script
<script>
const ctx = document.getElementById('reactionsChartStatisticsChart');
const getLabels = () => $wire.reactions.map(item => item.action_label);
const getValues = () => $wire.reactions.map(item => item.reaction_count);
let chartObj = new Chart(ctx, {
type: 'pie',
data: {
labels: getLabels(),
datasets: [{
label: '# of Reactions',
data: getValues(),
borderWidth: 2
}]
},
options: {
scales: {
y: { beginAtZero: true }
},
plugins: {
legend: {
position: 'top',
labels: {
font: { size: 18 },
fontColor: 'white'
}
},
title: {
display: true,
text: 'Reactions by type with total quality {{ $totalReactionCount }}'
}
}
}
});
// This event listener updates the chart
$wire.on("refresh-chart", () => {
chartObj.data.labels = getLabels();
chartObj.data.datasets = [{ data: getValues() }];
chartObj.update()
});
</script>
@endscript
In the backend I've moved the data preparation to a specific method prepareData() which is called initially in the mount() method and then in the updated() method. The updated() method is invoked by Livewire everytime an input changes value and is used also to dispatch the refresh-chart event.
In the frontend I've added the event listener $wire.on("refresh-chart", ....) which updates the chart data and then calls chartObj.update() to update the DOM
wire:ignore in the chart <div> is still required