Layout reference
A Layout is the JSON that says how a Dimension is rendered: rows of widgets, each bound to named channels. It is separate from the data. The data service payload says what data exists, and the layout says which widget shows which channel and how.
A dashboard node publishes the layout with the dimension. On the dashboard-tools runner that node is dashboard-v2 and the layout is its layoutConfiguration key. On system-utils it is heat-system-next-dimension, which can also take the layout from its parent output when layoutFromParentOutput is true. The Next dashboard reads the stored layout, resolves each column’s channels against the realm in view and renders.
A layout is not a Legacy v1 layout. Legacy used cols[].component mount ids and fixed payload keys per widget. The v2 layout described here binds by channel id and is documented for the Next dashboard only.
Top-level document
{
"$schema": "https://heatvr.io/layout-schema.json",
"version": "1.0.0",
"configuration": {
"enableGlobalPlaybackControls": true,
"defaultRealm": "default",
"showRealmFilterDropdown": false
},
"realms": ["default"],
"components": {
"rows": []
}
}| Key | Required | Meaning |
|---|---|---|
version | No | Layout schema version label. dashboard-v2 writes "1.0" when it is missing. |
configuration | No | Page-level settings. See below. |
realms | Yes | The realm names this layout knows about. A Realm is a namespace for channels within one dimension, used to compare trainees or runs side by side. |
components.rows | Yes | The rows of widgets. An empty array is valid and renders an empty page. |
$schema | No | Points editors at the schema. dashboard-v2 sets it on the stored layout. |
Page configuration
| Key | Type | Meaning |
|---|---|---|
enableGlobalPlaybackControls | boolean | Show the page-level playback bar that drives every playback-aware widget through the realm clock. Default false. |
enableLiveDataPolling | boolean | Poll for new data while the dashboard is open. |
defaultRealm | string | The realm selected when the page opens. |
showRealmFilterDropdown | boolean | Show the realm picker at the top of the page. |
realmFilterLabel | string | Label for that picker. Defaults to “Realms”. |
modalButton, modalComponentsName | string, string[] | Present in the schema. The dashboard today derives the modal button from a Modal row instead. See Modal. |
Rows
Each entry in components.rows is one horizontal band of the page.
| Key | Type | Meaning |
|---|---|---|
cols | array | The columns in the row. Required. |
tab | string | Name of the tab that contains the row, when the page is tabbed. |
category | string | Free grouping label. |
dualComponent | "None", "Dual", "Side" | Whether the row renders a dual component and how. Widgets that support it are marked supportsDualColumn in the component registry. |
isCollapsible | boolean | Whether the row can be folded away. |
marginBottom | number | Bottom margin in pixels. |
Columns
Each entry in cols places one Widget.
| Key | Type | Meaning |
|---|---|---|
component | string | The widget id, for example ComposableChart. Case-sensitive. The full list is on the components index. |
colspan | number | Width as a proportion of the row. Two columns with 6 and 6 share the row equally, as do 1 and 1. |
visible | boolean | Hide the column without removing it. |
marginBottom | number | Bottom margin in pixels. |
configuration | object | The widget’s binding and options. See below. |
Column configuration
Three keys are common to every widget and are required by the component registry.
| Key | Meaning |
|---|---|
name | The widget instance name. Unique within the layout, so two charts of the same type can coexist. |
titleContent | The panel title. |
channels | Channel ids this widget reads, resolved in the realm in view. Some widgets also bind channels inside their own configuration object and leave this list short or empty. |
Optional presentation keys apply across widgets.
| Key | Meaning |
|---|---|
shouldRenderTitle, titleIcon | Whether to show the title and an icon before it |
tooltipContent, shouldRenderTooltip | Tooltip text and whether to show it |
defaultRealm, showRealmFilterDropdown | Per-widget realm selection for widgets marked supportsRealmFilter |
enablePlaybackControls | Show a playback bar on this widget for widgets marked supportsPlaybackControls |
showOpenModalButton | Show a button that opens the page modal |
chartHeight, truncateXAxisLabels | BarChart presentation |
Per-widget configuration key
Most widgets take their options under one named object. The key is fixed per widget and recorded as widgetConfigKey in the component registry (ui/dashboard/src/dashboard-manifest/registry.ts). The registry also records requiredConfigurationKeys, which is always titleContent, channels and name plus the widget key where one exists.
| Component | Widget config key | Playback | Realm filter | Dual column |
|---|---|---|---|---|
BarChart | barChartItem | No | No | No |
CircuitStats | circuitStatsItems | No | No | No |
CognitiveLoad | cognitiveItem | Yes | No | Yes |
CognitiveLoadBreakdown | cognitiveLoadBreakdownItem | No | No | No |
Comms | commsItem | Yes | No | No |
ComposableChart | composableChartItem | Yes | Yes | Yes |
CustomRenderer | customRendererItem | No | No | No |
EyeTracking | None | No | No | Yes |
EyeTrackingContainer | eyeTrackingItem | No | No | No |
FlightPath | flightPathItem | Yes | No | Yes |
ImpactHeatmap | None | No | No | No |
LeaderBoard | leaderBoardItem | No | No | Yes |
MapDisplay | mapDisplayItem | Yes | Yes | Yes |
Modal | modalItem | Yes | Yes | Yes |
ProgressScore | progressScoreItems | No | No | No |
ResponseTime | responseTimeItem | No | No | Yes |
StatsSummary | statsItems | No | No | No |
TacticalMap | None | Yes | No | No |
TimelineChart | timelineItems | Yes | No | Yes |
VideoPlayback | videoPlaybackItem | Yes | No | No |
TimelineChart is only partially migrated to the data service. Use ComposableChart for new layouts. CustomRenderer binds no channels and is documented as layout only.
The JSON schema
The schema lives in the dashboard source at ui/dashboard/src/lib/data-service/data-lib/heat-client/docs/heat-layout-schema.json (view on GitHub ). Its $id is https://heatvr.io/layout-schema.json, which is the value the presets put in $schema. Each widget’s configuration object has its own definition under $defs, for example #/$defs/CommsItemSchema and #/$defs/StatsSummarySchema.
This is the schema trimmed to its skeleton. The component enum and the list of widget keys are the parts that change when a widget is added.
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://heatvr.io/layout-schema.json",
"title": "LayoutSchema",
"type": "object",
"required": ["components", "realms"],
"properties": {
"configuration": {
"type": "object",
"properties": {
"enableGlobalPlaybackControls": { "type": "boolean", "default": false },
"enableLiveDataPolling": { "type": "boolean" },
"defaultRealm": { "type": "string" },
"showRealmFilterDropdown": { "type": "boolean" },
"realmFilterLabel": { "type": "string" }
}
},
"realms": { "type": "array" },
"components": {
"type": "object",
"properties": {
"rows": {
"type": "array",
"items": {
"type": "object",
"properties": {
"tab": { "type": "string" },
"dualComponent": { "type": "string", "enum": ["None", "Dual", "Side"] },
"isCollapsible": { "type": "boolean" },
"marginBottom": { "type": "number", "minimum": 0 },
"cols": {
"type": "array",
"items": {
"type": "object",
"properties": {
"component": { "type": "string", "enum": ["BarChart", "ComposableChart", "StatsSummary", "..."] },
"visible": { "type": "boolean" },
"colspan": { "type": "number", "minimum": 1 },
"marginBottom": { "type": "number", "minimum": 0 },
"configuration": {
"type": "object",
"properties": {
"titleContent": { "type": "string" },
"channels": { "type": "array", "items": { "type": "string" } },
"name": { "type": "string" },
"composableChartItem": { "$ref": "#/$defs/ComposableChartItemSchema" },
"statsItems": { "$ref": "#/$defs/StatsSummarySchema" }
}
}
}
}
}
}
}
}
}
}
},
"$defs": {
"ComposableChartItemSchema": { "type": "object" },
"StatsSummarySchema": { "type": "object" }
}
}The "..." entry stands in for the remaining component ids. The $defs bodies are omitted here. Read the file for the full field lists.
How dashboard-v2 merges realms
The dashboard-v2 node stores the layout you configure, with realm hints filled in from the data it received. The steps below are taken from the runner’s _merge_layout_with_realms routine.
- It sets
$schematohttps://heatvr.io/layout-schema.jsonandversionto"1.0"when absent. - It appends every realm name found in the
$heat-dataservicepayload torealms, keeping the order you wrote and adding new names at the end. - When more than one realm results, it sets
configuration.showRealmFilterDropdowntotrueunless you set it. - When at least one realm exists, it sets
configuration.defaultRealmto the first realm unless you set it.
Nothing else in the layout is touched. Values you set always win. This means a template can ship a layout that names no realms at all and still open on the right one, and a template that names ["pilot-a", "pilot-b"] opens on pilot-a with the picker shown.
heat-system-next-dimension on system-utils applies the same four steps before it persists the layout, so a template behaves the same whichever publisher it ends in.
Related
- Next dashboard components for every component id
- Data service payload, the other half of the contract
- dashboard-v2 upstream contract
- Layout and ComposableChart for building layouts from Arbex
- Reference layouts per widget:
tools/arbex/reference/next/