Skip to Content
This documentation is provided with the HEAT environment and is relevant for this HEAT instance only.
Dashboard ComponentsLegacyComposableChart (Legacy)

ComposableChart (Legacy)

Stacked, synchronized multi-slice charts on Legacy dashboards using v1 combined-custom-charts payloads and an explicit legacy manifest (layout + bindings). Rendering uses the same SVG/D3 ComposableChart presentational component exported from heat-next, wrapped by ComposableChartLegacy.

When to use legacy binding vs v2 full support

⚠️

Full, production-grade ComposableChart support is the v2 path: ComposableChartDS in ui/dashboard with $heat-dataservice, channel URIs, and page-level PlayBackControlDS driving TimelineClock. Use this legacy binding only when you must ship on Legacy dashboards (ui/legacy, v1 facade) and cannot migrate the page to dashboard-v2 yet.

Legacy binding (this doc)v2 full support
Frontendui/legacy + heat-next ComposableChartLegacyui/dashboard + ComposableChartDS
Datav1 combined-custom-charts (DateTime strings)$heat-dataservice channels (series / events / ranges)
Layout configcomposableChartLegacyManifest on layout col (legacy-only)composableChartItem in v2 layout schema
Playback syncLocal scrubber in widget onlyRealm TimelineClock syncs with map and other DS widgets
Channel URIsNot usedRequired per slice
Manifest validationTypeScript + docs onlyPartial JSON schema in heat-layout-schema.json

Dashboard generation

Value
Runner nodedashboard (v1)
Frontendui/legacy via heat-next (ComposableChartLegacy)
Legacy API / facade keycombined-custom-charts
Presentational chartheat-next ComposableChart (SVG + D3)

Layout mount identifier

ComposableChart , set on each column in layoutConfiguration:

{ "component": "ComposableChart", "colspan": 12, "marginBottom": 24, "configuration": { "composableChartLegacyManifest": { } } }

If composableChartLegacyManifest is omitted, the legacy wrapper shows an empty state. Production layouts must embed an explicit manifest (layout + bindings). The terrain prototype manifest exists only for Storybook and unit tests in ui/dashboard.

See Legacy mount registry.

Data payload key

combined-custom-charts , array of chart objects published by the v1 dashboard node (same source as TimelineChart on legacy pages).

{ "combined-custom-charts": [ { "title": "Terrain timeline", "datasets": [ { "name": "Air Speed", "type": "area", "data": [{ "DateTime": "2025-02-17T15:36:37.000Z", "Data": 50 }] } ] } ] }

Limitations

LimitationDetail
No channel URIsBindings map v1 dataset names to slices; no $heat-dataservice ingest
No cross-widget clockLocal playback only; does not sync with map or other widgets
playback sliceNot supported , reserved; renders placeholder if present in layout
Manual bindingsNo auto-generation from combined-custom-charts without a manifest
No legacy manifest JSON schemaDocumented TypeScript shape only
Spanning line timesMust be session-relative ms in layout (element.times), not v1 DateTime
x-axis ticksSession-relative ms in layout.xAxis.ticks, aligned with resolver x-domain
Static sliceInline static binding value only (no separate v1 stats key resolver yet)

v1 dataset types

The resolver reuses the same type rules as v2 ingest extractTimelineData:

v1 dataset.typeResolved slice dataTypical slice
area, line (default series){ kind: "series" } or multi-series in one area slicearea
scatter1, scatterEvent timesevents_lane
rangeBar{ kind: "ranges" }ranges_lane

Series point fields

FieldDescription
DateTime or StartTimeISO timestamp (absolute); converted to session-relative x
Data, value, or DurationNumeric y value for area/scatter series

Range point fields

FieldDescription
StartTime, EndTimeISO timestamps for range start/end

Event point fields

FieldDescription
DateTime or StartTimeISO timestamp for discrete event

Legacy manifest shape

Legacy manifests are not part of the v2 heat-layout-schema.json. They pair a v2-compatible layout (slice vocabulary) with bindings that describe how each element.order maps to v1 data.

