Widget

图表 Widget

简介

Filament 自带多个图表 Widget 模板,你可以用来展示实时、交互式图表。

使用以下命令开始创建 Widget:

php artisan make:filament-widget BlogPostsChart --chart

有一个 ChartWidget 类用于所有图表。图表的类型通过 getType() 方法设置。在这个例子中,该方法返回字符串 'line'

protected ?string $heading 变量用于设置图表的标题。如果你想要动态设置标题,可以重写 getHeading() 方法。

getData() 方法用于返回一个包含数据集和标签的数组。每个数据集都是一个用来绘制到图表的点构成的标签化数组,每个标签都是一个字符串。Filament 用来渲染图表的这种数据结构,与 Chart.js 库是一致的。你可以借助 Chart.js 文档去了解 getData() 方法基于图表类型可能的返回值。

<?php

namespace App\Filament\Widgets;

use Filament\Widgets\ChartWidget;

class BlogPostsChart extends ChartWidget
{
    protected ?string $heading = 'Blog Posts';

    protected function getData(): array
    {
        return [
            'datasets' => [
                [
                    'label' => 'Blog posts created',
                    'data' => [0, 10, 5, 2, 21, 32, 45, 74, 65, 45, 77, 89],
                ],
            ],
            'labels' => ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'],
        ];
    }

    protected function getType(): string
    {
        return 'line';
    }
}

现在,你可以到仪表盘上查看该 Widget 了。

Line chart

可用图表类型

以下是你可以继承的图表 Widget 类,以及他们对应的 Chart.js 文档页面,从中你可以了解 getData() 应该返回哪些值。

For example, you could use a bar chart by returning 'bar' from the getType() method:

Bar chart

Here are examples of the other available chart types:

Pie chart
Doughnut chart
Radar chart
Polar area chart
Scatter chart
Bubble chart

自定义图表颜色

通过设置 $color 属性,你可以自定义图表数据的颜色

protected string $color = 'info';

如果你要进一步自定义颜色,或者在多个数据集中使用多种颜色,你仍然可以在数据中使用 Chart.js 的颜色选项

protected function getData(): array
{
    return [
        'datasets' => [
            [
                'label' => 'Blog posts created',
                'data' => [0, 10, 5, 2, 21, 32, 45, 74, 65, 45, 77, 89],
                'backgroundColor' => '#36A2EB',
                'borderColor' => '#9BD0F5',
            ],
        ],
        'labels' => ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'],
    ];
}

从 Eloquent 模型中生成图表数据

要想从 Eloquent 模型中生成图表数据,Filament 推荐你安装 flowframe/laravel-trend 包。查看其文档

以下是使用 laravel-trend 包从模型中生成图表数据的一个示例:

use Flowframe\Trend\Trend;
use Flowframe\Trend\TrendValue;

protected function getData(): array
{
    $data = Trend::model(BlogPost::class)
        ->between(
            start: now()->startOfYear(),
            end: now()->endOfYear(),
        )
        ->perMonth()
        ->count();

    return [
        'datasets' => [
            [
                'label' => 'Blog posts',
                'data' => $data->map(fn (TrendValue $value) => $value->aggregate),
            ],
        ],
        'labels' => $data->map(fn (TrendValue $value) => $value->date),
    ];
}

过滤图表数据

Basic Select filter

你可以设置图表过滤器,用以调整图表上显示的数据。通常用来调整图表数据渲染的时间周期:

要设置过滤器的默认值,使用 $filter 属性:

public ?string $filter = 'today';

然后,定义 getFilters() 方法,以返回一个值和标签组成的数组给过滤器:

protected function getFilters(): ?array
{
    return [
        'today' => 'Today',
        'week' => 'Last week',
        'month' => 'Last month',
        'year' => 'This year',
    ];
}

你可以在 getData() 方法内使用当前活跃的过滤值:

protected function getData(): array
{
    $activeFilter = $this->filter;

    // ...
}
Chart with filter

NOTE

The $filter property is user-controllable. Although the <select> element only offers the keys returned from getFilters(), a crafted request can set $this->filter to any string, so it is not limited to those keys. You must ensure the value is valid before using it in a query — for example, by checking it against the keys of getFilters(), or by using a match expression with a safe default. Never interpolate $this->filter directly into a raw query.

Custom filters

You can use schema components to build custom filters for your chart widget. This approach offers a more flexible way to define filters.

To get started, use the HasFiltersSchema trait and implement the filtersSchema() method:

