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

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: Zoom and Pan a Chart (9 Demos)

JavaScript Chart with Multiple X Axes | SciChart.js Demo

JavaScript Chart with Multiple X Axes

Demonstrates Multiple X & Y Axis on a JavaScript Chart using SciChart.js. SciChart supports unlimited left, right, top, bottom X, Y axis with configurable alignment and individual zooming, panning

JavaScript Chart with Secondary Y Axes | SciChart.js Demo

JavaScript Chart with Secondary Y Axes

Demonstrates Secondary Y Axis on a JavaScript Chart using SciChart.js. SciChart supports unlimited, multiple left, right, top, bottom X, Y axis with configurable alignment and individual zooming, panning

Drag JavaScript Chart Axis to Scale or Pan | SciChart.js

Drag JavaScript Chart Axis to Scale or Pan

Demonstrates how to Zoom, Scale or Pan individual Axis on a JavaScript Chart with SciChart.js AxisDragModifiers

Zoom and Pan a Realtime JavaScript Chart | SciChart.js

Zoom and Pan a Realtime JavaScript Chart

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

Zoom and Pan with JavaScript Chart multiple Modifiers

Zoom and Pan with JavaScript Chart multiple Modifiers

Demonstrates how to use multiple Zoom and Pan Modifiers on a JavaScript Chart with SciChart.js

Zoom and Pan with Overview Chart | Javascript Charts | SciChart.js

Zoom and Pan with Overview Chart

Demonstrates how to zoom and pan with an Overview Chart

Virtualized JavaScript Charts: Load Data on Zoom/Pan

Virtualized JavaScript Charts: Load Data on Zoom/Pan

shows how to load data on zoom/pan and how to create an overview chart for this case.

JavaScript Polar Modifiers | Polar Interactivity Modifiers

JavaScript Polar Modifiers | Polar Interactivity Modifiers Demo

Explore SciChart's Polar Interactivity Modifiers including zooming, panning, and cursor tracking. Try the demo to trial the Polar Chart Behavior Modifiers.

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

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