Client/Server Websocket Data Streaming

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

Fullscreen

Edit

 Edit

Docs

drawExample.ts

angular.ts

theme.ts

Copy to clipboard
Minimise
Fullscreen
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    };
645

Websocket Big Data Angular Integration

Overview

This 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.

Technical Implementation

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.

Features and Capabilities

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.

Integration and Best Practices

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.

angular Chart Examples & Demos

See Also: Performance Demos & Showcases (12 Demos)

Realtime Angular Chart Performance Demo | SciChart.js

Realtime Angular Chart Performance Demo

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

Load 500 Series x 500 Points Performance Demo | SciChart

Load 500 Series x 500 Points Performance Demo

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

Load 1 Million Points Performance Demo | SciChart.js Demo

Load 1 Million Points Performance Demo

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

Realtime Ghosted Traces | Angular Charts | SciChart.js Demo

Realtime Ghosted Traces

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

Realtime Audio Spectrum Analyzer Chart | SciChart.js Demo

Realtime Audio Spectrum Analyzer Chart Example

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

Oil & Gas Explorer Angular Dashboard | SciChart.js Demo

Oil & Gas Explorer Angular Dashboard

Demonstrates how to create Oil and Gas Dashboard

Server Traffic Dashboard | Angular Charts | SciChart.js Demo

Server Traffic Dashboard

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

Rich Interactions Showcase | Angular Charts | SciChart.js

Rich Interactions Showcase

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

Dynamic Layout Showcase | Angular Charts | SciChart.js Demo

Dynamic Layout Showcase

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

Dragabble Event Markers | Angular Charts | SciChart.js Demo

Dragabble Event Markers

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

Angular Population Pyramid | Angular Charts | SciChart.js

Angular Population Pyramid

Population Pyramid of Europe and Africa

NEW!
High Performance SVG Cursor & Rollover | SciChart.js Demo

High Performance SVG Cursor & Rollover

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

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