React Overview for SubCharts with Range Selection

Demonstrates how to create multiple synchronized subcharts with an interactive overview in a React application using SciChart.js

4

Fullscreen

Edit

 Edit

Docs

drawExample.ts

index.tsx

AxisSynchroniser.ts

theme.ts

SubChartsOverviewModifier.ts

Copy to clipboard
Minimise
Fullscreen
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};
296

Overview for SubCharts – React

Overview

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

Technical Implementation

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.

Features and Capabilities

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.

Integration and Best Practices

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.

react Chart Examples & Demos

See Also: Charts added in v5 (9 Demos)

NEW!
React Trading Drawing Tools | React Charts | SciChart.js

React Trading Drawing Tools

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.

NEW!
React Freehand Drawing Tools | React Charts | SciChart.js

React Freehand Drawing Tools

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

NEW!
React  Smith Chart | React Charts | SciChart.js Demo

React Smith Chart

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.

NEW!
High Performance SVG Cursor & Rollover | SciChart.js Demo

High Performance SVG Cursor & Rollover

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

NEW!
React Force Directed Graph | React Charts | SciChart.js

React Force Directed Graph

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

NEW!
React Orderbook Heatmap | React Charts | SciChart.js Demo

Order Book Heatmap

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

NEW!
High Precision Date Axis | React Charts | SciChart.js Demo

High Precision Date Axis

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

NEW!
React Chart with DiscontinuousDateAxis Comparison | SciChart

DiscontinuousDateAxis Comparison with React

NEW!
React Chart with BaseValue Axes | React Charts | SciChart.js

React Chart with BaseValue Axes

Demonstrates BaseValue Axes on a React Chart using SciChart.js to create non-linear and custom-scaled axes

SciChart Ltd, 16 Beaufort Court, Admirals Way, Docklands, London, E14 9XL.