aw codes icon logo
Docs Hub

Search across every project.

Thermal Switch project

Heatmap visualizations for Filament: calendar activity grids and custom tile layouts.

1.x Current

On this page

Tile

A tile heatmap places one tile per key at an explicit row and column. Use it for anything with a meaningful arrangement: racks in a server room, desks on a floor, machines on a production line. It is built from values keyed by tile:

['east-1' => 83, 'east-2' => 42, 'west-1' => 97]

Layouts

A layout describes geometry only: which tiles exist, where each one sits, and what it is called. It never holds values, colors or levels.

Each tile is a Tile with a key, a short label drawn on the tile, a row and a column, and an optional full name used for tooltips and screen readers. Rows and columns start at 1.

use Awcodes\Thermal\Layouts\Tile;

Tile::make('east-1', 'E1', row: 1, column: 1, name: 'East 1');

Without a name, the short label is used everywhere.

Defining a layout

For a reusable layout, extend TileLayout and return the tiles from tiles():

use Awcodes\Thermal\Layouts\Tile;
use Awcodes\Thermal\Layouts\TileLayout;

class DatacenterLayout extends TileLayout
{
    protected function tiles(): array
    {
        return [
            Tile::make('east-1', 'E1', row: 1, column: 1, name: 'East 1'),
            Tile::make('east-2', 'E2', row: 2, column: 1, name: 'East 2'),
            Tile::make('west-1', 'W1', row: 1, column: 3, name: 'West 1'),
            Tile::make('west-2', 'W2', row: 2, column: 3, name: 'West 2'),
        ];
    }
}

For a one-off layout, pass the tiles to the constructor instead:

$layout = new TileLayout([
    Tile::make('a', 'A', row: 1, column: 1),
    Tile::make('b', 'B', row: 1, column: 2),
]);

The grid is as large as the furthest row and column. Positions without a tile stay empty, so the second column in the example above is an aisle between the two racks.

A layout rejects duplicate keys and two tiles placed at the same position with an InvalidArgumentException.

Matching data keys

Data keys match tile keys exactly by default. To accept other spellings, override resolveKey(), which returns the tile key a data key refers to, or null when there is no such tile:

public function resolveKey(int | string $key): ?string
{
    $key = strtolower((string) $key);

    return $this->hasTile($key) ? $key : null;
}

Keys that do not resolve to a tile are ignored, including when scaling. When several keys resolve to the same tile, their values are added together. The U.S. States layout uses this to accept abbreviations in any case and full state names.

Widget

A tile heatmap widget titled Requests per rack, showing four rows of ten green rack tiles labeled A1 to D10, split by an empty aisle row and an empty aisle column A tile heatmap widget titled Requests per rack, showing four rows of ten green rack tiles labeled A1 to D10, split by an empty aisle row and an empty aisle column

Extend TileHeatmapWidget, return a layout from getLayout() and your values from getData():

use Awcodes\Thermal\Layouts\TileLayout;
use Awcodes\Thermal\Widgets\TileHeatmapWidget;

class DatacenterHealth extends TileHeatmapWidget
{
    protected ?string $heading = 'Datacenter health';

    protected ?string $unit = 'request';

    protected function getLayout(): TileLayout
    {
        return new DatacenterLayout;
    }

    protected function getData(): array
    {
        return ['east-1' => 83, 'east-2' => 42, 'west-1' => 97];
    }
}

Entry

TileHeatmapEntry renders the same tiles inside a read-only schema. It requires a layout, given as an instance or a closure:

use Awcodes\Thermal\Entries\TileHeatmapEntry;

TileHeatmapEntry::make('racks')
    ->layout(new DatacenterLayout)
    ->data(fn (Datacenter $record): array => $record->requestsPerRack())
    ->unit('request');

Without data(), the entry uses its state. An entry without a layout throws a LogicException when it renders.

Labels and tooltips

Each tile shows its short label. The default tooltip combines the full name and the value, such as East 1 — 83 requests, or West 2 — no data for a tile without data.

Responsive behavior

Tile geometry is meaningful, so tiles never wrap onto new rows. They shrink with the container from 3.5rem down to 2rem, and below that the grid scrolls horizontally inside its own area. The grid and its legend are centered in the available space.