Harumi

Widgets

Every dashboard.toml widget type — required and optional keys, a working example, and a live render for each.

Every widget is a [[widgets]] entry

A dashboard file (dashboard/<name>.toml, or the legacy root dashboard.toml) renders one view per [[widgets]] table. Every entry, of every type below, needs the same three keys:

Prop

Type

Everything else is type-specific — covered per type below, each with a live render produced by the same components the product uses. A widget with an unknown type, or missing one of its type's required keys, is dropped from the rendered dashboard and reported by harumi dashboard validate rather than breaking the page.

Beyond widgets

dashboard.toml can also declare reusable datasets, define new KPIs as SQL, and add clock-driven playback — none of which are covered here. See Dashboard datasets, metrics, and the clock. For what a color/colors value below may be, and the full palette of names, see Colors.

Layout

You don't place widgets. The dashboard packs them into rows in the order they appear in [[widgets]], by how much room each type needs:

  • metric cards that follow each other share one row, side by side. They wrap onto more rows only when the dashboard is too narrow for them.
  • chart, treemap and heatmap take half a row: two in a row sit side by side (on a narrow dashboard they stack). One left without a partner takes the full row.
  • table, timeline, gantt-chart and map take a row of their own.

Declare related widgets next to each other: three metric widgets in a row form a strip, but a table between two of them splits the strip in two.

layout.columns is ignored

Older dashboards may still have a [layout] table with columns. It still parses, but it no longer changes anything; you can delete it.

KPI card

metric

The one KPI widget: a title, the value large, and an optional line under it with a description on the left and a trend on the right. Write one metric per number; cards that follow each other share a row. A breakdown per entity (per machine, per shift) is not a KPI card: put it in a table or a chart.

Prop

Type

[[widgets]]
type = "metric"
id = "utilization"
title = "Machine utilization"
value_key = "totals.utilization"
delta_key = "totals.utilization_delta"
format = "percent"
description = "4 machines, 480 min"

Live preview, rendered with the real dashboard components:

Machine utilization
87%
4 machines, 480 min+4%

When less is better, set good_direction = "down": a rise then turns red and a fall green. Here late jobs went up by one, so the trend is red, and the description line is read from the run output with description_key:

[[widgets]]
type = "metric"
id = "late-jobs"
title = "Late jobs"
value_key = "totals.late_jobs"
delta_key = "totals.late_jobs_delta"
good_direction = "down"
description_key = "totals.late_jobs_note"
Late jobs
1
1 of 6 jobs+1

kpi-rail was removed

The kpi-rail widget, which grouped several tiles in one widget, is gone: write one metric per number instead. A dashboard that still uses it keeps showing its single-value tiles as separate metric cards, but harumi dashboard validate reports it as an unknown type and its per-entity rows_key lists are no longer drawn.

Table

table

A sortable data grid bound to an array of row objects.

Prop

Type

[[widgets]]
type = "table"
id = "jobs"
title = "Jobs"
rows_key = "jobs"
columns = [
  { key = "job", label = "Job" },
  { key = "priority", label = "Priority" },
  { key = "due", label = "Due (min)" },
  { key = "completed", label = "Completed (min)" },
  { key = "slack", label = "Slack (min)" },
  { key = "cost", label = "Cost" },
  { key = "status", label = "Status" },
]

Live preview, rendered with the real dashboard components:

Jobs
JobPriorityDue (min)Completed (min)Slack (min)CostStatus
Job AStandard3603303026800On time
Job BRush4003802024100On time
Job CStandard4804404021500On time
Job DStandard4804602019900On time
Job ERush420470-5022700Late
Job FStandard4804503013400On time

Charts

chart

One widget for every chart picture, picked by variant: line, bar, area, scatter, pie or donut. Switching how data is drawn never means switching widget, and the fonts, grid, grays and spacing are the product's own, so every chart looks like it belongs.

Prop

Type

[[widgets]]
type = "chart"
id = "convergence"
title = "Solver convergence"
variant = "line"
data_key = "convergence"
x_key = "t"
series = [
  { key = "best", label = "Best makespan" },
  { key = "bound", label = "Lower bound" },
]

Live preview, rendered with the real dashboard components:

Solver convergence

The same type with variant = "bar", comparing categories instead of a trend:

[[widgets]]
type = "chart"
id = "machine-time"
title = "Machine time by state"
variant = "bar"
data_key = "machine_hours"
x_key = "machine"
series = [
  { key = "busy", label = "Busy" },
  { key = "setup", label = "Setup" },
  { key = "idle", label = "Idle" },
]

Choosing a variant

Your questionUse
How does it change over time?line (or area to emphasize volume)
Which category is bigger?bar, with orientation = "horizontal" when the labels are long
What share of the whole is each part?pie or donut, only for about six slices or fewer
How do two numbers relate?scatter

A pie/donut shows only positive values, in source order unless you set sort, and past eight slices the smallest are folded into one Other slice — a pie of a dozen slivers says nothing, so prefer a sorted bar for that.

[[widgets]]
type = "chart"
id = "cost-share"
title = "Where the cost goes"
variant = "donut"
data_key = "cost_by_machine"
x_key = "machine"
series = [{ key = "cost", label = "Cost" }]
sort = "desc"
format = "currency"

Several groups: group_key, stack and series[].mark

When each row is one category in one group (long format), group_key turns every distinct group into its own colored series, so you don't list them by hand. Add stack to pile them up:

