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="${
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 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