use Filament\Forms\Components\DatePicker;
use Filament\Schemas\Schema;
use Filament\Widgets\ChartWidget\Concerns\HasFiltersSchema;

class BlogPostsChart extends ChartWidget
{
    use HasFiltersSchema;
    
    // ...
    
    public function filtersSchema(Schema $schema): Schema
    {
        return $schema->components([
            DatePicker::make('startDate')
                ->default(now()->subDays(30)),
            DatePicker::make('endDate')
                ->default(now()),
        ]);
    }
}

The filter values are accessible via the $this->filters array. You can use these values inside your getData() method:

protected function getData(): array
{
    $startDate = $this->filters['startDate'] ?? null;
    $endDate = $this->filters['endDate'] ?? null;

    return [
        // ...
    ];
}

The $this->filters array will always reflect the current form data. Please note that this data is not validated, as it is available live and not intended to be used for anything other than querying the database. You must ensure that the data is valid before using it.

Chart with custom filters

NOTE

If you want to add filters that apply to multiple widgets at once, see filtering widget data in the dashboard.

Deferring filter updates

By default, filters using the filtersSchema() method update the chart data immediately as they are changed. However, for complex queries or better user experience, you may want to defer filter updates until the user clicks an “Apply” button.

When deferred, filter changes are only applied when the user clicks the “Apply” button. This ensures that the chart only re-renders when the user has finished adjusting all of their filters.

The chart will display data using the default filter values when the page first loads, ensuring users see meaningful data immediately without needing to take action.

To enable deferred filters, set the $hasDeferredFilters property to true:

use Filament\Widgets\ChartWidget\Concerns\HasFiltersSchema;

class BlogPostsChart extends ChartWidget
{
    use HasFiltersSchema;

    protected bool $hasDeferredFilters = true;

    // ...
}

If you need dynamic control over whether filters are deferred, you may override the hasDeferredFilters() method:

public function hasDeferredFilters(): bool
{
    return auth()->user()->prefersDeferredFilters();
}

Resetting filters to defaults

When using deferred filters, a “Reset” link appears in the filter dropdown footer alongside the “Apply” button. Clicking this link restores all filters to their default values as defined in the filtersSchema() method. For example, if you set ->default(now()->subDays(30)) on a DatePicker, the reset action will restore that default date, not an empty value.

Customizing filter actions

You may customize the apply and reset actions that appear when using deferred filters. All methods that are available to customize action trigger buttons can be used:

use Filament\Actions\Action;

public function filtersApplyAction(Action $action): Action
{
    return $action
        ->label('Update Chart')
        ->color('success');
}

public function filtersResetAction(Action $action): Action
{
    return $action
        ->label('Clear Filters')
        ->color('danger');
}

Empty state

When the getData() method returns an empty array, the chart widget renders an “empty state” instead of the chart.

To customize when the empty state is rendered, override the isEmpty() method:

public function isEmpty(): bool
{
    $data = $this->getCachedData();

    return empty($data['datasets'][0]['data'] ?? []);
}

Setting the empty state heading

To customize the heading of the empty state, set the $emptyStateHeading property:

protected ?string $emptyStateHeading = 'No data available';

Alternatively, you can override the getEmptyStateHeading() method to return a dynamic heading:

use Illuminate\Contracts\Support\Htmlable;

public function getEmptyStateHeading(): string | Htmlable
{
    return "No sales yet for {$this->filter}";
}

Setting the empty state description

To customize the description of the empty state, set the $emptyStateDescription property:

protected ?string $emptyStateDescription = 'Check back later once data has been collected.';

Alternatively, you can override the getEmptyStateDescription() method to return a dynamic description:

use Illuminate\Contracts\Support\Htmlable;

public function getEmptyStateDescription(): string | Htmlable | null
{
    return 'Sales data will appear here once orders are placed.';
}

Setting the empty state icon

To customize the icon of the empty state, set the $emptyStateIcon property:

use Filament\Support\Icons\Heroicon;

protected string | BackedEnum | null $emptyStateIcon = Heroicon::OutlinedChartBar;

Alternatively, you can override the getEmptyStateIcon() method to return a dynamic icon:

use BackedEnum;
use Filament\Support\Icons\Heroicon;
use Illuminate\Contracts\Support\Htmlable;

public function getEmptyStateIcon(): string | BackedEnum | Htmlable
{
    return Heroicon::OutlinedShoppingCart;
}

