Demonstrates handling realtime big data with different chart types using SciChart.js, High Performance JavaScript Charts
Number of Series 10
Initial Points 10000
Max Points On Chart 10000
Points Per Update 10
Send Data Interval 100 ms
drawExample.ts
angular.ts
theme.ts
1import { io } from "socket.io-client";
2import { appTheme } from "../../../theme";
3import {
4 IBaseDataSeriesOptions,
5 TSciChart,
6 ESeriesType,
7 BaseDataSeries,
8 BaseRenderableSeries,
9 XyDataSeries,
10 FastLineRenderableSeries,
11 AUTO_COLOR,
12 XyyDataSeries,
13 FastBandRenderableSeries,
14 FastColumnRenderableSeries,
15 StackedMountainRenderableSeries,
16 XyCustomFilter,
17 EDataSeriesField,
18 XyScatterRenderableSeries,
19 EllipsePointMarker,
20 OhlcDataSeries,
21 FastCandlestickRenderableSeries,
22 XyTextDataSeries,
23 FastTextRenderableSeries,
24 EDataSeriesType,
25 XyzDataSeries,
26 INumericAxisOptions,
27 ENumericFormat,
28 SciChartSurface,
29 NumericAxis,
30 LayoutManager,
31 IRenderableSeries,
32 StackedColumnCollection,
33 StackedColumnRenderableSeries,
34 StackedMountainCollection,
35 RightAlignedOuterVerticallyStackedAxisLayoutStrategy,
36 MouseWheelZoomModifier,
37 ZoomPanModifier,
38 ZoomExtentsModifier,
39 NumberRange,
40} from "scichart";
41
42export type TMessage = {
43 title: string;
44 detail: string;
45};
46
47export interface ISettings {
48 seriesCount?: number;
49 pointsOnChart?: number;
50 pointsPerUpdate?: number;
51 sendEvery?: number;
52 initialPoints?: number;
53}
54
55let loadCount: number = 0;
56let loadTimes: number[];
57let avgLoadTime: number = 0;
58let avgRenderTime: number = 0;
59
60function GetRandomData(xValues: number[], positive: boolean) {
61 let prevYValue = Math.random();
62 const length = xValues.length;
63 const yValues: number[] = new Array(length); // Pre-allocate array
64 // tslint:disable-next-line: prefer-for-of
65 for (let j = 0; j < length; j++) {
66 const change = Math.random() * 10 - 5;
67 prevYValue = positive ? Math.abs(prevYValue + change) : prevYValue + change;
68 yValues[j] = prevYValue;
69 }
70 return yValues;
71}
72
73const extendRandom = (val: number, max: number) => val + Math.random() * max;
74
75const generateCandleData = (xValues: number[]) => {
76 let open = 10;
77 const length = xValues.length;
78 const openValues = new Array(length);
79 const highValues = new Array(length);
80 const lowValues = new Array(length);
81 const closeValues = new Array(length);
82
83 for (let i = 0; i < length; i++) {
84 const close = open + Math.random() * 10 - 5;
85 const high = Math.max(open, close);
86 highValues[i] = extendRandom(high, 5);
87 const low = Math.min(open, close);
88 lowValues[i] = extendRandom(low, -5);
89 closeValues[i] = close;
90 openValues[i] = open;
91 open = close;
92 }
93 return { openValues, highValues, lowValues, closeValues };
94};
95
96const generateCandleDataForAppendRange = (open: number, closeValues: number[]) => {
97 const length = closeValues.length;
98 const openValues = new Array(length);
99 const highValues = new Array(length);
100 const lowValues = new Array(length);
101 for (let i = 0; i < length; i++) {
102 const close = closeValues[i];
103 openValues[i] = open;
104 const high = Math.max(open, close);
105 highValues[i] = extendRandom(high, 5);
106 const low = Math.min(open, close);
107 lowValues[i] = extendRandom(low, -5);
108 open = close;
109 }
110 return { openValues, highValues, lowValues, closeValues };
111};
112
113const dsOptions: IBaseDataSeriesOptions = {
114 isSorted: true,
115 containsNaN: false,
116};
117
118const createRenderableSeries = (
119 wasmContext: TSciChart,
120 seriesType: ESeriesType
121): { dataSeries: BaseDataSeries; rendSeries: BaseRenderableSeries } => {
122 if (seriesType === ESeriesType.LineSeries) {
123 const dataSeries: XyDataSeries = new XyDataSeries(wasmContext, dsOptions);
124 const rendSeries: FastLineRenderableSeries = new FastLineRenderableSeries(wasmContext, {
125 dataSeries,
126 stroke: AUTO_COLOR,
127 strokeThickness: 3,
128 opacity: 0.8,
129 });
130 return { dataSeries, rendSeries };
131 } else if (seriesType === ESeriesType.BandSeries) {
132 const dataSeries: XyyDataSeries = new XyyDataSeries(wasmContext, dsOptions);
133 const rendSeries: FastBandRenderableSeries = new FastBandRenderableSeries(wasmContext, {
134 stroke: AUTO_COLOR,
135 strokeY1: AUTO_COLOR,
136 fill: AUTO_COLOR,
137 fillY1: AUTO_COLOR,
138 dataSeries,
139 strokeThickness: 2,
140 opacity: 0.8,
141 });
142 return { dataSeries, rendSeries };
143 } else if (seriesType === ESeriesType.ColumnSeries) {
144 const dataSeries: XyDataSeries = new XyDataSeries(wasmContext, dsOptions);
145 const rendSeries: FastColumnRenderableSeries = new FastColumnRenderableSeries(wasmContext, {
146 fill: AUTO_COLOR,
147 stroke: AUTO_COLOR,
148 dataSeries,
149 strokeThickness: 1,
150 });
151 return { dataSeries, rendSeries };
152 } else if (seriesType === ESeriesType.StackedMountainSeries) {
153 const dataSeries: XyDataSeries = new XyDataSeries(wasmContext, dsOptions);
154 const rendSeries: StackedMountainRenderableSeries = new StackedMountainRenderableSeries(wasmContext, {
155 stroke: AUTO_COLOR,
156 fill: AUTO_COLOR,
157 dataSeries,
158 });
159 return { dataSeries, rendSeries };
160 } else if (seriesType === ESeriesType.ScatterSeries) {
161 const dataSeries: XyyDataSeries = new XyyDataSeries(wasmContext, { containsNaN: false });
162 // Use Y and Y1 as X and Y for scatter
163 const filteredSeries: XyDataSeries = new XyCustomFilter(dataSeries, {
164 xField: EDataSeriesField.Y,
165 field: EDataSeriesField.Y1,
166 });
167 const rendSeries: XyScatterRenderableSeries = new XyScatterRenderableSeries(wasmContext, {
168 pointMarker: new EllipsePointMarker(wasmContext, {
169 width: 9,
170 height: 9,
171 strokeThickness: 2,
172 fill: AUTO_COLOR,
173 stroke: AUTO_COLOR,
174 opacity: 0.8,
175 }),
176 dataSeries: filteredSeries,
177 });
178 // return the unfiltered xyy series as that is the one we want to append to
179 return { dataSeries, rendSeries };
180 } else if (seriesType === ESeriesType.CandlestickSeries) {
181 const dataSeries: OhlcDataSeries = new OhlcDataSeries(wasmContext, dsOptions);
182 const rendSeries: FastCandlestickRenderableSeries = new FastCandlestickRenderableSeries(wasmContext, {
183 strokeThickness: 1,
184 dataSeries,
185 dataPointWidth: 0.9,
186 opacity: 0.75,
187 strokeUp: AUTO_COLOR,
188 brushUp: AUTO_COLOR,
189 strokeDown: AUTO_COLOR,
190 brushDown: AUTO_COLOR,
191 });
192 return { dataSeries, rendSeries };
193 } else if (seriesType === ESeriesType.TextSeries) {
194 const dataSeries: XyTextDataSeries = new XyTextDataSeries(wasmContext, dsOptions);
195 const rendSeries: FastTextRenderableSeries = new FastTextRenderableSeries(wasmContext, {
196 dataSeries,
197 dataLabels: {
198 style: {
199 fontFamily: "Arial",
200 fontSize: 6,
201 },
202 color: AUTO_COLOR,
203 calculateTextBounds: false,
204 },
205 });
206 return { dataSeries, rendSeries };
207 }
208 return { dataSeries: undefined, rendSeries: undefined };
209};
210
211const prePopulateData = (
212 dataSeries: BaseDataSeries,
213 dataSeriesType: EDataSeriesType,
214 xValues: number[],
215 positive: boolean
216) => {
217 const yValues: number[] = GetRandomData(xValues, positive);
218 switch (dataSeriesType) {
219 case EDataSeriesType.Xy:
220 (dataSeries as XyDataSeries).appendRange(xValues, yValues);
221 break;
222 case EDataSeriesType.Xyy:
223 (dataSeries as XyyDataSeries).appendRange(xValues, yValues, GetRandomData(xValues, positive));
224 break;
225 case EDataSeriesType.Xyz:
226 (dataSeries as XyzDataSeries).appendRange(
227 xValues,
228 yValues,
229 GetRandomData(xValues, positive).map((z) => Math.abs(z / 5))
230 );
231 break;
232 case EDataSeriesType.Ohlc:
233 const { openValues, highValues, lowValues, closeValues } = generateCandleData(xValues);
234 (dataSeries as OhlcDataSeries).appendRange(xValues, openValues, highValues, lowValues, closeValues);
235 break;
236 case EDataSeriesType.XyText:
237 (dataSeries as XyTextDataSeries).appendRange(
238 xValues,
239 yValues,
240 yValues.map((textval) => textval.toFixed())
241 );
242 break;
243 default:
244 break;
245 }
246};
247
248const appendData = (
249 dataSeries: BaseDataSeries,
250 dataSeriesType: EDataSeriesType,
251 index: number,
252 xValues: number[],
253 yArray: number[][],
254 pointsOnChart: number,
255 pointsPerUpdate: number
256) => {
257 switch (dataSeriesType) {
258 case EDataSeriesType.Xy:
259 const xySeries = dataSeries as XyDataSeries;
260 xySeries.appendRange(xValues, yArray[index]);
261 if (xySeries.count() > pointsOnChart) {
262 xySeries.removeRange(0, pointsPerUpdate);
263 }
264 break;
265 case EDataSeriesType.Xyy:
266 const xyySeries = dataSeries as XyyDataSeries;
267 xyySeries.appendRange(xValues, yArray[2 * index], yArray[2 * index + 1]);
268 if (xyySeries.count() > pointsOnChart) {
269 xyySeries.removeRange(0, pointsPerUpdate);
270 }
271 break;
272 case EDataSeriesType.Xyz:
273 const xyzSeries = dataSeries as XyzDataSeries;
274 xyzSeries.appendRange(
275 xValues,
276 yArray[2 * index],
277 yArray[2 * index + 1].map((z) => Math.abs(z / 5))
278 );
279 if (xyzSeries.count() > pointsOnChart) {
280 xyzSeries.removeRange(0, pointsPerUpdate);
281 }
282 break;
283 case EDataSeriesType.Ohlc:
284 const ohlcSeries = dataSeries as OhlcDataSeries;
285 const { openValues, highValues, lowValues, closeValues } = generateCandleDataForAppendRange(
286 ohlcSeries.getNativeCloseValues().get(ohlcSeries.count() - 1),
287 yArray[index]
288 );
289 ohlcSeries.appendRange(xValues, openValues, highValues, lowValues, closeValues);
290 if (ohlcSeries.count() > pointsOnChart) {
291 ohlcSeries.removeRange(0, pointsPerUpdate);
292 }
293 break;
294 case EDataSeriesType.XyText:
295 const xytextSeries = dataSeries as XyTextDataSeries;
296 xytextSeries.appendRange(
297 xValues,
298 yArray[index],
299 yArray[index].map((obj) => obj.toFixed())
300 );
301 if (xytextSeries.count() > pointsOnChart) {
302 xytextSeries.removeRange(0, pointsPerUpdate);
303 }
304 break;
305 default:
306 break;
307 }
308};
309
310const axisOptions: INumericAxisOptions = {
311 drawMajorBands: false,
312 drawMinorGridLines: false,
313 drawMinorTickLines: false,
314 labelFormat: ENumericFormat.Decimal,
315 labelPrecision: 0,
316};
317
318export const drawExample =
319 (updateMessages: (newMessages: TMessage[]) => void, seriesType: ESeriesType) =>
320 async (rootElement: string | HTMLDivElement) => {
321 let seriesCount = 10;
322 let pointsOnChart = 1000;
323 let pointsPerUpdate = 100;
324 let sendEvery = 30;
325 let initialPoints: number = 0;
326
327 const { wasmContext, sciChartSurface } = await SciChartSurface.create(rootElement, {
328 theme: appTheme.SciChartJsTheme,
329 });
330 const xAxis = new NumericAxis(wasmContext, axisOptions);
331 sciChartSurface.xAxes.add(xAxis);
332 let yAxis = new NumericAxis(wasmContext, { ...axisOptions });
333 sciChartSurface.yAxes.add(yAxis);
334 let dataSeriesArray: BaseDataSeries[];
335 let dataSeriesType = EDataSeriesType.Xy;
336 if (seriesType === ESeriesType.BubbleSeries) {
337 dataSeriesType = EDataSeriesType.Xyz;
338 } else if (seriesType === ESeriesType.BandSeries || seriesType === ESeriesType.ScatterSeries) {
339 dataSeriesType = EDataSeriesType.Xyy;
340 } else if (seriesType === ESeriesType.CandlestickSeries) {
341 dataSeriesType = EDataSeriesType.Ohlc;
342 } else if (seriesType === ESeriesType.TextSeries) {
343 dataSeriesType = EDataSeriesType.XyText;
344 }
345
346 const initChart = () => {
347 sciChartSurface.renderableSeries.asArray().forEach((rs) => rs.delete());
348 sciChartSurface.renderableSeries.clear();
349 sciChartSurface.chartModifiers.asArray().forEach((cm) => cm.delete());
350 sciChartSurface.chartModifiers.clear();
351 sciChartSurface.yAxes.asArray().forEach((ya) => ya.delete());
352 sciChartSurface.yAxes.clear();
353 sciChartSurface.layoutManager = new LayoutManager();
354 yAxis = new NumericAxis(wasmContext, { ...axisOptions });
355 sciChartSurface.yAxes.add(yAxis);
356 dataSeriesArray = new Array<BaseDataSeries>(seriesCount);
357 let stackedCollection: IRenderableSeries;
358 let xValues: number[];
359 const positive = [ESeriesType.StackedColumnSeries, ESeriesType.StackedMountainSeries].includes(seriesType);
360 for (let i = 0; i < seriesCount; i++) {
361 const { dataSeries, rendSeries } = createRenderableSeries(wasmContext, seriesType);
362 dataSeriesArray[i] = dataSeries;
363 if (seriesType === ESeriesType.StackedColumnSeries) {
364 if (i === 0) {
365 stackedCollection = new StackedColumnCollection(wasmContext, { dataPointWidth: 1 });
366 sciChartSurface.renderableSeries.add(stackedCollection);
367 }
368 (rendSeries as StackedColumnRenderableSeries).stackedGroupId = i.toString();
369 (stackedCollection as StackedColumnCollection).add(rendSeries as StackedColumnRenderableSeries);
370 } else if (seriesType === ESeriesType.StackedMountainSeries) {
371 if (i === 0) {
372 stackedCollection = new StackedMountainCollection(wasmContext);
373 sciChartSurface.renderableSeries.add(stackedCollection);
374 }
375 (stackedCollection as StackedMountainCollection).add(rendSeries as StackedMountainRenderableSeries);
376 } else if (seriesType === ESeriesType.ColumnSeries) {
377 // create stacked y axis
378 if (i === 0) {
379 // tslint:disable-next-line: max-line-length
380 sciChartSurface.layoutManager.rightOuterAxesLayoutStrategy =
381 new RightAlignedOuterVerticallyStackedAxisLayoutStrategy();
382 yAxis = new NumericAxis(wasmContext, { id: "0", ...axisOptions });
383 } else {
384 sciChartSurface.yAxes.add(
385 new NumericAxis(wasmContext, {
386 id: i.toString(),
387 ...axisOptions,
388 })
389 );
390 }
391 rendSeries.yAxisId = i.toString();
392 sciChartSurface.renderableSeries.add(rendSeries);
393 } else {
394 sciChartSurface.renderableSeries.add(rendSeries);
395 }
396
397 if (i === 0) {
398 xValues = Array.from(Array(initialPoints).keys());
399 }
400 // Generate points
401 prePopulateData(dataSeries, dataSeriesType, xValues, positive);
402 sciChartSurface.zoomExtents(0);
403 }
404 return positive;
405 };
406
407 const dataBuffer: { x: number[]; ys: number[][]; sendTime: number }[] = [];
408 const maxBufferSize = 20; // Limit buffer size to prevent memory issues
409 let isRunning: boolean = false;
410 const newMessages: TMessage[] = [];
411 let loadStart = 0;
412 let loadTime = 0;
413 let renderStart = 0;
414 let renderTime = 0;
415 let droppedPackets = 0;
416
417 const loadData = (data: { x: number[]; ys: number[][]; sendTime: number }) => {
418 for (let i = 0; i < seriesCount; i++) {
419 appendData(dataSeriesArray[i], dataSeriesType, i, data.x, data.ys, pointsOnChart, pointsPerUpdate);
420 }
421 if (dataSeriesArray[0].count() < pointsOnChart) {
422 xAxis.visibleRange = new NumberRange(xAxis.visibleRange.min, xAxis.visibleRange.max + pointsPerUpdate);
423 } else {
424 xAxis.visibleRange = new NumberRange(
425 xAxis.visibleRange.min + pointsPerUpdate,
426 xAxis.visibleRange.max + pointsPerUpdate
427 );
428 }
429 loadTime = new Date().getTime() - loadStart;
430 };
431
432 sciChartSurface.preRender.subscribe(() => {
433 renderStart = new Date().getTime();
434 });
435
436 let messageUpdateCounter = 0;
437 sciChartSurface.rendered.subscribe(() => {
438 if (!isRunning || loadStart === 0) return;
439 avgLoadTime = (avgLoadTime * loadCount + loadTime) / (loadCount + 1);
440 renderTime = new Date().getTime() - renderStart;
441 avgRenderTime = (avgRenderTime * loadCount + renderTime) / (loadCount + 1);
442 loadCount++;
443
444 // Only update messages every 10 renders to reduce DOM overhead
445 messageUpdateCounter++;
446 if (messageUpdateCounter >= 10) {
447 newMessages.push({
448 title: `Avg Load Time `,
449 detail: `${avgLoadTime.toFixed(2)} ms`,
450 });
451 newMessages.push({
452 title: `Avg Render Time `,
453 detail: `${avgRenderTime.toFixed(2)} ms`,
454 });
455 newMessages.push({
456 title: `Max FPS `,
457 detail: `${Math.min(60, 1000 / (avgLoadTime + avgRenderTime)).toFixed(1)}`,
458 });
459 if (droppedPackets > 0) {
460 newMessages.push({
461 title: `Dropped Packets `,
462 detail: `${droppedPackets}`,
463 });
464 }
465 updateMessages(newMessages);
466 newMessages.length = 0;
467 messageUpdateCounter = 0;
468 }
469 });
470
471 const loadFromBuffer = () => {
472 if (dataBuffer.length > 0) {
473 loadStart = new Date().getTime();
474
475 // Process multiple buffered items more efficiently
476 const batchSize = Math.min(dataBuffer.length, 5); // Limit batch processing
477 let totalXLength = 0;
478 let totalYsLength = 0;
479
480 // Calculate total lengths to pre-allocate arrays
481 for (let i = 0; i < batchSize; i++) {
482 totalXLength += dataBuffer[i].x.length;
483 if (i === 0) totalYsLength = dataBuffer[i].ys.length;
484 }
485
486 // Pre-allocate arrays for better performance
487 const x = new Array(totalXLength);
488 const ys: number[][] = new Array(totalYsLength);
489 for (let j = 0; j < totalYsLength; j++) {
490 ys[j] = new Array(totalXLength);
491 }
492
493 let xIndex = 0;
494 let yIndex = 0;
495
496 // Copy data efficiently without spread operator
497 for (let i = 0; i < batchSize; i++) {
498 const el = dataBuffer[i];
499 const elXLength = el.x.length;
500
501 // Copy x values
502 for (let k = 0; k < elXLength; k++) {
503 x[xIndex++] = el.x[k];
504 }
505
506 // Copy y values
507 for (let y = 0; y < el.ys.length; y++) {
508 for (let k = 0; k < elXLength; k++) {
509 ys[y][yIndex + k] = el.ys[y][k];
510 }
511 }
512 yIndex += elXLength;
513 }
514
515 loadData({ x, ys, sendTime: dataBuffer[0].sendTime });
516 dataBuffer.splice(0, batchSize);
517 }
518
519 if (isRunning) {
520 requestAnimationFrame(loadFromBuffer);
521 }
522 };
523
524 let socket: ReturnType<typeof io>;
525
526 const initWebSocket = (positive: boolean) => {
527 if (socket) {
528 socket.disconnect();
529 socket.connect();
530 } else {
531 const hostedFromLocalhost = window.location.hostname.includes("localhost");
532 const hostedFromSandbox = !hostedFromLocalhost && !window.location.hostname.includes("scichart.com");
533
534 // TODO this check probably could be improved
535 // consider the app is running locally in dev mode
536 const isDevMode = hostedFromLocalhost && Number.parseInt(window.location.port) > 8000;
537
538 if (isDevMode || hostedFromSandbox) {
539 socket = io("https://www.scichart.com", { path: "/demo/socket.io" });
540 } else {
541 socket = io({ path: "/demo/socket.io" });
542 }
543 socket.on("data", (message: any) => {
544 // Throttle data to prevent buffer overflow with extreme update rates
545 if (dataBuffer.length < maxBufferSize) {
546 dataBuffer.push(message);
547 } else {
548 // Drop oldest packets when buffer is full (FIFO)
549 dataBuffer.shift();
550 dataBuffer.push(message);
551 droppedPackets++;
552 }
553 });
554 socket.on("finished", () => {
555 socket.disconnect();
556 });
557 }
558
559 // If initial data has been generated, this should be an array of the last y values of each series
560 const index = dataSeriesArray[0].count() - 1;
561 let series: number[];
562 if (
563 dataSeriesType === EDataSeriesType.Xy ||
564 dataSeriesType === EDataSeriesType.Ohlc ||
565 dataSeriesType === EDataSeriesType.XyText
566 ) {
567 if (index >= 0) {
568 series = dataSeriesArray.map((d) => d.getNativeYValues().get(index));
569 } else {
570 series = Array.from(Array(seriesCount)).fill(0);
571 }
572 } else if (dataSeriesType === EDataSeriesType.Xyy) {
573 if (index >= 0) {
574 series = [];
575 for (const dataSeries of dataSeriesArray) {
576 const xyy = dataSeries as XyyDataSeries;
577 series.push(xyy.getNativeYValues().get(index));
578 series.push(xyy.getNativeY1Values().get(index));
579 }
580 } else {
581 series = Array.from(Array(seriesCount * 2)).fill(0);
582 }
583 } else if (dataSeriesType === EDataSeriesType.Xyz) {
584 if (index >= 0) {
585 series = [];
586 for (const dataSeries of dataSeriesArray) {
587 const xyy = dataSeries as XyzDataSeries;
588 series.push(xyy.getNativeYValues().get(index));
589 series.push(xyy.getNativeZValues().get(index));
590 }
591 } else {
592 series = Array.from(Array(seriesCount * 2)).fill(0);
593 }
594 }
595 socket.emit("getData", { series, startX: index + 1, pointsPerUpdate, sendEvery, positive, scale: 10 });
596 isRunning = true;
597 loadFromBuffer();
598 };
599
600 const settings = {
601 seriesCount: 10,
602 pointsOnChart: 5000,
603 pointsPerUpdate: 10,
604 sendEvery: 100,
605 initialPoints: 5000,
606 };
607
608 const updateSettings = (newValues: ISettings) => {
609 Object.assign(settings, newValues);
610 };
611
612 // Buttons for chart
613 const startUpdate = () => {
614 console.log("start streaming");
615 loadCount = 0;
616 avgLoadTime = 0;
617 avgRenderTime = 0;
618 loadTimes = [];
619 loadStart = 0;
620 droppedPackets = 0;
621 messageUpdateCounter = 0;
622 seriesCount = settings.seriesCount;
623 initialPoints = settings.initialPoints;
624 pointsOnChart = settings.pointsOnChart;
625 pointsPerUpdate = settings.pointsPerUpdate;
626 sendEvery = settings.sendEvery;
627 const positive = initChart();
628 initWebSocket(positive);
629 };
630
631 const stopUpdate = () => {
632 console.log("stop streaming");
633 socket?.disconnect();
634 isRunning = false;
635 if (sciChartSurface.chartModifiers.size() === 0) {
636 sciChartSurface.chartModifiers.add(
637 new MouseWheelZoomModifier(),
638 new ZoomPanModifier({ enableZoom: true }),
639 new ZoomExtentsModifier()
640 );
641 }
642 };
643 return { wasmContext, sciChartSurface, controls: { startUpdate, stopUpdate, updateSettings } };
644 };
645This example demonstrates a high-performance real-time charting application using SciChart.js within an Angular standalone component. The application streams massive datasets via a WebSocket connection and allows users to dynamically choose different chart types such as line, column, mountain, band, scatter, and candlestick charts using Angular Material controls.
The solution leverages Angular’s two-way data binding with ngModel to update chart configurations on the fly. The SciChart chart is implemented using the ScichartAngularComponent from scichart-angular, which initializes the chart and exposes control methods to update settings and start/stop data streaming. Events from Angular Material sliders, radio buttons, and buttons are used to modify parameters like series count and point updates, while ChangeDetectorRef is employed to trigger efficient UI refreshes in response to real-time data changes as described in Angular ChangeDetectorRef. Additionally, the WebSocket integration via Socket.IO manages the live data feed, in line with best practices from WebSockets in Angular: A Comprehensive Guide.
The application supports real-time data streaming with dynamic updates, enabling the chart to continuously append and remove points to maintain performance while handling millions of data points. Users can adjust parameters such as the number of series, initial points, points per update, and the data sending interval using Angular Material sliders with logarithmic scaling. This precise control mechanism makes use of Angular’s data binding and event handling techniques, echoing the concepts covered in Angular Two-Way Binding.
This example exemplifies how to integrate SciChart.js into an Angular environment by combining the power of the scichart-angular component with Angular Material’s robust UI elements. For guidance on setting up projects with SciChart.js, developers can refer to Getting Started with SciChart JS and the scichart-angular package on npm. Moreover, insights into optimizing Angular performance and change detection can be found in Angular Change Detection and Runtime Optimization, which is crucial for maintaining responsiveness in high-frequency data update scenarios.

This demo showcases the incredible realtime performance of our Angular charts by updating the series with millions of data-points!

This demo showcases the incredible performance of our Angular Chart by loading 500 series with 500 points (250k points) instantly!

This demo showcases the incredible performance of our JavaScript Chart by loading a million points instantly.

This demo showcases the realtime performance of our Angular Chart by animating several series with thousands of data-points at 60 FPS

See the frequency of recordings with the Angular audio spectrum analyzer example from SciChart. This real-time audio visualizer demo uses a Fourier Transform.

Demonstrates how to create Oil and Gas Dashboard

This dashboard demo showcases the incredible realtime performance of our Angular charts by updating the series with millions of data-points!

This demo showcases the incredible realtime performance of our Angular charts by updating the series with millions of data-points!

Demonstrates a custom modifier which can convert from single chart to grid layout and back.

Demonstrates how to repurpose a Candlestick Series into dragabble, labled, event markers

Population Pyramid of Europe and Africa

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