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="${
90 legendHeight - 2
91 }" rx="4" ry="4" fill="#0D1523CC" stroke="#E8C667AA" stroke-width="1"/>
92 ${rows}
93 </svg>`;
94};
95
96export const drawExample = async (
97 rootElement: string | HTMLDivElement,
98 initialConfigs: SubChartConfig[]
99): Promise<{ wasmContext: any; sciChartSurface: SciChartSurface; manager: SubChartManager }> => {
100 const { wasmContext, sciChartSurface } = await SciChartSurface.create(rootElement);
101
102 const subChartMap = new Map<string, SciChartSubSurface>();
103 const subChartRuntimeMap = new Map<string, SubChartRuntime>();
104
105 let nextStreamX = Math.floor(Date.now() / 1000) + 1;
106 const initialVisibleRange = new NumberRange(nextStreamX - POINTS_ON_CHART, nextStreamX - 1);
107 const axisSynchroniser = new AxisSynchroniser(initialVisibleRange);
108
109 const mainXAxis = new NumericAxis(wasmContext, {
110 id: "mainXAxis",
111 isVisible: false,
112 autoRange: EAutoRange.Always,
113 });
114 const mainYAxis = new NumericAxis(wasmContext, {
115 id: "mainYAxis",
116 isVisible: false,
117 autoRange: EAutoRange.Always,
118 });
119
120 sciChartSurface.xAxes.add(mainXAxis);
121 sciChartSurface.yAxes.add(mainYAxis);
122
123 const overviewModifier = new SubChartsOverviewModifier({
124 overviewPosition: new Rect(0, SUBCHARTS_HEIGHT, 1, OVERVIEW_HEIGHT),
125 isTransparent: true,
126 axisTitle: "Overview - All Charts",
127 labelStyle: {
128 color: "#ffffff80",
129 fontSize: 10,
130 },
131 majorTickLineStyle: {
132 color: "#ffffff80",
133 tickSize: 6,
134 strokeThickness: 1,
135 },
136 yAxisGrowBy: new NumberRange(0.1, 0.1),
137 });
138
139 sciChartSurface.chartModifiers.add(overviewModifier);
140
141 const getCurrentConfigs = (): SubChartConfig[] =>
142 Array.from(subChartRuntimeMap.entries()).map(([id, runtime]) => ({
143 id,
144 phase: runtime.phase,
145 color: runtime.color,
146 title: runtime.title,
147 }));
148
149 const createCursorModifier = () =>
150 new CursorModifier({
151 modifierGroup: "subcharts-cursor",
152 showAxisLabels: true,
153 showTooltip: true,
154 isSvgOnly: false,
155 crosshairStroke: appTheme.ForegroundColor,
156 crosshairStrokeThickness: 1,
157 axisLabelFill: appTheme.ForegroundColor,
158 axisLabelStroke: "#0D1523",
159 tooltipContainerBackground: "#0D1523",
160 tooltipTextStroke: appTheme.ForegroundColor,
161 tooltipLegendTemplate: getTooltipLegendTemplate,
162 });
163
164 const createSubChart = (config: SubChartConfig, rect: Rect) => {
165 const subChartOptions: I2DSubSurfaceOptions = {
166 id: config.id,
167 position: rect,
168 coordinateMode: ESubSurfacePositionCoordinateMode.Relative,
169 };
170
171 const subChart = SciChartSubSurface.createSubSurface(sciChartSurface, subChartOptions);
172
173 const subXAxis = new NumericAxis(wasmContext, {
174 autoRange: EAutoRange.Always,
175 labelFormat: ENumericFormat.Date_HHMMSS,
176 cursorLabelFormat: ENumericFormat.Date_HHMMSS,
177 drawMinorGridLines: false,
178 maxAutoTicks: 6,
179 });
180
181 const subYAxis = new NumericAxis(wasmContext, {
182 autoRange: EAutoRange.Always,
183 growBy: new NumberRange(0.1, 0.1),
184 axisTitle: config.title,
185 axisTitleStyle: { fontSize: 14 },
186 drawMinorGridLines: false,
187 });
188
189 subChart.xAxes.add(subXAxis);
190 subChart.yAxes.add(subYAxis);
191
192 const data = createLineData(config.phase, nextStreamX - POINTS_ON_CHART, POINTS_ON_CHART);
193 const dataSeries = new XyDataSeries(wasmContext, {
194 xValues: data.xValues,
195 yValues: data.yValues,
196 dataSeriesName: config.title,
197 });
198
199 const lineSeries = new FastLineRenderableSeries(wasmContext, {
200 dataSeries,
201 strokeThickness: 3,
202 stroke: config.color,
203 opacity: 0.7,
204 });
205
206 axisSynchroniser.addAxis(subXAxis);
207 subChart.renderableSeries.add(lineSeries);
208
209 subChart.chartModifiers.add(
210 new ZoomPanModifier(),
211 new MouseWheelZoomModifier({ xyDirection: EXyDirection.XDirection }),
212 new ZoomExtentsModifier(),
213 createCursorModifier()
214 );
215
216 subChartMap.set(config.id, subChart);
217 subChartRuntimeMap.set(config.id, {
218 subChart,
219 dataSeries,
220 phase: config.phase,
221 color: config.color,
222 title: config.title,
223 });
224
225 subChart.zoomExtents();
226 };
227
228 const clearAllSubCharts = () => {
229 const currentSubCharts = Array.from(subChartMap.values());
230 currentSubCharts.forEach((subChart) => {
231 const xAxis = subChart.xAxes.get(0);
232 if (xAxis) {
233 axisSynchroniser.removeAxis(xAxis);
234 }
235 sciChartSurface.removeSubChart(subChart);
236 });
237 subChartMap.clear();
238 subChartRuntimeMap.clear();
239 };
240
241 const recreateSubChartsWithLayout = (configs: SubChartConfig[]) => {
242 sciChartSurface.suspendUpdates();
243 try {
244 clearAllSubCharts();
245
246 if (configs.length === 0) {
247 return;
248 }
249
250 configs.forEach((config, index) => {
251 const yStart = (index / configs.length) * SUBCHARTS_HEIGHT;
252 const height = (1 / configs.length) * SUBCHARTS_HEIGHT;
253 createSubChart(config, new Rect(0, yStart, 1, height));
254 });
255 } finally {
256 sciChartSurface.resumeUpdates();
257 }
258 };
259
260 const updateSubCharts = (configs: SubChartConfig[]) => {
261 recreateSubChartsWithLayout(configs);
262 };
263
264 const addSubChart = (config: SubChartConfig) => {
265 if (subChartMap.has(config.id)) {
266 return;
267 }
268 recreateSubChartsWithLayout([...getCurrentConfigs(), config]);
269 };
270
271 const removeSubChart = (id: string) => {
272 if (!subChartMap.has(id)) {
273 return;
274 }
275 recreateSubChartsWithLayout(getCurrentConfigs().filter((cfg) => cfg.id !== id));
276 };
277
278 const updateLayout = () => {
279 recreateSubChartsWithLayout(getCurrentConfigs());
280 };
281
282 recreateSubChartsWithLayout(initialConfigs);
283 sciChartSurface.zoomExtents();
284
285 const streamInterval = window.setInterval(() => {
286 subChartRuntimeMap.forEach((runtime) => {
287 runtime.dataSeries.append(nextStreamX, getYValue(nextStreamX, runtime.phase));
288 const excess = runtime.dataSeries.count() - POINTS_ON_CHART;
289 if (excess > 0) {
290 runtime.dataSeries.removeRange(0, excess);
291 }
292 });
293
294 nextStreamX += 1;
295 }, STREAM_INTERVAL_MS);
296
297 sciChartSurface.addDeletable({
298 delete: () => window.clearInterval(streamInterval),
299 });
300
301 const manager: SubChartManager = {
302 updateSubCharts,
303 addSubChart,
304 removeSubChart,
305 updateLayout,
306 };
307
308 return { wasmContext, sciChartSurface, manager };
309};
310This 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.

Create an interactive JavaScript trading charts for technical analysis. Trading Drawing Tools Demo, which shows how to use Polylines, Extended Lines, Rays, Channels, Pitchforks, Pitchfans, Fibonnaci Retracements, Measure, Stop Loss and Take Profit chart drawing tools for Technical Analysis.

An example of using JavaScript FreehandDrawingModifier for arbitrary drawing on trading and financial charts. Can be used for drawing trends, arrow, markers, text, etc.

Interactive JavaScript Smith chart for RF impedance matching — place markers, build matching networks step by step with the component chain, and switch between impedance and admittance grids.

Demonstrates how to use the SVG render layer in SciChart.js to maintain smooth cursor interaction on heavy charts with millions of points.

JavaScript Force Directed Graph demo by SciChart.js. Visualize network graphs with physics simulation, interactive node dragging, and hover tooltips.

Create a Javascript heatmap chart showing historical orderbook levels using the high performance SciChart.js chart library. Get free demo now.

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


Demonstrates BaseValue Axes on a JavaScript Chart using SciChart.js to create non-linear and custom-scaled axes such as log-like scales