Angular 64-Chart Dashboard Performance Demo

Using the SubCharts API as part of SciChart.js, this demo showcases an 8x8 grid of 64 charts updating in realtime in JavaScript.

Fullscreen

Edit

 Edit

Docs

drawExample.ts

index.tsx

theme.ts

helpers.ts

Copy to clipboard
Minimise
Fullscreen
1import {
2    appendData,
3    createRenderableSeries,
4    getDataSeriesTypeForRenderableSeries,
5    getSubChartPositionIndexes,
6    prePopulateData,
7} from "./helpers";
8
9import {
10    BaseDataSeries,
11    EAnnotationLayer,
12    ECoordinateMode,
13    EDataSeriesType,
14    EAutoRange,
15    ENumericFormat,
16    EHorizontalAnchorPoint,
17    EMultiLineAlignment,
18    ESeriesType,
19    IRenderableSeries,
20    INumericAxisOptions,
21    I2DSubSurfaceOptions,
22    MouseWheelZoomModifier,
23    NativeTextAnnotation,
24    NumericAxis,
25    NumberRange,
26    Rect,
27    RightAlignedOuterVerticallyStackedAxisLayoutStrategy,
28    SciChartSubSurface,
29    SciChartSurface,
30    StackedColumnCollection,
31    StackedColumnRenderableSeries,
32    StackedMountainCollection,
33    StackedMountainRenderableSeries,
34    Thickness,
35    TSciChart,
36    ZoomExtentsModifier,
37    ZoomPanModifier,
38} from "scichart";
39import { appTheme } from "../../../theme";
40
41export type TMessage = {
42    title: string;
43    detail: string;
44};
45
46const axisOptions: INumericAxisOptions = {
47    useNativeText: true,
48    isVisible: false,
49    drawMajorBands: false,
50    drawMinorGridLines: false,
51    drawMinorTickLines: false,
52    drawMajorTickLines: false,
53    drawMajorGridLines: false,
54    labelStyle: { fontSize: 8 },
55    labelFormat: ENumericFormat.Decimal,
56    labelPrecision: 0,
57    autoRange: EAutoRange.Always,
58};
59
60// theme overrides
61const sciChartTheme = appTheme.SciChartJsTheme;
62
63export const drawGridExample = async (
64    rootElement: string | HTMLDivElement,
65    updateMessages: (newMessages: TMessage[]) => void
66) => {
67    const subChartsNumber = 64;
68    const columnsNumber = 8;
69    const rowsNumber = 8;
70
71    const dataSettings = {
72        seriesCount: 3,
73        pointsOnChart: 5000,
74        sendEvery: 30,
75        initialPoints: 20,
76    };
77
78    const originalGetStrokeColor = sciChartTheme.getStrokeColor;
79    let counter = 0;
80    sciChartTheme.getStrokeColor = (index: number, max: number, context: TSciChart) => {
81        const currentIndex = counter % subChartsNumber;
82        counter += 3;
83        return originalGetStrokeColor.call(sciChartTheme, currentIndex, subChartsNumber, context);
84    };
85
86    const originalGetFillColor = sciChartTheme.getFillColor;
87    sciChartTheme.getFillColor = (index: number, max: number, context: TSciChart) => {
88        const currentIndex = counter % subChartsNumber;
89        counter += 3;
90        return originalGetFillColor.call(sciChartTheme, currentIndex, subChartsNumber, context);
91    };
92    ///
93
94    const { wasmContext, sciChartSurface: mainSurface } = await SciChartSurface.createSingle(rootElement, {
95        theme: sciChartTheme,
96    });
97
98    const mainXAxis = new NumericAxis(wasmContext, {
99        isVisible: false,
100        id: "mainXAxis",
101    });
102
103    mainSurface.xAxes.add(mainXAxis);
104    const mainYAxis = new NumericAxis(wasmContext, {
105        isVisible: false,
106        id: "mainYAxis",
107    });
108    mainSurface.yAxes.add(mainYAxis);
109
110    const seriesNamesMap: { [key in ESeriesType]?: string } = {
111        [ESeriesType.LineSeries]: "Line",
112        [ESeriesType.StackedColumnSeries]: "Stacked Column",
113        [ESeriesType.ColumnSeries]: "Column",
114        [ESeriesType.StackedMountainSeries]: "Mountain",
115        [ESeriesType.BandSeries]: "Band",
116        [ESeriesType.CandlestickSeries]: "Candle",
117    };
118
119    const seriesTypes = [
120        ESeriesType.LineSeries,
121        // ESeriesType.BubbleSeries,
122        ESeriesType.StackedColumnSeries,
123        ESeriesType.ColumnSeries,
124        ESeriesType.StackedMountainSeries,
125        ESeriesType.BandSeries,
126        // ESeriesType.ScatterSeries,
127        ESeriesType.CandlestickSeries,
128        // ESeriesType.TextSeries
129    ];
130
131    const subChartPositioningCoordinateMode = ECoordinateMode.Relative;
132
133    const subChartsMap: Map<
134        SciChartSubSurface,
135        { seriesType: ESeriesType; dataSeriesType: EDataSeriesType; dataSeriesArray: BaseDataSeries[] }
136    > = new Map();
137
138    const xValues = Array.from(new Array(dataSettings.initialPoints).keys());
139
140    const initSubChart = (seriesType: ESeriesType, subChartIndex: number) => {
141        // calculate sub-chart position and sizes
142        const { rowIndex, columnIndex } = getSubChartPositionIndexes(subChartIndex, columnsNumber);
143        const width = 1 / columnsNumber;
144        const height = 1 / rowsNumber;
145
146        const position = new Rect(columnIndex * width, rowIndex * height, width, height);
147
148        // sub-surface configuration
149        const subChartOptions: I2DSubSurfaceOptions = {
150            id: `subChart-${subChartIndex}`,
151            theme: sciChartTheme,
152            position,
153            parentXAxisId: mainXAxis.id,
154            parentYAxisId: mainYAxis.id,
155            coordinateMode: subChartPositioningCoordinateMode,
156            subChartPadding: Thickness.fromNumber(1),
157            viewportBorder: {
158                color: "rgba(150, 74, 148, 0.51)",
159                border: 2,
160            },
161            // title: seriesNamesMap[seriesType],
162            titleStyle: {
163                placeWithinChart: true,
164                fontSize: 12,
165                padding: Thickness.fromString("10 4 0 4"),
166                color: appTheme.ForegroundColor,
167            },
168        };
169
170        // create sub-surface
171        const subChartSurface = mainSurface.addSubChart(subChartOptions);
172
173        // add axes to the sub-surface
174        const subChartXAxis = new NumericAxis(wasmContext, {
175            ...axisOptions,
176            id: `${subChartSurface.id}-XAxis`,
177            growBy: new NumberRange(0.0, 0.0),
178            useNativeText: true,
179        });
180
181        subChartSurface.xAxes.add(subChartXAxis);
182
183        const subChartYAxis = new NumericAxis(wasmContext, {
184            ...axisOptions,
185            id: `${subChartSurface.id}-YAxis`,
186            growBy: new NumberRange(0.01, 0.1),
187            useNativeText: true,
188        });
189        subChartSurface.yAxes.add(subChartYAxis);
190
191        // add series to sub-surface
192        const dataSeriesArray: BaseDataSeries[] = new Array(dataSettings.seriesCount);
193        const dataSeriesType = getDataSeriesTypeForRenderableSeries(seriesType);
194
195        let stackedCollection: IRenderableSeries;
196        const positive = [ESeriesType.StackedColumnSeries, ESeriesType.StackedMountainSeries].includes(seriesType);
197
198        for (let i = 0; i < dataSettings.seriesCount; i++) {
199            const { dataSeries, rendSeries } = createRenderableSeries(
200                wasmContext,
201                seriesType,
202                subChartXAxis.id,
203                subChartYAxis.id
204            );
205
206            dataSeriesArray[i] = dataSeries;
207
208            // add series to the sub-chart and apply additional configurations per series type
209            if (seriesType === ESeriesType.StackedColumnSeries) {
210                if (i === 0) {
211                    stackedCollection = new StackedColumnCollection(wasmContext, {
212                        dataPointWidth: 1,
213                        xAxisId: subChartXAxis.id,
214                        yAxisId: subChartYAxis.id,
215                    });
216                    subChartSurface.renderableSeries.add(stackedCollection);
217                }
218                (rendSeries as StackedColumnRenderableSeries).stackedGroupId = i.toString();
219                (stackedCollection as StackedColumnCollection).add(rendSeries as StackedColumnRenderableSeries);
220            } else if (seriesType === ESeriesType.StackedMountainSeries) {
221                if (i === 0) {
222                    stackedCollection = new StackedMountainCollection(wasmContext, {
223                        xAxisId: subChartXAxis.id,
224                        yAxisId: subChartYAxis.id,
225                    });
226                    subChartSurface.renderableSeries.add(stackedCollection);
227                }
228                (stackedCollection as StackedMountainCollection).add(rendSeries as StackedMountainRenderableSeries);
229            } else if (seriesType === ESeriesType.ColumnSeries) {
230                // create Stacked Y Axis
231                if (i === 0) {
232                    subChartSurface.layoutManager.rightOuterAxesLayoutStrategy =
233                        new RightAlignedOuterVerticallyStackedAxisLayoutStrategy();
234                    rendSeries.yAxisId = subChartYAxis.id;
235                } else {
236                    const additionalYAxis = new NumericAxis(wasmContext, {
237                        ...axisOptions,
238                        id: `${subChartSurface.id}-YAxis${i}`,
239                    });
240                    subChartSurface.yAxes.add(additionalYAxis);
241                    rendSeries.yAxisId = additionalYAxis.id;
242                }
243
244                subChartSurface.renderableSeries.add(rendSeries);
245            } else {
246                subChartSurface.renderableSeries.add(rendSeries);
247            }
248
249            // Generate points
250            prePopulateData(dataSeries, dataSeriesType, xValues, positive);
251
252            subChartSurface.zoomExtents(0);
253        }
254
255        subChartsMap.set(subChartSurface, { seriesType, dataSeriesType, dataSeriesArray });
256
257        return positive;
258    };
259
260    // generate the subcharts grid
261    for (let subChartIndex = 0; subChartIndex < subChartsNumber; ++subChartIndex) {
262        const seriesType = seriesTypes[subChartIndex % seriesTypes.length];
263        initSubChart(seriesType, subChartIndex);
264    }
265
266    // setup for realtime updates
267    let isRunning: boolean = false;
268    const newMessages: TMessage[] = [];
269    let loadStart = 0;
270    let loadCount: number = 0;
271    let avgRenderTime: number = 0;
272
273    const updateCharts = () => {
274        if (!isRunning) {
275            return;
276        }
277        loadStart = new Date().getTime();
278        subChartsMap.forEach(({ seriesType, dataSeriesArray, dataSeriesType }) => {
279            for (let i = 0; i < dataSettings.seriesCount; i++) {
280                const pointsToUpdate = Math.round(Math.max(1, dataSeriesArray[i].count() / 50));
281                appendData(
282                    seriesType,
283                    dataSeriesArray[i],
284                    dataSeriesType,
285                    i,
286                    dataSettings.pointsOnChart,
287                    pointsToUpdate
288                );
289            }
290        });
291
292        setTimeout(updateCharts, dataSettings.sendEvery);
293    };
294
295    // render time info calculation
296    mainSurface.rendered.subscribe(() => {
297        if (!isRunning || loadStart === 0) return;
298        const reDrawTime = new Date().getTime() - loadStart;
299        avgRenderTime = (avgRenderTime * loadCount + reDrawTime) / (loadCount + 1);
300        const charts = Array.from(subChartsMap.values());
301        const totalPoints = charts[0].dataSeriesArray[0].count() * 3 * charts.length;
302        newMessages.push({
303            title: `Total Points `,
304            detail: `${totalPoints}`,
305        });
306        newMessages.push({
307            title: `Average Render Time `,
308            detail: `${avgRenderTime.toFixed(2)} ms`,
309        });
310        newMessages.push({
311            title: `Max FPS `,
312            detail: `${Math.min(60, 1000 / avgRenderTime).toFixed(1)}`,
313        });
314        updateMessages(newMessages);
315        newMessages.length = 0;
316    });
317
318    // Buttons for chart
319    const startUpdate = () => {
320        console.log("start streaming");
321        loadCount = 0;
322        avgRenderTime = 0;
323        loadStart = 0;
324        isRunning = true;
325        updateCharts();
326    };
327
328    const stopUpdate = () => {
329        console.log("stop streaming");
330        isRunning = false;
331        if (mainSurface.chartModifiers.size() === 0) {
332            mainSurface.chartModifiers.add(
333                new MouseWheelZoomModifier(),
334                new ZoomPanModifier({ enableZoom: true }),
335                new ZoomExtentsModifier()
336            );
337        }
338    };
339
340    const setLabels = (show: boolean) => {
341        subChartsMap.forEach((v, k) => {
342            k.xAxes.get(0).isVisible = show;
343            k.yAxes.asArray().forEach((y) => (y.isVisible = show));
344        });
345    };
346
347    return {
348        wasmContext,
349        sciChartSurface: mainSurface,
350        controls: {
351            startUpdate,
352            stopUpdate,
353            setLabels,
354        },
355    };
356};
357

See Also: Subcharts API (1 Demo)

Dynamic Layout Showcase | SciChart.js Demo

Dynamic Layout Showcase

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

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