[[widgets]]
type = "chart"
id = "cost-mix"
title = "Cost by machine and shift"
variant = "bar"
stack = "stacked"
data_key = "cost_by_shift"
x_key = "machine"
group_key = "shift"
series = [{ key = "cost", label = "Cost" }]
format = "currency"

stack = "percent" normalizes each category to 100% (bars and areas only; a line series makes it fall back to stacked). To mix pictures on one axis, give a series its own mark:

[[widgets]]
type = "chart"
id = "output-vs-target"
title = "Output against target"
variant = "bar"
data_key = "daily"
x_key = "day"
series = [
  { key = "output", label = "Output" },
  { key = "target", label = "Target", mark = "line" },
]

line-chart and bar-chart still work

Dashboards written with line-chart or bar-chart keep rendering: the platform reads them as chart with variant = "line" or "bar". Write chart in anything new.

Timelines & schedules

timeline

A resource-row timeline on a numeric time axis — the current, recommended type for scheduling/job-shop solver results (one row per machine/resource, one bar per task). Supersedes gantt-chart (below): interrupted work folds into one item drawn with gaps, non-working spans render as background bands, the axis is drag-to-zoom, and a [clock] (see Dashboard datasets, metrics, and the clock) puts its play bar at the top of the card and shows as a now-marker.

Prop

Type

[[widgets]]
type = "timeline"
id = "schedule"
title = "Machine schedule"
items_key = "schedule"
id_key = "op"
color_key = "job"
colors = { "Job E" = "red-600" }
regions_key = "breaks"
region_label_key = "label"
region_resource_key = "resource"
time_unit = "min"

Live preview, rendered with the real dashboard components:

Machine schedule
CNC 1
CNC 2
Lathe
Press

gantt-chart

A resource-row Gantt chart on a numeric time axis — the same shape timeline covers, kept so dashboards written before timeline existed keep rendering. Prefer timeline for a new dashboard.

Prop

Type

[[widgets]]
type = "gantt-chart"
id = "schedule"
title = "Machine schedule"
tasks_key = "schedule"
color_key = "job"
time_unit = "min"

Live preview, rendered with the real dashboard components:

Machine schedule
0 min100 min200 min300 min400 min
CNC 1
A1 · Mill
C1 · Mill
E1 · Mill
E1 · Mill
B3 · Finish
D3 · Finish
CNC 2
B1 · Mill
D1 · Mill
A3 · Finish
F1 · Mill
C3 · Finish
Lathe
A2 · Turn
C2 · Turn
E2 · Turn
F2 · Turn
Press
B2 · Press
D2 · Press
A4 · Press
E3 · Press

Breakdowns

treemap

A flat treemap — one rectangle per row, area proportional to value_key, labeled from name_key. Good for a breakdown where relative size matters more than exact values (e.g. cost by machine).

Prop

Type

[[widgets]]
type = "treemap"
id = "machine-load"
title = "Busy minutes by machine"
items_key = "machine_hours"
value_key = "busy"
name_key = "machine"
capacity_key = "totals.capacity"

Live preview, rendered with the real dashboard components:

Busy minutes by machine

heatmap

An entity x time-bucket grid, shaded by intensity — one row per resource, one column per bucket (e.g. hour of day). A (resource, bucket) pair with no matching row renders as a distinct unavailable cell rather than a zero.

Prop

Type

[[widgets]]
type = "heatmap"
id = "hourly-cycles"
title = "Cycles per hour"
items_key = "hourly_cycles"
resource_key = "resource"
bucket_key = "bucket"
value_key = "value"
unit = "cycles"

Live preview, rendered with the real dashboard components:

Cycles per hour
 678910111213
CNC 13844474522414643
CNC 23541391218404437
Lathe–28333615313430
Press526158602755––

Maps

map

Geocoded points and/or routes on a real-world map — delivery stops, vehicle routes, facility locations. Set points_key, routes_key, or both: a stop layer plus the paths connecting them. A widget with neither renders an empty state. Clicking a point toggles it in the dashboard's selection, joined by id_key the same way timeline's is.

Prop

Type

[[widgets]]
type = "map"
id = "deliveries"
title = "Delivery run"
points_key = "stops"
label_key = "label"
id_key = "id"
color_key = "van"
routes_key = "routes"
route_color_key = "van"

Live preview, rendered with the real dashboard components:

Delivery run

Tiles in production

OpenStreetMap's public tile servers are fine for trying the widget out, but they're a shared, rate-limited service. For a dashboard with real traffic, set tile_url to your own tile server or a paid provider, and attribution to its credit.

Legacy chart types

line-chart and bar-chart predate chart, which folds both into one type picked by variant. They are still fully supported — a dashboard written before chart existed keeps rendering, because the platform reads each as a chart — but they are no longer separate widgets, so prefer chart for anything new.

Legacy typeSame as
type = "line-chart"type = "chart" with variant = "line"
type = "bar-chart"type = "chart" with variant = "bar"

They take the same data_key, x_key and series keys as chart; a variant you set yourself wins over the one the old name implies.

Checking your widgets before you commit

harumi dashboard widgets prints this same reference from your terminal (optionally filtered with --type), and harumi dashboard validate checks a real dashboard.toml against it — flagging an unknown type, a missing required key, or (given --against, --run, or --latest) a dot-path that doesn't resolve against an actual output.json.

On this page