Adding empty state actions

You can add actions to the empty state to prompt users to take action by overriding the getEmptyStateActions() method:

use Filament\Actions\Action;

public function getEmptyStateActions(): array
{
    return [
        Action::make('refresh')
            ->label('Refresh')
            ->action('refresh'),
    ];
}

Using a custom empty state view

You may use a completely custom empty state view by overriding the getEmptyState() method:

use Illuminate\Contracts\Support\Htmlable;
use Illuminate\Contracts\View\View;

public function getEmptyState(): View | Htmlable | null
{
    return view('widgets.charts.custom-empty-state');
}

实时更新图表数据(轮询)

图表 Widget 默认每 5 秒钟刷新一次数据。

你可以通过重写类上的 $pollingInterval 属性自定义间隔时间:

protected ?string $pollingInterval = '10s';

或者,也可以完全禁用轮询:

protected ?string $pollingInterval = null;

设置图表最大高度

使用 $maxHeight 属性,你可以在图表上设置一个最大高度,以避免其变得过大:

protected ?string $maxHeight = '300px';
Chart with maximum height

设置图表配置项

你可以在图表类中指定 $options 属性,用以控制 Chart.js 库提供的多个配置项。比如,你想让 Line Chart 关闭 legend

protected ?array $options = [
    'plugins' => [
        'legend' => [
            'display' => false,
        ],
    ],
];

此外,你可以重写 getOptions() 方法,返回选项的动态数组:

protected function getOptions(): array
{
    return [
        'plugins' => [
            'legend' => [
                'display' => false,
            ],
        ],
    ];
}

这些 PHP 数组会在图表渲染时转换成 JSON 对象。如果你想在该方法中返回原始 JavaScript,可以返回 RawJs 对象。如果你想使用 JavaScript 回调函数,这就很有用。比如:

use Filament\Support\RawJs;

protected function getOptions(): RawJs
{
    return RawJs::make(<<<JS
        {
            scales: {
                y: {
                    ticks: {
                        callback: (value) => '€' + value,
                    },
                },
            },
        }
    JS);
}

Styling charts in a theme

Chart.js paints a chart onto a <canvas>, so almost none of it can be reached from a stylesheet. A custom theme is CSS only and cannot call getOptions(), so Filament exposes the parts of a chart that a theme is most likely to want to change as CSS custom properties. You may set them on .fi-wi-chart, or on any element above it to cover every chart in the panel at once:

.fi-wi-chart {
    --chart-border-width: 1;
    --chart-line-tension: 0.4;
    --chart-point-radius: 3;
    --chart-point-style: rect;
    --chart-bar-border-radius: 4;
}

--chart-border-width sets the thickness of the line that a chart draws around its data. --chart-line-tension curves the line of a line chart, from 0 for straight segments up to 1. --chart-point-radius sizes the markers on a line, radar or scatter chart, and --chart-point-style shapes them, accepting any of Chart.js’ point styles - circle, cross, crossRot, dash, line, rect, rectRounded, rectRot, star or triangle - as well as none to hide them entirely. --chart-bar-border-radius rounds the corners of the bars in a bar chart, which are already slightly rounded by default. Set it to 0 for square bars.

These values are handed to Chart.js rather than used by the browser, so they are plain numbers and keywords, without units. If you set one to something Chart.js cannot use, it is ignored and the chart keeps its default. They are also read again whenever the color scheme changes, so you may give light and dark mode different values.

NOTE

These properties are for styling every chart in a panel at once, which is what a theme usually wants. To change a single chart, use getOptions() instead - anything you set there wins over the properties here.

Styling the chart legend

The legend beneath a chart is drawn onto the canvas as well. Two properties control the color swatch next to each label:

.fi-wi-chart {
    --chart-legend-box-width: 16;
    --chart-legend-border-radius: 0;
}

--chart-legend-box-width sets how wide each swatch is, and --chart-legend-border-radius rounds its corners, which are slightly rounded by default to match the bars of a bar chart. Set it to 0 for square swatches.

Styling chart tooltips

The tooltip that appears when hovering over a chart is drawn onto the canvas as well. Its shape comes from two more properties:

.fi-wi-chart {
    --chart-tooltip-corner-radius: 0;
    --chart-tooltip-border-width: 1;
}

Its colors are set differently, so that you can use the same palette and dark mode variants as the rest of your theme. Filament reads them from elements that you style with an ordinary color declaration:

