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="${
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};
310This 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.



Interactive Angular 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.

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

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

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

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


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