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 RolloverModifier,
17 EXyDirection,
18} from "scichart";
19
20import { appTheme } from "../../../theme";
21import { SubChartsOverviewModifier } from "./SubChartsOverviewModifier";
22import { AxisSynchroniser } from "../../MultiChart/SyncMultiChart/AxisSynchroniser";
23
24const colorsArr = [
25 appTheme.MutedBlue,
26 appTheme.MutedOrange,
27 appTheme.MutedPink,
28 appTheme.MutedPurple,
29 appTheme.MutedRed,
30 appTheme.MutedSkyBlue,
31 appTheme.MutedTeal,
32];
33
34export type TMarkType = EPerformanceMarkType | string;
35
36export interface SubChartConfig {
37 id: string;
38 phase: number;
39 color: string;
40 title: string;
41}
42
43export interface SubChartManager {
44 updateSubCharts: (configs: SubChartConfig[]) => void;
45 addSubChart: (config: SubChartConfig) => void;
46 removeSubChart: (id: string) => void;
47 updateLayout: () => void;
48}
49
50// Helper to create some sample data
51const createLineData = (phase: number) => {
52 const xValues: number[] = [];
53 const yValues: number[] = [];
54 for (let i = 0; i < 500; i++) {
55 const x = i;
56 const y = Math.sin(i * 0.1 + phase);
57 xValues.push(x);
58 yValues.push(y);
59 }
60 return { xValues, yValues };
61};
62
63export const drawExample = async (
64 rootElement: string | HTMLDivElement,
65 initialConfigs: SubChartConfig[]
66): Promise<{ wasmContext: any; sciChartSurface: SciChartSurface; manager: SubChartManager }> => {
67 // Create a SciChartSurface
68 const { wasmContext, sciChartSurface } = await SciChartSurface.create(rootElement);
69
70 // Store references to subcharts for dynamic management
71 const subChartMap = new Map<string, SciChartSubSurface>();
72 const axisSynchroniser = new AxisSynchroniser(new NumberRange(0, 500));
73
74 // Add main axes to the surface for the overview to reference
75 const mainXAxis = new NumericAxis(wasmContext, {
76 id: "mainXAxis",
77 isVisible: false, // Hidden since subcharts have their own axes
78 autoRange: EAutoRange.Always,
79 });
80 const mainYAxis = new NumericAxis(wasmContext, {
81 id: "mainYAxis",
82 isVisible: false, // Hidden since subcharts have their own axes
83 autoRange: EAutoRange.Always,
84 });
85
86 sciChartSurface.xAxes.add(mainXAxis);
87 sciChartSurface.yAxes.add(mainYAxis);
88
89 // Create overview modifier
90 const overviewModifier = new SubChartsOverviewModifier({
91 overviewPosition: new Rect(0, 0.8, 1, 0.2),
92 isTransparent: true,
93 axisTitle: "Overview - All Charts",
94 labelStyle: {
95 color: "#ffffff80",
96 fontSize: 10,
97 },
98 majorTickLineStyle: {
99 color: "#ffffff80",
100 tickSize: 6,
101 strokeThickness: 1,
102 },
103 yAxisGrowBy: new NumberRange(0.1, 0.1),
104 });
105
106 sciChartSurface.chartModifiers.add(overviewModifier);
107
108 const updateLayout = () => {
109 // Simple layout update without recreating subcharts
110 // This is called after add/remove operations to ensure proper spacing
111 // The actual repositioning will be handled by the individual add/remove functions
112 };
113
114 const addSubChart = (config: SubChartConfig, index?: number) => {
115 // Don't add if already exists
116 if (subChartMap.has(config.id)) {
117 return;
118 }
119
120 // For now, just add at the bottom to avoid repositioning existing charts
121 const currentCount = subChartMap.size;
122 const chartIndex = currentCount; // Always add at the end
123
124 // Calculate position: this is a simple approach that may cause overlapping
125 // but avoids the MouseManager issues
126 const rect = new Rect(0, chartIndex * 0.2, 1, 0.2);
127
128 const subChartOptions: I2DSubSurfaceOptions = {
129 id: config.id,
130 position: rect,
131 coordinateMode: ESubSurfacePositionCoordinateMode.Relative,
132 };
133
134 const subChart = SciChartSubSurface.createSubSurface(sciChartSurface, subChartOptions);
135
136 // Create axes for the subchart
137 const subXAxis = new NumericAxis(wasmContext);
138 const subYAxis = new NumericAxis(wasmContext, {
139 growBy: new NumberRange(0.1, 0.1),
140 axisTitle: config.title,
141 axisTitleStyle: { fontSize: 14 },
142 drawMinorGridLines: false,
143 });
144
145 subChart.xAxes.add(subXAxis);
146 subChart.yAxes.add(subYAxis);
147
148 // Create data and series
149 const data = createLineData(config.phase);
150 const dataSeries = new XyDataSeries(wasmContext, {
151 xValues: data.xValues,
152 yValues: data.yValues,
153 });
154
155 const lineSeries = new FastLineRenderableSeries(wasmContext, {
156 dataSeries,
157 strokeThickness: 4,
158 stroke: config.color,
159 opacity: 0.6,
160 });
161
162 // Add to synchronizer and subchart
163 axisSynchroniser.addAxis(subXAxis);
164 subChart.renderableSeries.add(lineSeries);
165
166 // Add modifiers
167 subChart.chartModifiers.add(
168 new ZoomPanModifier(),
169 new MouseWheelZoomModifier({ xyDirection: EXyDirection.XDirection }),
170 new ZoomExtentsModifier(),
171 new RolloverModifier({ modifierGroup: "one" })
172 );
173
174 // Store reference
175 subChartMap.set(config.id, subChart);
176
177 subChart.zoomExtents();
178 };
179
180 const removeSubChart = (id: string) => {
181 const subChart = subChartMap.get(id);
182 if (subChart) {
183 // Remove from synchronizer
184 const xAxis = subChart.xAxes.get(0);
185 if (xAxis) {
186 axisSynchroniser.removeAxis(xAxis);
187 }
188
189 // Use removeSubChart on the parent surface to properly remove from subChartsProperty array
190 // This prevents MouseManager from iterating over deleted subcharts
191 sciChartSurface.removeSubChart(subChart);
192 subChartMap.delete(id);
193 }
194 };
195
196 const updateSubChart = (id: string, config: SubChartConfig) => {
197 const subChart = subChartMap.get(id);
198 if (subChart) {
199 // Update title
200 const yAxis = subChart.yAxes.get(0);
201 if (yAxis) {
202 yAxis.axisTitle = config.title;
203 }
204
205 // Update color
206 const series = subChart.renderableSeries.get(0) as FastLineRenderableSeries;
207 if (series) {
208 series.stroke = config.color;
209 }
210
211 // Update data if phase changed
212 const data = createLineData(config.phase);
213 if (series && series.dataSeries) {
214 (series.dataSeries as XyDataSeries).clear();
215 (series.dataSeries as XyDataSeries).appendRange(data.xValues, data.yValues);
216 }
217 }
218 };
219
220 const updateSubCharts = (configs: SubChartConfig[]) => {
221 // Use the safer recreate approach to avoid MouseManager issues
222 recreateSubChartsWithLayout(configs);
223 };
224
225 const recreateSubChartsWithLayout = (configs: SubChartConfig[]) => {
226 // Suspend updates to prevent MouseManager from iterating over partially deleted subcharts
227 sciChartSurface.suspendUpdates();
228
229 try {
230 // Clear existing subcharts - use the proper removeSubChart function to ensure cleanup
231 const currentIds = Array.from(subChartMap.keys());
232 currentIds.forEach((id) => {
233 removeSubChart(id);
234 });
235
236 // Recreate with proper layout
237 const count = configs.length;
238 if (count === 0) {
239 sciChartSurface.resumeUpdates();
240 return;
241 }
242
243 configs.forEach((config, index) => {
244 // Calculate position: each subchart takes 1/count of the available 80% height
245 // Y position starts at (index/count) * 0.8 and has height of (1/count) * 0.8
246 const yStart = (index / count) * 0.8;
247 const height = (1 / count) * 0.8;
248 const rect = new Rect(0, yStart, 1, height);
249
250 const subChartOptions: I2DSubSurfaceOptions = {
251 id: config.id,
252 position: rect,
253 coordinateMode: ESubSurfacePositionCoordinateMode.Relative,
254 };
255
256 const subChart = SciChartSubSurface.createSubSurface(sciChartSurface, subChartOptions);
257
258 // Create axes for the subchart
259 const subXAxis = new NumericAxis(wasmContext);
260 const subYAxis = new NumericAxis(wasmContext, {
261 growBy: new NumberRange(0.1, 0.1),
262 axisTitle: config.title,
263 axisTitleStyle: { fontSize: 14 },
264 drawMinorGridLines: false,
265 });
266
267 subChart.xAxes.add(subXAxis);
268 subChart.yAxes.add(subYAxis);
269
270 // Create data and series
271 const data = createLineData(config.phase);
272 const dataSeries = new XyDataSeries(wasmContext, {
273 xValues: data.xValues,
274 yValues: data.yValues,
275 });
276
277 const lineSeries = new FastLineRenderableSeries(wasmContext, {
278 dataSeries,
279 strokeThickness: 4,
280 stroke: config.color,
281 opacity: 0.6,
282 });
283
284 // Add to synchronizer and subchart
285 axisSynchroniser.addAxis(subXAxis);
286 subChart.renderableSeries.add(lineSeries);
287
288 // Add modifiers
289 subChart.chartModifiers.add(
290 new ZoomPanModifier(),
291 new MouseWheelZoomModifier({ xyDirection: EXyDirection.XDirection }),
292 new ZoomExtentsModifier(),
293 new RolloverModifier({ modifierGroup: "one" })
294 );
295
296 // Store reference
297 subChartMap.set(config.id, subChart);
298
299 subChart.zoomExtents();
300 });
301 } finally {
302 sciChartSurface.resumeUpdates();
303 }
304 };
305
306 // Initialize with provided configs
307 initialConfigs.forEach((config) => addSubChart(config));
308
309 sciChartSurface.zoomExtents();
310
311 const manager: SubChartManager = {
312 updateSubCharts,
313 addSubChart,
314 removeSubChart,
315 updateLayout,
316 };
317
318 return { wasmContext, sciChartSurface, manager };
319};
320This 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