Skip to Content
This documentation is provided with the HEAT environment and is relevant for this HEAT instance only.
RunnersSystem UtilsArbex API referenceLayout and ComposableChart

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:

ApproachWhereBest for
ProductionlayoutConfiguration on the dimension nodeStable dashboards, template review
IterationsuggestedLayoutConfiguration on arbex buildRoot() + dimension layoutFromParentOutput: truePrototyping 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();
APIWhy
createBuilder(options?)Realms, realm filter, optional realm groups; global playback off unless configuration.enableGlobalPlaybackControls: 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 .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

MethodUse for
addAreaSliceLine or step series (addSeries, yAxis)
addScatterSliceScatter points
addEventsLaneSliceBoolean events (addEventSeries)
addRangesLaneSliceInterval bands. Single-series: chain .channels([...]). Multi-series: chain addRangeSeries({ id, label, color, tooltipLabel, barHeight, channel })
addLegendSliceLegend items (addLegendItem)
addSpanningLinesSliceVertical markers (times([...]))
addStaticSliceValue channels (KPI-style)
addTranscriptSliceTimed 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 realmGroupId must match a declared group id; an unmatched id leaves the widget with no realm.
  • Widgets without realmGroupId follow the page dropdown, so layouts that omit realmGroups behave exactly as before.
  • A widget with its own dropdown (TimelineChart) updates its group, so every widget in that group moves together.
  • modalItem.realmGroupId lets a modal’s dropdown drive a group too.

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.