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 了。
可用图表类型
以下是你可以继承的图表 Widget 类,以及他们对应的 Chart.js 文档页面,从中你可以了解 getData() 应该返回哪些值。
- Bar chart - Chart.js 文档
- Bubble chart - Chart.js 文档
- Doughnut chart - Chart.js 文档
- Line chart - Chart.js 文档
- Pie chart - Chart.js 文档
- Polar area chart - Chart.js 文档
- Radar chart - Chart.js 文档
- Scatter chart - Chart.js 文档
For example, you could use a bar chart by returning 'bar' from the getType() method:
Here are examples of the other available chart types:
自定义图表颜色
通过设置 $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;
// ...
}
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.
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';
设置图表配置项
你可以在图表类中指定 $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.';
}
禁用懒加载
默认情况下,Widget 使用懒加载。这意味着只有当它们在页面中可见时才会加载。
要禁用该行为,你可以在 Widget 类中重写 $isLazy 属性:
protected static bool $isLazy = false;
让图表可折叠
通过将 Widget 类的 $isCollapsible 属性设置成 true,你可以让图表变成可折叠:
protected bool $isCollapsible = true;
使用自定义 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 GitHubStill need help? Join our Discord community or open a GitHub discussion