Demonstrates how to create multiple synchronized subcharts with an interactive overview using SciChart.js
drawExample.ts
index.html
AxisSynchroniser.ts
vanilla.ts
theme.ts
SubChartsOverviewModifier.ts
1import {
2 SciChartSurface,
3 NumericAxis,
4 NumberRange,
5 FastLineRenderableSeries,
6 XyDataSeries,
7 ZoomExtentsModifier,
8 MouseWheelZoomModifier,
9 ZoomPanModifier,
10 ESubSurfacePositionCoordinateMode,
11 Rect,
12 EPerformanceMarkType,
13 SciChartSubSurface,
14 I2DSubSurfaceOptions,
15 EAutoRange,
16 EXyDirection,
17 CursorModifier,
18 ENumericFormat,
19 SeriesInfo,
20 CursorTooltipSvgAnnotation,
21} from "scichart";
22import { appTheme } from "../../../theme";
23import { SubChartsOverviewModifier } from "./SubChartsOverviewModifier";
24import { AxisSynchroniser } from "../../MultiChart/SyncMultiChart/AxisSynchroniser";
25
26const STREAM_INTERVAL_MS = 1000;
27const POINTS_ON_CHART = 180;
28const OVERVIEW_HEIGHT = 0.2;
29const SUBCHARTS_HEIGHT = 1 - OVERVIEW_HEIGHT;
30
31export type TMarkType = EPerformanceMarkType | string;
32
33export interface SubChartConfig {
34 id: string;
35 phase: number;
36 color: string;
37 title: string;
38}
39
40export interface SubChartManager {
41 updateSubCharts: (configs: SubChartConfig[]) => void;
42 addSubChart: (config: SubChartConfig) => void;
43 removeSubChart: (id: string) => void;
44 updateLayout: () => void;
45}
46
47interface SubChartRuntime {
48 subChart: SciChartSubSurface;
49 dataSeries: XyDataSeries;
50 phase: number;
51 color: string;
52 title: string;
53}
54
55const getYValue = (x: number, phase: number): number =>
56 Math.sin(x * 0.08 + phase) * 0.75 + Math.cos(x * 0.015 + phase * 0.5) * 0.25;
57
58const createLineData = (phase: number, startX: number, count: number) => {
59 const xValues: number[] = [];
60 const yValues: number[] = [];
61 for (let i = 0; i < count; i++) {
62 const x = startX + i;
63 xValues.push(x);
64 yValues.push(getYValue(x, phase));
65 }
66 return { xValues, yValues };
67};
68
69const getTooltipLegendTemplate = (seriesInfos: SeriesInfo[], svgAnnotation: CursorTooltipSvgAnnotation) => {
70 const legendItems = seriesInfos.filter((si) => si.isWithinDataBounds);
71 if (!legendItems.length) {
72 return "<svg width='100%' height='100%'></svg>";
73 }
74
75 const lineHeight = 18;
76 const padding = 8;
77 const titleY = padding + 12;
78 const legendHeight = padding * 2 + 12 + lineHeight * legendItems.length;
79 const currentTime = legendItems[0].formattedXValue;
80
81 let rows = `<text x="${padding}" y="${titleY}" font-size="12" font-family="Verdana" fill="#d4d7dd">Time: ${currentTime}</text>`;
82 legendItems.forEach((seriesInfo, index) => {
83 const y = titleY + 8 + (index + 1) * lineHeight;
84 const seriesColor = seriesInfo.stroke || "#E8C667";
85 rows += `<text x="${padding}" y="${y}" font-size="12" font-family="Verdana" fill="${seriesColor}">${seriesInfo.seriesName}: ${seriesInfo.formattedYValue}</text>`;
86 });
87
88 return `<svg width="100%" height="${legendHeight}">
89 <rect x="1" y="1" width="98%" height="${legendHeight - 2}" rx="4" ry="4" fill="#0D1523CC" stroke="#E8C667AA" stroke-width="1"/>
90 ${rows}
91 </svg>`;
92};
93
94export const drawExample = async (
95 rootElement: string | HTMLDivElement,
96 initialConfigs: SubChartConfig[]
97): Promise<{ wasmContext: any; sciChartSurface: SciChartSurface; manager: SubChartManager }> => {
98 const { wasmContext, sciChartSurface } = await SciChartSurface.create(rootElement);
99
100 const subChartMap = new Map<string, SciChartSubSurface>();
101 const subChartRuntimeMap = new Map<string, SubChartRuntime>();
102
103 let nextStreamX = Math.floor(Date.now() / 1000) + 1;
104 const initialVisibleRange = new NumberRange(nextStreamX - POINTS_ON_CHART, nextStreamX - 1);
105 const axisSynchroniser = new AxisSynchroniser(initialVisibleRange);
106
107 const mainXAxis = new NumericAxis(wasmContext, {
108 id: "mainXAxis",
109 isVisible: false,
110 autoRange: EAutoRange.Always,
111 });
112 const mainYAxis = new NumericAxis(wasmContext, {
113 id: "mainYAxis",
114 isVisible: false,
115 autoRange: EAutoRange.Always,
116 });
117
118 sciChartSurface.xAxes.add(mainXAxis);
119 sciChartSurface.yAxes.add(mainYAxis);
120
121 const overviewModifier = new SubChartsOverviewModifier({
122 overviewPosition: new Rect(0, SUBCHARTS_HEIGHT, 1, OVERVIEW_HEIGHT),
123 isTransparent: true,
124 axisTitle: "Overview - All Charts",
125 labelStyle: {
126 color: "#ffffff80",
127 fontSize: 10,
128 },
129 majorTickLineStyle: {
130 color: "#ffffff80",
131 tickSize: 6,
132 strokeThickness: 1,
133 },
134 yAxisGrowBy: new NumberRange(0.1, 0.1),
135 });
136
137 sciChartSurface.chartModifiers.add(overviewModifier);
138
139 const getCurrentConfigs = (): SubChartConfig[] =>
140 Array.from(subChartRuntimeMap.entries()).map(([id, runtime]) => ({
141 id,
142 phase: runtime.phase,
143 color: runtime.color,
144 title: runtime.title,
145 }));
146
147 const createCursorModifier = () =>
148 new CursorModifier({
149 modifierGroup: "subcharts-cursor",
150 showAxisLabels: true,
151 showTooltip: true,
152 isSvgOnly: false,
153 crosshairStroke: appTheme.ForegroundColor,
154 crosshairStrokeThickness: 1,
155 axisLabelFill: appTheme.ForegroundColor,
156 axisLabelStroke: "#0D1523",
157 tooltipContainerBackground: "#0D1523",
158 tooltipTextStroke: appTheme.ForegroundColor,
159 tooltipLegendTemplate: getTooltipLegendTemplate,
160 });
161
162 const createSubChart = (config: SubChartConfig, rect: Rect) => {
163 const subChartOptions: I2DSubSurfaceOptions = {
164 id: config.id,
165 position: rect,
166 coordinateMode: ESubSurfacePositionCoordinateMode.Relative,
167 };
168
169 const subChart = SciChartSubSurface.createSubSurface(sciChartSurface, subChartOptions);
170
171 const subXAxis = new NumericAxis(wasmContext, {
172 autoRange: EAutoRange.Always,
173 labelFormat: ENumericFormat.Date_HHMMSS,
174 cursorLabelFormat: ENumericFormat.Date_HHMMSS,
175 drawMinorGridLines: false,
176 maxAutoTicks: 6,
177 });
178
179 const subYAxis = new NumericAxis(wasmContext, {
180 autoRange: EAutoRange.Always,
181 growBy: new NumberRange(0.1, 0.1),
182 axisTitle: config.title,
183 axisTitleStyle: { fontSize: 14 },
184 drawMinorGridLines: false,
185 });
186
187 subChart.xAxes.add(subXAxis);
188 subChart.yAxes.add(subYAxis);
189
190 const data = createLineData(config.phase, nextStreamX - POINTS_ON_CHART, POINTS_ON_CHART);
191 const dataSeries = new XyDataSeries(wasmContext, {
192 xValues: data.xValues,
193 yValues: data.yValues,
194 dataSeriesName: config.title,
195 });
196
197 const lineSeries = new FastLineRenderableSeries(wasmContext, {
198 dataSeries,
199 strokeThickness: 3,
200 stroke: config.color,
201 opacity: 0.7,
202 });
203
204 axisSynchroniser.addAxis(subXAxis);
205 subChart.renderableSeries.add(lineSeries);
206
207 subChart.chartModifiers.add(
208 new ZoomPanModifier(),
209 new MouseWheelZoomModifier({ xyDirection: EXyDirection.XDirection }),
210 new ZoomExtentsModifier(),
211 createCursorModifier()
212 );
213
214 subChartMap.set(config.id, subChart);
215 subChartRuntimeMap.set(config.id, {
216 subChart,
217 dataSeries,
218 phase: config.phase,
219 color: config.color,
220 title: config.title,
221 });
222
223 subChart.zoomExtents();
224 };
225
226 const clearAllSubCharts = () => {
227 const currentSubCharts = Array.from(subChartMap.values());
228 currentSubCharts.forEach((subChart) => {
229 const xAxis = subChart.xAxes.get(0);
230 if (xAxis) {
231 axisSynchroniser.removeAxis(xAxis);
232 }
233 sciChartSurface.removeSubChart(subChart);
234 });
235 subChartMap.clear();
236 subChartRuntimeMap.clear();
237 };
238
239 const recreateSubChartsWithLayout = (configs: SubChartConfig[]) => {
240 sciChartSurface.suspendUpdates();
241 try {
242 clearAllSubCharts();
243
244 if (configs.length === 0) {
245 return;
246 }
247
248 configs.forEach((config, index) => {
249 const yStart = (index / configs.length) * SUBCHARTS_HEIGHT;
250 const height = (1 / configs.length) * SUBCHARTS_HEIGHT;
251 createSubChart(config, new Rect(0, yStart, 1, height));
252 });
253 } finally {
254 sciChartSurface.resumeUpdates();
255 }
256 };
257
258 const updateSubCharts = (configs: SubChartConfig[]) => {
259 recreateSubChartsWithLayout(configs);
260 };
261
262 const addSubChart = (config: SubChartConfig) => {
263 if (subChartMap.has(config.id)) {
264 return;
265 }
266 recreateSubChartsWithLayout([...getCurrentConfigs(), config]);
267 };
268
269 const removeSubChart = (id: string) => {
270 if (!subChartMap.has(id)) {
271 return;
272 }
273 recreateSubChartsWithLayout(getCurrentConfigs().filter((cfg) => cfg.id !== id));
274 };
275
276 const updateLayout = () => {
277 recreateSubChartsWithLayout(getCurrentConfigs());
278 };
279
280 recreateSubChartsWithLayout(initialConfigs);
281 sciChartSurface.zoomExtents();
282
283 const streamInterval = window.setInterval(() => {
284 subChartRuntimeMap.forEach((runtime) => {
285 runtime.dataSeries.append(nextStreamX, getYValue(nextStreamX, runtime.phase));
286 const excess = runtime.dataSeries.count() - POINTS_ON_CHART;
287 if (excess > 0) {
288 runtime.dataSeries.removeRange(0, excess);
289 }
290 });
291
292 nextStreamX += 1;
293 }, STREAM_INTERVAL_MS);
294
295 sciChartSurface.addDeletable({
296 delete: () => window.clearInterval(streamInterval),
297 });
298
299 const manager: SubChartManager = {
300 updateSubCharts,
301 addSubChart,
302 removeSubChart,
303 updateLayout,
304 };
305
306 return { wasmContext, sciChartSurface, manager };
307};
308This example demonstrates how to build a multi-panel chart layout in JavaScript using SciChart.js, where several subcharts are vertically stacked and synchronized through a shared interactive overview. The overview chart displays aggregated data from all subcharts and allows users to zoom and pan all charts simultaneously using a draggable range selector.
The chart is initialized by creating a single SciChartSurface, then dynamically adding multiple SciChartSubSurface instances. Each subchart owns its own NumericAxis pair and FastLineRenderableSeries, while an AxisSynchroniser keeps all X-axes aligned. Data is generated using XyDataSeries, simulating phase-shifted sine waves.
An interactive overview is implemented using a custom SubChartsOverviewModifier. This modifier creates an additional subsurface occupying the bottom 20% of the chart, clones renderable series from each subchart, and attaches an OverviewRangeSelectionModifier to control the visible range across all subcharts.
Dynamic SubChart Management: Subcharts can be added, removed, or fully recreated at runtime without triggering interaction issues, using safe lifecycle management with suspendUpdates().
Synchronized Zooming and Panning: Mouse wheel zoom, drag panning, and programmatic zooming are automatically synchronized across all subcharts via a shared X-axis range.
Interactive Overview Panel: The overview aggregates all visible data and provides a draggable selection window that controls the visible range of every subchart simultaneously.
High-Performance Rendering: All charts leverage SciChart.js WebAssembly rendering, ensuring smooth performance even with multiple charts and large datasets.
This example demonstrates best practices for managing multiple subcharts using SciChartSubSurface, including safe cleanup, axis synchronization, and modifier coordination. Developers can extend this pattern to build advanced dashboards, signal analysis tools, or monitoring applications. For more information, see the SubSurfaces API and Performance Tips & Tricks.

Demonstrates Multiple X & Y Axis on a JavaScript Chart using SciChart.js. SciChart supports unlimited left, right, top, bottom X, Y axis with configurable alignment and individual zooming, panning

Demonstrates Secondary Y Axis on a JavaScript Chart using SciChart.js. SciChart supports unlimited, multiple left, right, top, bottom X, Y axis with configurable alignment and individual zooming, panning

Demonstrates how to Zoom, Scale or Pan individual Axis on a JavaScript Chart with SciChart.js AxisDragModifiers

Demonstrates how to zoom and pan a realtime JavaScript Chart while it is updating, with SciChart.js ZoomState API

Demonstrates how to use multiple Zoom and Pan Modifiers on a JavaScript Chart with SciChart.js

Demonstrates how to zoom and pan with an Overview Chart

shows how to load data on zoom/pan and how to create an overview chart for this case.

Explore SciChart's Polar Interactivity Modifiers including zooming, panning, and cursor tracking. Try the demo to trial the Polar Chart Behavior Modifiers.

Demonstrates 64-bit precision Date Axis in SciChart.js handling Nanoseconds to Billions of Years