Skip to main content

Creating a CPU load Chart with Filament ChartWidgets

· 4 min read
Wilson Velasco

Filament offers a simple and powerful way to incorporate interactive charts into your applications using chart widgets.

In this article, we will show you how we created the CPU load chart in MoonGuard using ChartWidget and how to customize it to display data from an Eloquent model.

Generating Chart Data from an Eloquent Model

To create the ChartWidget in your Laravel project, you need to run the following command:

php artisan make:filament-widget BlogPostsChart --chart

However, if you are creating it in a PHP package for Laravel, you will have to manually create the class because the artisan command is not available.

Using the flowframe/laravel-trend package, we can efficiently generate chart data from an Eloquent model.

In our example, we are going to create a chart that shows the server's CPU load over time.

// Namespace and necessary dependencies
use Flowframe\Trend\Trend;
use Flowframe\Trend\TrendValue;
use Taecontrol\MoonGuard\Models\ServerMetric;

// CpuLoadChart class that extends ChartWidget
class CpuLoadChart extends ChartWidget
// ...

// We set the default filter value
public ?string $filter = 'hour';

// We define the filters
protected function getFilters(): ?array
return [
'hour' => 'Last Hour',
'day' => 'Last Day',
'week' => 'Last Week',

protected function getData(): array
// Check if there is a selected site ID
if ($this->siteId) {
// Define the time range and frequency according to the filter
$filter = $this->filter;
$query = ServerMetric::where('site_id', $this->siteId);

// Use match to define the range and frequency
match ($filter) {
'hour' => $data = Trend::query($query)
->between(start: now()->subHour(), end: now())

'day' => $data = Trend::query($query)
->between(start: now()->subDay(), end: now())

'week' => $data = Trend::query($query)
->between(start: now()->subWeek(), end: now())

// Prepare the data for the chart
$chartData = [
'datasets' => [
'label' => 'CPU Load',
'data' => $data->map(fn (TrendValue $value) => $value->aggregate == 0 ? null : $value->aggregate),
'spanGaps' => true,
'borderColor' => '#9BD0F5',
'fill' => true,
'labels' => $data->map(function (TrendValue $value) use ($filter) {
// Format the labels according to the filter
if ($filter === 'hour') {
[$date, $time] = explode(' ', $value->date);
return substr($time, 0, 5);
} elseif ($filter === 'day') {
[$date, $time] = explode(' ', $value->date);
return $time;
} else {
return $value->date;

return $chartData;

return [];

// ...

In this line of code 'data' => $data->map(...) it is crucial for preparing the data that will be displayed on the chart.

Here is where we map each metric value to its corresponding aggregated value.

The validation of $value->aggregate == 0 allows us to handle cases where metrics are zero. In cases where there are no data available in the time interval, we do not want the line to fall to zero, so we replace the value with a null. In the chart configuration, we will indicate that we want to ignore and span the points that do have value (we refer to the spanGaps property).

You can find more information about the Chart.js API in its official documentation and examples.

protected function getData(): array
// ...
'data' => $data->map(fn (TrendValue $value) => $value->aggregate == 0 ? null : $value->aggregate),
// ...

Configuring Chart Options

Filament uses Chart.js to render charts, and we can configure options such as scales and plugins to customize the chart's appearance and functionality.

protected function getOptions(): RawJs
return RawJs::make(<<<JS
responsive: true,
scales: {
y: {
min: 0,
max: 100,
ticks: {
callback: (value) => value + '%',
plugins: {
tooltip: {
callbacks: {
label: function(context) {
let label = context.dataset.label || '';
if (label) {
label += ': ';
label += Math.round(context.raw) + '%';
return label;

Creating dynamic and easy-to-understand charts is crucial for monitoring the performance and health of our applications.

If you want more details on how to work with charts in Filament, consult the official documentation at