.fi-wi-chart {
    & .fi-wi-chart-tooltip-bg-color {
        @apply text-gray-900 dark:text-white;
    }

    & .fi-wi-chart-tooltip-text-color {
        @apply text-white dark:text-gray-900;
    }

    & .fi-wi-chart-tooltip-border-color {
        @apply text-gray-700 dark:text-gray-200;
    }
}

A tooltip has no border until you give it a width, so --chart-tooltip-border-width and .fi-wi-chart-tooltip-border-color usually change together.

The colors of the chart itself work in the same way: .fi-wi-chart-bg-color and .fi-wi-chart-border-color fill and outline the data, .fi-wi-chart-grid-color draws the grid lines, and .fi-wi-chart-text-color labels the axes.

The small charts inside a stats overview widget are styled separately, with their own set of properties.

添加描述

使用 getDescription() 方法,你可以在图表的标题下面添加描述:

public function getDescription(): ?string
{
    return 'The number of blog posts published per month.';
}
Chart with description

禁用懒加载

默认情况下,Widget 使用懒加载。这意味着只有当它们在页面中可见时才会加载。

要禁用该行为,你可以在 Widget 类中重写 $isLazy 属性:

protected static bool $isLazy = false;

让图表可折叠

通过将 Widget 类的 $isCollapsible 属性设置成 true,你可以让图表变成可折叠:

protected bool $isCollapsible = true;
Collapsible chart

使用自定义 Chart.js 插件

Chart.js 提供了强大的插件系统,允许你扩展其功能并创建自定义图表行为。本指南将详解如何在图表 Widget 中使用它们。

第 1 步: 使用 NPM 安装插件

要开始使用,请先在项目中使用 NPM 安装该插件。本指南中,我们将安装 chartjs-plugin-datalabels

npm install chartjs-plugin-datalabels --save-dev

第 2 步: 创建 JavaScript 文件导入该插件

你可以创建 JavaScript 文件,在其中定义自定义插件。本指南针中,我们称之为 filament-chart-js-plugins。导入该插件,并将其添加到 window.filamentChartJsPlugins 数组:

import ChartDataLabels from 'chartjs-plugin-datalabels'

window.filamentChartJsPlugins ??= []
window.filamentChartJsPlugins.push(ChartDataLabels)

This is equivalent to including the plugins “inline” via new Chart(..., { plugins: [...] }) when instantiating a Chart.js chart.

如果数组还没有初始化,那么在 push 之前对其进行初始化是很重要的。这可以确保注册 Chart.js 插件的多个 JavaScript 文件(尤其是来自 Filament 插件的文件)不会相互覆盖,无论它们的启动顺序如何。

You can push as many plugins to the array as you would like to install, you do not need a separate file to import each plugin.

Additionally, you can also register any “global plugins” which will use Chart.register([...]) in the window.filamentChartJsGlobalPlugins array:

import ChartDataLabels from 'chartjs-plugin-datalabels'

window.filamentChartJsGlobalPlugins ??= []
window.filamentChartJsGlobalPlugins.push(ChartDataLabels)

第 3 步: 使用 Vite 编译 JavaScript 文件

现在,你需要使用 Vite 或者其他捆绑器(Bundler)编译 JavaScript 文件。在 Vite 配置(通常是 vite.config.js)中导入该文件。比如:

import { defineConfig } from 'vite';
import laravel from 'laravel-vite-plugin';

export default defineConfig({
    plugins: [
        laravel({
            input: [
                'resources/css/app.css',
                'resources/js/app.js',
                'resources/css/filament/admin/theme.css',
                'resources/js/filament-chart-js-plugins.js', // Include the new file in the `input` array so it is built
            ],
        }),
    ],
});

使用 npm run build 编译该文件。

第 4 步: 在 Filament 中注册该 JavaScript 文件

Filament 需要知道在渲染图表 Widget 时导入此 JavaScript 文件。你可以在类似 AppServiceProvider 的服务提供者的 boot() 方法中执行此操作:

use Filament\Support\Assets\Js;
use Filament\Support\Facades\FilamentAsset;
use Illuminate\Support\Facades\Vite;

FilamentAsset::register([
    Js::make('chart-js-plugins', Vite::asset('resources/js/filament-chart-js-plugins.js'))->module(),
]);

可查看更多关于资源注册以及为特定面板注册资源

Edit on GitHub

Still need help? Join our Discord community or open a GitHub discussion

Previous
统计概览 Widget