type ComposableChartLegacyManifest = { layout: ComposableChartLayoutItem; // same slice types as v2 layout bindings: LegacySliceBinding[]; dataSource?: { key: "combined-custom-charts"; chartIndex?: number }; }; type LegacySliceBinding = { order: number; // matches element.order in layout source: LegacySliceBindingSource; };

Binding source kinds

source.kindUsed forDescription
layout_onlylegend, spanning_linesData comes from layout fields only
area_seriesarea (multi-series)Maps each layout series[].id to a v1 dataset name
datasetarea (single), scatter, ranges_lane, single event laneOne v1 dataset by datasetName
multi_datasetevents_laneMaps each eventSeries[].id to a v1 dataset name
staticstaticInline { value: unknown } (e.g. StatsCard payload)

Example manifest (Storybook / tests only)

Source: ui/dashboard/src/components/organisms/composable-chart/legacy/fixtures/terrainStory.fixture.ts (terrainTimelineStoryManifest). Not exported from heat-next.

{ "layout": { "anchor": "time", "xAxis": { "ticks": [0, 300000, 600000, 900000] }, "elements": [ { "type": "legend", "order": 0, "height": 40, "legendItems": [] }, { "type": "area", "order": 1, "height": 160, "series": [ { "id": "speed", "label": "Speed", "color": "#81842C", "render": "line_gradient", "unit": " km/h" }, { "id": "elevation", "label": "Elevation", "color": "#999999", "render": "step_area", "unit": " m" } ]}, { "type": "spanning_lines", "order": 2, "times": [198000, 630000, 639000], "style": { "color": "#C13D40", "visibilityKey": "hits" } }, { "type": "ranges_lane", "order": 3, "label": "Comms", "height": 32 }, { "type": "events_lane", "order": 4, "label": "Commander", "height": 32, "eventSeries": [ { "id": "cmdr_rep", "label": "Reports" }, { "id": "cmdr_cmd", "label": "Commands" } ]} ] }, "bindings": [ { "order": 0, "source": { "kind": "layout_only" } }, { "order": 1, "source": { "kind": "area_series", "series": [ { "seriesId": "speed", "datasetName": "Air Speed", "datasetType": "area" }, { "seriesId": "elevation", "datasetName": "Altitude", "datasetType": "area" } ] } }, { "order": 2, "source": { "kind": "layout_only" } }, { "order": 3, "source": { "kind": "dataset", "datasetName": "Comms Events", "datasetType": "rangeBar" } }, { "order": 4, "source": { "kind": "multi_dataset", "datasets": { "cmdr_rep": "Commander Reports", "cmdr_cmd": "Commander Commands" } } } ], "dataSource": { "key": "combined-custom-charts", "chartIndex": 0 } }

Slice layout fields (colors, y-axis bands, legend items, spanning times) mirror the Next ComposableChart reference Storybook terrain example.

Step-by-step: implement a custom legacy chart

  1. Runner output , Ensure combined-custom-charts includes datasets with stable name and correct type (area, scatter1, rangeBar).
  2. Design layout , Copy slice structure from the Next doc or terrain reference; assign unique order per slice.
  3. Write bindings , One binding per slice that needs v1 data; use layout_only for legend and spanning lines whose times live in layout.
  4. Embed manifest , Add configuration.composableChartLegacyManifest on the layout col (required for legacy UI).
  5. Mount widget , Set "component": "ComposableChart" in layoutConfiguration.
  6. Verify , Load session in ui/legacy; check resolver warnings in dev builds; compare with Storybook terrain mock.

See also: ComposableChart custom legacy guide.

Resolver API

Exported from heat-next:

import { resolveLegacyComposableChart, ComposableChartLegacy, type ComposableChartLegacyManifest, } from "heat-next"; const { slices, xDomain, xAxis, anchor, warnings } = resolveLegacyComposableChart({ manifest, payload: combinedCustomCharts, timeOriginMs: optionalOverride, });
Result fieldDescription
slicesComposableChartResolvedSlice[] ready for ComposableChart
xDomainSession-relative [min, max] ms from data extent
xAxis, anchorPassed through from manifest layout
warningsNon-fatal issues (missing dataset, missing binding, unsupported slice)

Slice binding reference

legend , layout_only

Provide legendItems on the layout element. No v1 dataset.

area , area_series or dataset

  • Multi-series: area_series with seriesId matching layout series[].id.
  • Single series: dataset binding; layout may omit series array.

spanning_lines , layout_only

Set element.times in session-relative milliseconds (same units as resolved x-domain). Optional style.color and style.visibilityKey for legend toggle.

ranges_lane , dataset

Bind to a v1 dataset with type: "rangeBar".

events_lane , multi_dataset or dataset

  • Multiple series: multi_dataset keys must match eventSeries[].id.
  • Single series: dataset with scatter1 type.

scatter , dataset

Maps continuous v1 series points to scatter positions.

static , static

{ "order": 7, "source": { "kind": "static", "value": { "statsCard": [] } } }

playback , not supported

If present in layout, resolver emits a warning and the slice shows the standard “Not ready for use” placeholder. Use the widget’s local playback bar (legacy) or page-level controls on v2.