Angular Overview for SubCharts with Range Selection

Demonstrates how to create multiple synchronized subcharts with an interactive overview in an Angular 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    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 – Angular

Overview

This example demonstrates how to create a multi-panel chart layout with synchronized subcharts and an overview range selector using SciChart.js within an Angular application. Each subchart displays its own data while remaining fully synchronized through shared zoom and pan interactions.

Technical Implementation

The chart is hosted inside a standalone Angular component using the scichart-angular integration. A custom initialization function dynamically creates multiple SciChartSubSurface instances, each with its own NumericAxis, FastLineRenderableSeries, and interaction modifiers such as ZoomPanModifier and MouseWheelZoomModifier.

An overview panel is added using a custom SubChartsOverviewModifier, which creates an additional subsurface at the bottom of the chart. This overview aggregates series from all subcharts and applies an OverviewRangeSelectionModifier to synchronize the visible range across every chart.

Features and Capabilities

Dynamic Multi-Chart Layout: Subcharts are automatically positioned and resized based on the number of active charts.

Unified Range Selection: The overview panel provides a single control point for zooming and panning all subcharts simultaneously.

Robust Lifecycle Handling: The example carefully manages subchart creation and deletion to avoid interaction issues, ensuring stable behavior during dynamic updates.

Enterprise-Grade Performance: Leveraging SciChart.js WebAssembly rendering ensures smooth interactivity even with multiple charts and dense datasets.

Integration and Best Practices

This example follows recommended patterns for integrating SciChart.js into Angular, including isolating chart initialization logic and using safe update suspension during layout changes. Developers building analytical dashboards or monitoring tools can use this approach as a foundation. See the SciChart Angular Documentation and SubCharts API for further details.

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