Demonstrates how to create multiple synchronized subcharts with an interactive overview in an Angular 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="${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};
308This 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.
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.
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.
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.

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

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

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

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

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

Demonstrates how to zoom and pan with an Overview Chart

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

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

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