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

Demonstrates Multiple X & Y Axis on a React 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 React 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 React Chart with SciChart.js AxisDragModifiers

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

Demonstrates how to use multiple Zoom and Pan Modifiers on a React 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