Layout and ComposableChart
heat.layout builds v2 layoutConfiguration JSON for heat-system-next-dimension. Layout channels arrays must list the same string ids you used in heat.dataservice channel specs.
Two ways to supply layout:
| Approach | Where | Best for |
|---|---|---|
| Production | layoutConfiguration on the dimension node | Stable dashboards, template review |
| Iteration | suggestedLayoutConfiguration on arbex buildRoot() + dimension layoutFromParentOutput: true | Prototyping in script without editing dimension JSON |
Layout builder basics
const layout = heat.layout
.createBuilder({
version: "1.0.0",
realms: ["default"],
})
.addRow()
.addColumn("BarChart", {
name: "Pods",
titleContent: "Pod counts",
channels: ["cluster-kpis"],
colspan: 6,
barChartItem: { /* widget-specific keys */ },
})
.build();| API | Why |
|---|---|
createBuilder(options?) | Realms, realm filter, optional realm groups; global playback off unless configuration.enableGlobalPlaybackControls: true; ComposableChart click-lock and hover-sync scrubbing off unless configuration.scrubberOn: true |
addRow(opts?) | New grid row (category, tab, title, collapsible, …) |
addColumn(componentId, config) | Single-widget columns (BarChart, MapDisplay, StatsSummary, …) |
build() | Final layout object |
Each widget type uses a dedicated config key (barChartItem, mapDisplayItem, flightPathItem, …). See Next dashboard components for per-widget contracts.
ComposableChart (preferred for multi-lane charts)
Why ComposableChart: one column, shared time (or index) axis, multiple stacked slices (area, events lane, ranges lane, legend, static values, transcript), each bound to dataservice channel ids.
Fluent flow:
.addComposableChart({
name: "MainChart",
titleContent: "Metrics",
channels: ["synthetic-metric"], // must match ds channel id(s)
colspan: 12,
})
.anchor("time") // "time" | "index" | "none"
.xAxis({ timeLabelMode: "elapsed" }) // optional; "wallClock" opts in to real time-of-day labels
.addAreaSlice({ label: "Series", height: 220, order: 1 })
.addSeries({
id: "line-1",
label: "Synthetic metric",
channel: "synthetic-metric",
color: "#38BDF8",
render: "line",
})
.yAxis({ domain: [0, 100] })
.endComposableChart()Slice helpers
| Method | Use for |
|---|---|
addAreaSlice | Line or step series (addSeries, yAxis) |
addScatterSlice | Scatter points |
addEventsLaneSlice | Boolean events (addEventSeries) |
addRangesLaneSlice | Interval bands. Single-series: chain .channels([...]). Multi-series: chain addRangeSeries({ id, label, color, tooltipLabel, barHeight, channel }) |
addLegendSlice | Legend items (addLegendItem) |
addSpanningLinesSlice | Vertical markers (times([...])) |
addStaticSlice | Value channels (KPI-style) |
addTranscriptSlice | Timed transcript utterances (value channel; out-of-band, clock-synced) |
addPlaybackSlice() | In-chart seek bar (no play button) |
addFlightTracksSlice() | Path + altitude pane (flightTracks({ leftPercent, topDownView, trailMs, terrain }); terrain.manifestUrl points at an env-assets terrain manifest, see the composable chart component docs) |
Return to the row builder with endComposableChart().
Realm groups (independent realm selection)
By default the page realm dropdown sets one realm for every widget. When parts of a page must show different realms at the same time (a vignette timeline beside a whole-session assessment, or two timelines comparing two vignettes), declare realm groups and subscribe widgets to them.
const layout = heat.layout
.createBuilder({
version: "1.0.0",
realms: ["default", "vignette_1", "assessment_a"],
configuration: {
showRealmFilterDropdown: true,
realmGroups: [
{ id: "main", realms: ["default", "vignette_1"], defaultRealm: "default" },
{ id: "assessment", realms: ["assessment_a"] },
],
realmGroupId: "main", // the page dropdown drives the "main" group
},
})
.addRow()
.addColumn("TimelineChart", {
name: "MainTimeline",
titleContent: "Timeline",
channels: ["timeline-ch"],
colspan: 6,
realmGroupId: "main",
})
.addColumn("StatsSummary", {
name: "AssessmentStats",
titleContent: "Assessment",
channels: ["stats-ch"],
colspan: 6,
realmGroupId: "assessment",
statsItems: { position: 1, columns: [{ label: "Score", name: "score" }] },
})
.build();Rules:
- Each group selects one realm at a time. A widget’s
realmGroupIdmust match a declared group id; an unmatched id leaves the widget with no realm. - Widgets without
realmGroupIdfollow the page dropdown, so layouts that omitrealmGroupsbehave exactly as before. - A widget with its own dropdown (TimelineChart) updates its group, so every widget in that group moves together.
modalItem.realmGroupIdlets a modal’s dropdown drive a group too.
Optional realmClock. Set realmClock: "<realm name>" on any column to link the widget to that realm’s clock. It is optional and independent of realmGroupId. The layout carries the value and the data service returns it alongside the widget’s title and tooltip.
Deprecated defaultRealm. Existing scripts that set configuration.defaultRealm or a column defaultRealm keep working: the page opens on that realm and a widget’s own dropdown starts there. Prefer realmGroups[].defaultRealm for new layouts. This layout key is unrelated to heat.dataservice.createBuilder({ defaultRealm }), which only decides where channels are stored.
Ship layout with data from one run
const ds = heat.dataservice.createBuilder({ defaultRealm: "default" });
ds.addGroup("demo", "Demo");
ds.series({ id: "synthetic-metric", name: "Metric", groupId: "demo" }, points);
const layout = heat.layout.createBuilder({ version: "1.0.0", realms: ["default"] })
.addRow()
.addComposableChart({ name: "c", titleContent: "Chart", channels: ["synthetic-metric"], colspan: 12 })
.anchor("time")
.addAreaSlice({ height: 220, order: 1 })
.addSeries({ id: "s1", label: "Metric", channel: "synthetic-metric" })
.endComposableChart();
return ds.buildRoot({ suggestedLayoutConfiguration: layout });Enable on the arbex node:
{
"dataserviceOutput": {
"enabled": true,
"persistence": "dataservice-root",
"layoutOutput": { "enabled": true, "require": true }
}
}On heat-system-next-dimension: layoutFromParentOutput: true. Preset: sample-arbex-composable-dashboard. Script: sample-arbex-composable-dashboard.js.
Deprecated widgets
TimelineChart via addColumn("TimelineChart", …) is deprecated for new layouts. Prefer ComposableChart. See TimelineChart.