Custom ComposableChart on Legacy dashboards
This guide walks through adding a custom composable chart to a Legacy session detail page. For v2 / dashboard-v2 pages, use ComposableChart (Next) instead.
Prerequisites
- Legacy dashboard node output includes
combined-custom-chartswith named datasets - Layout uses mount id
ComposableChart - heat-next build includes
ComposableChartLegacy(runnpm run build:libinui/dashboardwhen developing locally)
1. Shape runner output
Publish datasets the resolver understands:
{
"combined-custom-charts": [
{
"title": "My chart",
"datasets": [
{
"name": "Speed",
"type": "area",
"unit": "km/h",
"data": [
{ "DateTime": "2025-02-17T15:36:37.000Z", "Data": 42 }
]
},
{
"name": "Comms window",
"type": "rangeBar",
"data": [
{
"StartTime": "2025-02-17T15:37:00.000Z",
"EndTime": "2025-02-17T15:38:30.000Z"
}
]
},
{
"name": "Report events",
"type": "scatter1",
"data": [
{ "DateTime": "2025-02-17T15:40:00.000Z", "Data": 1 }
]
}
]
}
]
}Dataset name values must match your manifest bindings exactly (case-insensitive match).
2. Author the legacy manifest
Start from the terrain reference:
- TypeScript (Storybook/tests only):
terrainTimelineStoryManifestinui/dashboard/src/components/organisms/composable-chart/legacy/fixtures/terrainStory.fixture.ts - Docs: ComposableChart (Legacy) manifest section
Minimal example:
{
"layout": {
"anchor": "time",
"elements": [
{
"type": "area",
"order": 0,
"height": 160,
"series": [{ "id": "speed", "label": "Speed", "color": "#81842C" }]
},
{
"type": "ranges_lane",
"order": 1,
"label": "Comms",
"height": 32
},
{
"type": "events_lane",
"order": 2,
"label": "Reports",
"height": 32,
"eventSeries": [{ "id": "reports", "label": "Reports", "color": "#666" }]
}
]
},
"bindings": [
{
"order": 0,
"source": {
"kind": "area_series",
"series": [{ "seriesId": "speed", "datasetName": "Speed", "datasetType": "area" }]
}
},
{
"order": 1,
"source": { "kind": "dataset", "datasetName": "Comms window", "datasetType": "rangeBar" }
},
{
"order": 2,
"source": { "kind": "multi_dataset", "datasets": { "reports": "Report events" } }
}
],
"dataSource": { "key": "combined-custom-charts", "chartIndex": 0 }
}3. Embed manifest in layout
Add the manifest to the layout column configuration:
{
"layoutConfiguration": [
{
"cols": [
{
"component": "ComposableChart",
"colspan": 12,
"marginBottom": 24,
"configuration": {
"composableChartLegacyManifest": { }
}
}
]
}
]
}Replace { } with your manifest JSON. If omitted, the SPA wrapper uses the terrain reference manifest (demo only).
4. Verify in ui/legacy
- Deploy or load a session whose dashboard layout includes
ComposableChart. - Confirm
combined-custom-chartsloads (same path as TimelineChart). - Check for resolver warnings (shown inline in dev when datasets are missing).
- Exercise local playback: scrubber should move the yellow cursor when
anchoris"time".
5. Optional: unit test your manifest
In ui/dashboard, resolve against a fixture payload:
import { resolveLegacyComposableChart } from "@/components/organisms/composable-chart/legacy";
import { buildTerrainLegacyPayload } from "@/components/organisms/composable-chart/legacy/fixtures/terrainLegacyPayload.fixture";
const result = resolveLegacyComposableChart({
manifest: myManifest,
payload: buildTerrainLegacyPayload(),
});
expect(result.warnings).toEqual([]);
expect(result.slices.length).toBeGreaterThan(0);6. Storybook reference
Compare with TerrainTimelineMock in ui/dashboard Storybook (Organisms/ComposableChart). Legacy binding should visually match when dataset names and layout align with the terrain reference.
Limitations reminder
- No
$heat-dataserviceor TimelineClock sync with map playback - No in-chart
playbackslice (placeholder only) - Spanning line times must be session-relative ms in layout, not v1 DateTime strings