JavaScript Overview for SubCharts with Range Selection

Demonstrates how to create multiple synchronized subcharts with an interactive overview using SciChart.js

4

Fullscreen

Edit

 Edit

Docs

drawExample.ts

index.html

AxisSynchroniser.ts

vanilla.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    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};
310

Overview for SubCharts – JavaScript

Overview

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

Technical Implementation

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.

Features and Capabilities

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.

Integration and Best Practices

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.

javascript Chart Examples & Demos

See Also: Charts added in v5 (9 Demos)

NEW!
JavaScript Trading Drawing Tools | Javascript Charts | SciChart.js

JavaScript Trading Drawing Tools

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.

NEW!
JavaScript Freehand Drawing Tools | SciChart.js Demo

JavaScript Freehand Drawing Tools

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

NEW!
JavaScript Chart with Smith Chart | SciChart.js Demo

JavaScript Chart with Smith Chart

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.

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!
JavaScript Force Directed Graph | Javascript Charts | SciChart.js

JavaScript Force Directed Graph

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

NEW!
JavaScript Orderbook Heatmap | Javascript Charts | SciChart.js

JavaScript Orderbook Heatmap

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

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

High Precision Date Axis

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

NEW!
JavaScript Chart with DiscontinuousDateAxis Comparison

DiscontinuousDateAxis Comparison with Javascript

NEW!
JavaScript Chart with BaseValue Axes | SciChart.js Demo

JavaScript Chart with BaseValue Axes

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

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