Demonstrates how to create multiple synchronized subcharts with an interactive overview in a React application using SciChart.js
drawExample.ts
index.tsx
AxisSynchroniser.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 ENumericFormat,
18} from "scichart";
19import { SubChartsOverviewModifier } from "./SubChartsOverviewModifier";
20import { AxisSynchroniser } from "../../MultiChart/SyncMultiChart/AxisSynchroniser";
21
22const STREAM_INTERVAL_MS = 1000;
23const POINTS_ON_CHART = 180;
24const OVERVIEW_HEIGHT = 0.2;
25const SUBCHARTS_HEIGHT = 1 - OVERVIEW_HEIGHT;
26
27export type TMarkType = EPerformanceMarkType | string;
28
29export interface SubChartConfig {
30 id: string;
31 phase: number;
32 color: string;
33 title: string;
34}
35
36export interface SubChartManager {
37 updateSubCharts: (configs: SubChartConfig[]) => void;
38 addSubChart: (config: SubChartConfig) => void;
39 removeSubChart: (id: string) => void;
40 updateLayout: () => void;
41}
42
43interface SubChartRuntime {
44 subChart: SciChartSubSurface;
45 dataSeries: XyDataSeries;
46 phase: number;
47 color: string;
48 title: string;
49}
50
51const getYValue = (x: number, phase: number): number =>
52 Math.sin(x * 0.08 + phase) * 0.75 + Math.cos(x * 0.015 + phase * 0.5) * 0.25;
53
54const createLineData = (phase: number, startX: number, count: number) => {
55 const xValues: number[] = [];
56 const yValues: number[] = [];
57 for (let i = 0; i < count; i++) {
58 const x = startX + i;
59 xValues.push(x);
60 yValues.push(getYValue(x, phase));
61 }
62 return { xValues, yValues };
63};
64
65export const drawExample = async (
66 rootElement: string | HTMLDivElement,
67 initialConfigs: SubChartConfig[]
68): Promise<{ wasmContext: any; sciChartSurface: SciChartSurface; manager: SubChartManager }> => {
69 const { wasmContext, sciChartSurface } = await SciChartSurface.create(rootElement);
70
71 const subChartMap = new Map<string, SciChartSubSurface>();
72 const subChartRuntimeMap = new Map<string, SubChartRuntime>();
73
74 let nextStreamX = Math.floor(Date.now() / 1000) + 1;
75 const initialVisibleRange = new NumberRange(nextStreamX - POINTS_ON_CHART, nextStreamX - 1);
76 const axisSynchroniser = new AxisSynchroniser(initialVisibleRange);
77
78 const mainXAxis = new NumericAxis(wasmContext, {
79 id: "mainXAxis",
80 isVisible: false,
81 autoRange: EAutoRange.Always,
82 });
83 const mainYAxis = new NumericAxis(wasmContext, {
84 id: "mainYAxis",
85 isVisible: false,
86 autoRange: EAutoRange.Always,
87 });
88
89 sciChartSurface.xAxes.add(mainXAxis);
90 sciChartSurface.yAxes.add(mainYAxis);
91
92 const overviewModifier = new SubChartsOverviewModifier({
93 overviewPosition: new Rect(0, SUBCHARTS_HEIGHT, 1, OVERVIEW_HEIGHT),
94 isTransparent: true,
95 axisTitle: "Overview - All Charts",
96 labelStyle: {
97 color: "#ffffff80",
98 fontSize: 10,
99 },
100 majorTickLineStyle: {
101 color: "#ffffff80",
102 tickSize: 6,
103 strokeThickness: 1,
104 },
105 yAxisGrowBy: new NumberRange(0.1, 0.1),
106 xAxisLabelFormat: ENumericFormat.Date_HHMMSS,
107 });
108
109 sciChartSurface.chartModifiers.add(overviewModifier);
110
111 const getCurrentConfigs = (): SubChartConfig[] =>
112 Array.from(subChartRuntimeMap.entries()).map(([id, runtime]) => ({
113 id,
114 phase: runtime.phase,
115 color: runtime.color,
116 title: runtime.title,
117 }));
118
119 const createSubChart = (config: SubChartConfig, rect: Rect) => {
120 const subChartOptions: I2DSubSurfaceOptions = {
121 id: config.id,
122 position: rect,
123 coordinateMode: ESubSurfacePositionCoordinateMode.Relative,
124 };
125
126 const subChart = SciChartSubSurface.createSubSurface(sciChartSurface, subChartOptions);
127
128 // X axis uses Never autorange so user pan/zoom is not overridden by streaming data.
129 // The streaming loop advances visibleRange manually while the user is at the live edge.
130 const subXAxis = new NumericAxis(wasmContext, {
131 autoRange: EAutoRange.Never,
132 visibleRange: axisSynchroniser.visibleRange,
133 labelFormat: ENumericFormat.Date_HHMMSS,
134 cursorLabelFormat: ENumericFormat.Date_HHMMSS,
135 drawMinorGridLines: false,
136 maxAutoTicks: 6,
137 });
138
139 const subYAxis = new NumericAxis(wasmContext, {
140 autoRange: EAutoRange.Always,
141 growBy: new NumberRange(0.1, 0.1),
142 axisTitle: config.title,
143 axisTitleStyle: { fontSize: 14 },
144 drawMinorGridLines: false,
145 });
146
147 subChart.xAxes.add(subXAxis);
148 subChart.yAxes.add(subYAxis);
149
150 const data = createLineData(config.phase, nextStreamX - POINTS_ON_CHART, POINTS_ON_CHART);
151 const dataSeries = new XyDataSeries(wasmContext, {
152 xValues: data.xValues,
153 yValues: data.yValues,
154 dataSeriesName: config.title,
155 });
156
157 const lineSeries = new FastLineRenderableSeries(wasmContext, {
158 dataSeries,
159 strokeThickness: 3,
160 stroke: config.color,
161 opacity: 0.7,
162 });
163
164 axisSynchroniser.addAxis(subXAxis);
165 subChart.renderableSeries.add(lineSeries);
166
167 subChart.chartModifiers.add(
168 new ZoomPanModifier(),
169 new MouseWheelZoomModifier({ xyDirection: EXyDirection.XDirection }),
170 new ZoomExtentsModifier()
171 );
172
173 subChartMap.set(config.id, subChart);
174 subChartRuntimeMap.set(config.id, {
175 subChart,
176 dataSeries,
177 phase: config.phase,
178 color: config.color,
179 title: config.title,
180 });
181 };
182
183 const clearAllSubCharts = () => {
184 const currentSubCharts = Array.from(subChartMap.values());
185 currentSubCharts.forEach((subChart) => {
186 const xAxis = subChart.xAxes.get(0);
187 if (xAxis) {
188 axisSynchroniser.removeAxis(xAxis);
189 }
190 // SciChartSurface.removeSubChart does not fire onDetachSubSurface on modifiers, so
191 // proactively clear the renderable series first. That triggers collectionChanged on
192 // the subchart's series collection, which the overview modifier listens to and uses
193 // to disconnect its shared dataSeries before the subchart deletion deletes them.
194 const seriesToRemove = subChart.renderableSeries.asArray().slice();
195 seriesToRemove.forEach((series) => {
196 subChart.renderableSeries.remove(series, false);
197 });
198 sciChartSurface.removeSubChart(subChart);
199 });
200 subChartMap.clear();
201 subChartRuntimeMap.clear();
202 };
203
204 const recreateSubChartsWithLayout = (configs: SubChartConfig[]) => {
205 sciChartSurface.suspendUpdates();
206 try {
207 clearAllSubCharts();
208
209 if (configs.length === 0) {
210 return;
211 }
212
213 configs.forEach((config, index) => {
214 const yStart = (index / configs.length) * SUBCHARTS_HEIGHT;
215 const height = (1 / configs.length) * SUBCHARTS_HEIGHT;
216 createSubChart(config, new Rect(0, yStart, 1, height));
217 });
218 } finally {
219 sciChartSurface.resumeUpdates();
220 }
221 };
222
223 const updateSubCharts = (configs: SubChartConfig[]) => {
224 recreateSubChartsWithLayout(configs);
225 };
226
227 const addSubChart = (config: SubChartConfig) => {
228 if (subChartMap.has(config.id)) {
229 return;
230 }
231 recreateSubChartsWithLayout([...getCurrentConfigs(), config]);
232 };
233
234 const removeSubChart = (id: string) => {
235 if (!subChartMap.has(id)) {
236 return;
237 }
238 recreateSubChartsWithLayout(getCurrentConfigs().filter((cfg) => cfg.id !== id));
239 };
240
241 const updateLayout = () => {
242 recreateSubChartsWithLayout(getCurrentConfigs());
243 };
244
245 recreateSubChartsWithLayout(initialConfigs);
246 sciChartSurface.zoomExtents();
247
248 const streamInterval = window.setInterval(() => {
249 // Snapshot whether the user was at the live edge before this tick — if so, we advance
250 // the visible range together with the new data so the chart auto-scrolls. If the user
251 // has zoomed/panned away from the live edge, we leave the visible range alone.
252 const previousLastX = nextStreamX - 1;
253 const currentRange = axisSynchroniser.visibleRange;
254 const wasFollowingLive = currentRange != null && currentRange.max >= previousLastX - 0.5;
255
256 subChartRuntimeMap.forEach((runtime) => {
257 runtime.dataSeries.append(nextStreamX, getYValue(nextStreamX, runtime.phase));
258 const excess = runtime.dataSeries.count() - POINTS_ON_CHART;
259 if (excess > 0) {
260 runtime.dataSeries.removeRange(0, excess);
261 }
262 });
263
264 if (wasFollowingLive && currentRange != null) {
265 const newRange = new NumberRange(currentRange.min + 1, currentRange.max + 1);
266 if (subChartRuntimeMap.size > 0) {
267 subChartRuntimeMap.forEach((runtime) => {
268 const xAxis = runtime.subChart.xAxes.get(0);
269 if (xAxis) {
270 xAxis.visibleRange = newRange;
271 }
272 });
273 } else {
274 // No subcharts but keep the synchroniser following live so newly added charts
275 // start at the current live edge.
276 axisSynchroniser.visibleRange = newRange;
277 }
278 }
279
280 nextStreamX += 1;
281 }, STREAM_INTERVAL_MS);
282
283 sciChartSurface.addDeletable({
284 delete: () => window.clearInterval(streamInterval),
285 });
286
287 const manager: SubChartManager = {
288 updateSubCharts,
289 addSubChart,
290 removeSubChart,
291 updateLayout,
292 };
293
294 return { wasmContext, sciChartSurface, manager };
295};
296This example demonstrates how to implement a multi-subchart layout with a shared overview in a React application using SciChart.js. Multiple vertically stacked charts are rendered within a single SciChartSurface, each synchronized on the X-axis and controlled through an interactive overview panel.
The chart is initialized via the <SciChartReact /> component, which invokes a custom drawExample function. This function dynamically creates several SciChartSubSurface instances, each configured with its own axes, renderable series, and interaction modifiers. An AxisSynchroniser ensures all subcharts maintain the same visible X-range.
The overview functionality is encapsulated in a reusable SubChartsOverviewModifier, which listens for subchart lifecycle events and mirrors their renderable series into an overview subsurface. The OverviewRangeSelectionModifier allows users to control zoom and pan interactions across all subcharts from a single control surface.
React-Friendly Lifecycle Management: Subcharts are safely created and destroyed without React reconciliation conflicts by leveraging SciChart’s internal update suspension mechanisms.
Centralized Zoom Control: Users can zoom and pan all subcharts simultaneously using either direct mouse interaction or the overview range selector.
Reusable Overview Modifier: The overview logic is encapsulated in a custom chart modifier, making it easy to reuse across different dashboards or chart configurations.
High-Performance Real-Time Charts: The example showcases how SciChart.js integrates seamlessly into React while maintaining WebAssembly-powered performance.
This approach follows best practices for integrating SciChart.js into React by isolating chart creation logic from React rendering. Developers can extend this pattern to support real-time streaming data, dynamic chart layouts, or advanced dashboard interactions. For more guidance, see Creating a SciChart React Component and the React Charts with SciChart.js guide.

Create an interactive React 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 React FreehandDrawingModifier for arbitrary drawing on trading and financial charts. Can be used for drawing trends, arrow, markers, text, etc.

Interactive React 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.

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

Create a React 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 React Chart using SciChart.js to create non-linear and custom-scaled axes