Rich Interactions Showcase

Demonstrates rich interactivity with custom modifiers using SciChart.js, High Performance JavaScript Charts

Fullscreen

Edit

 Edit

Docs

drawExample.ts

angular.ts

theme.ts

AddIOModifier.ts

DiscreteAxisMarker.ts

PointDragModifier.ts

Copy to clipboard
Minimise
Fullscreen
1import { appTheme } from "../../../theme";
2import { AddIOModifier } from "./AddIOModifier";
3import { DiscreteAxisMarker } from "./DiscreteAxisMarker";
4import { PointDragModifier } from "./PointDragModifier";
5
6import {
7    AnnotationClickEventArgs,
8    AxisMarkerAnnotation,
9    BasePaletteProvider,
10    BoxAnnotation,
11    CustomAnnotation,
12    ECoordinateMode,
13    EDataChangeType,
14    EHorizontalAnchorPoint,
15    EStrokePaletteMode,
16    ESeriesType,
17    EAxisAlignment,
18    ELabelPlacement,
19    EExecuteOn,
20    EDraggingGripPoint,
21    EWrapTo,
22    FastLineRenderableSeries,
23    HeatmapColorMap,
24    HorizontalLineAnnotation,
25    IStrokePaletteProvider,
26    IRenderableSeries,
27    MouseWheelZoomModifier,
28    NativeTextAnnotation,
29    NumericAxis,
30    NumberRange,
31    PaletteFactory,
32    RolloverModifier,
33    RubberBandXyZoomModifier,
34    SciChartSurface,
35    SplineLineRenderableSeries,
36    TGradientStop,
37    TSciChart,
38    UniformHeatmapDataSeries,
39    UniformHeatmapRenderableSeries,
40    VerticalLineAnnotation,
41    XyDataSeries,
42    XyScatterRenderableSeries,
43    YAxisDragModifier,
44    ZoomExtentsModifier,
45    ZoomPanModifier,
46    EVerticalAnchorPoint,
47    GenericAnimation,
48    formatNumber,
49    EllipsePointMarker,
50} from "scichart";
51
52const gradientStops = [
53    { offset: 0, color: "#942B96" },
54    { offset: 0.3, color: "#3C2D91" },
55    { offset: 0.45, color: "#47bde6" },
56    { offset: 0.5, color: appTheme.DarkIndigo },
57    { offset: 0.55, color: "#68bcae" },
58    { offset: 0.7, color: "#e97064" },
59    { offset: 1.0, color: "#ae418d" },
60];
61
62const csGradientStops = [
63    { offset: 0, color: "#942B96" },
64    { offset: 0.3, color: "#3C2D91" },
65    { offset: 0.45, color: "#47bde6" },
66    { offset: 0.5, color: "#45AEC3" },
67    { offset: 0.55, color: "#68bcae" },
68    { offset: 0.7, color: "#e97064" },
69    { offset: 1.0, color: "#ae418d" },
70];
71
72let width = 700;
73let height = 500;
74
75export const getChartsInitializationApi = () => {
76    let initialZValues: number[][];
77    let velocities: number[][];
78    // main surface
79    let mainSurface: SciChartSurface;
80    let crossSectionSurface: SciChartSurface;
81    let inputSurface: SciChartSurface;
82    let outputSurface: SciChartSurface;
83    let addInputSeries: (color: string, freq?: number) => void;
84    let addOutputSeries: (color: string) => void;
85
86    const initMainChart = async (rootElement: string | HTMLDivElement) => {
87        const { sciChartSurface, wasmContext } = await SciChartSurface.create(rootElement, {
88            theme: appTheme.SciChartJsTheme,
89        });
90        const xAxis = new NumericAxis(wasmContext, { isVisible: false });
91        sciChartSurface.xAxes.add(xAxis);
92        const yAxis = new NumericAxis(wasmContext, {
93            axisAlignment: EAxisAlignment.Left,
94            isVisible: true,
95            drawLabels: false,
96            drawMajorGridLines: false,
97            drawMajorBands: false,
98            drawMajorTickLines: false,
99            drawMinorTickLines: false,
100            drawMinorGridLines: false,
101        });
102        sciChartSurface.yAxes.add(yAxis);
103
104        width = Math.floor(sciChartSurface.domCanvas2D.width);
105        height = Math.floor(sciChartSurface.domCanvas2D.height);
106        initialZValues = Array.from(Array(height), (_) => Array(width).fill(0));
107        velocities = Array.from(Array(height), (_) => Array(width).fill(0));
108
109        mainSurface = sciChartSurface;
110
111        return { sciChartSurface };
112    };
113
114    const initCrossSectionChart = async (rootElement: string | HTMLDivElement) => {
115        const { sciChartSurface, wasmContext } = await SciChartSurface.create(rootElement, {
116            theme: appTheme.SciChartJsTheme,
117        });
118
119        sciChartSurface.xAxes.add(
120            new NumericAxis(wasmContext, {
121                id: "xh",
122                isInnerAxis: true,
123                visibleRange: new NumberRange(0, width),
124                visibleRangeLimit: new NumberRange(0, width),
125                drawMinorGridLines: false,
126                zoomExtentsToInitialRange: true,
127                drawLabels: false,
128            })
129        );
130
131        sciChartSurface.yAxes.add(
132            new NumericAxis(wasmContext, {
133                id: "yh",
134                visibleRange: new NumberRange(-50, 50),
135                visibleRangeLimit: new NumberRange(-100, 100),
136                axisAlignment: EAxisAlignment.Right,
137                drawMinorGridLines: false,
138                labelPrecision: 0,
139                zoomExtentsToInitialRange: true,
140            })
141        );
142
143        sciChartSurface.xAxes.add(
144            new NumericAxis(wasmContext, {
145                id: "xv",
146                isInnerAxis: true,
147                visibleRange: new NumberRange(0, height),
148                visibleRangeLimit: new NumberRange(0, height),
149                flippedCoordinates: true,
150                axisAlignment: EAxisAlignment.Left,
151                zoomExtentsToInitialRange: true,
152                drawLabels: false,
153            })
154        );
155
156        sciChartSurface.yAxes.add(
157            new NumericAxis(wasmContext, {
158                id: "yv",
159                // isInnerAxis: true,
160                visibleRange: new NumberRange(-50, 50),
161                visibleRangeLimit: new NumberRange(-100, 100),
162                axisAlignment: EAxisAlignment.Top,
163                flippedCoordinates: true,
164                labelPrecision: 0,
165                zoomExtentsToInitialRange: true,
166            })
167        );
168
169        const lineSeriesh = new FastLineRenderableSeries(wasmContext, {
170            id: "h",
171            strokeThickness: 3,
172            stroke: "steelblue",
173            dataSeries: new XyDataSeries(wasmContext, { containsNaN: false, isSorted: true }),
174            paletteProvider: new YPalette(wasmContext, csGradientStops),
175            xAxisId: "xh",
176            yAxisId: "yh",
177        });
178        sciChartSurface.renderableSeries.add(lineSeriesh);
179        const lineSeriesv = new FastLineRenderableSeries(wasmContext, {
180            id: "v",
181            strokeThickness: 3,
182            stroke: "steelblue",
183            dataSeries: new XyDataSeries(wasmContext, { containsNaN: false, isSorted: true }),
184            paletteProvider: new YPalette(wasmContext, csGradientStops),
185            xAxisId: "xv",
186            yAxisId: "yv",
187        });
188        sciChartSurface.renderableSeries.add(lineSeriesv);
189
190        sciChartSurface.annotations.add(
191            new NativeTextAnnotation({
192                x1: 0.5,
193                y1: 0.02,
194                xCoordinateMode: ECoordinateMode.Relative,
195                yCoordinateMode: ECoordinateMode.Relative,
196                verticalAnchorPoint: EVerticalAnchorPoint.Top,
197                horizontalAnchorPoint: EHorizontalAnchorPoint.Center,
198                text: "Cross sections",
199                fontSize: 18,
200                textColor: appTheme.ForegroundColor,
201                opacity: 0.5,
202                wrapTo: EWrapTo.ViewRect,
203            })
204        );
205
206        sciChartSurface.chartModifiers.add(
207            new ZoomExtentsModifier(),
208            new MouseWheelZoomModifier(),
209            new RubberBandXyZoomModifier({ executeOn: EExecuteOn.MouseRightButton }),
210            new YAxisDragModifier({})
211        );
212
213        crossSectionSurface = sciChartSurface;
214
215        return { sciChartSurface };
216    };
217
218    const inputChart = async (rootElement: string | HTMLDivElement) => {
219        const { sciChartSurface, wasmContext } = await SciChartSurface.create(rootElement, {
220            theme: appTheme.SciChartJsTheme,
221        });
222        const xAxis = new NumericAxis(wasmContext, {
223            drawMinorGridLines: false,
224            visibleRange: new NumberRange(0, 30000),
225            visibleRangeLimit: new NumberRange(0, 30000),
226            axisAlignment: EAxisAlignment.Top,
227        });
228        xAxis.labelProvider.formatLabel = (dataValue) =>
229            xAxis.labelProvider.applyFormat(formatNumber(dataValue / 1000, xAxis.labelProvider.numericFormat, 0));
230
231        sciChartSurface.xAxes.add(xAxis);
232        const xValues = Array.from(Array(60)).map((_, i) => i * 500);
233        const yAxis = new NumericAxis(wasmContext, {
234            visibleRange: new NumberRange(-100, 100),
235            visibleRangeLimit: new NumberRange(-100, 100),
236            axisAlignment: EAxisAlignment.Left,
237            labelPrecision: 0,
238            drawMinorGridLines: false,
239        });
240
241        sciChartSurface.yAxes.add(yAxis);
242        const makeYValues = (frequency: number) => {
243            return Array.from(Array(60)).map(
244                (_, i) => Math.sin(((frequency * 2 - 30000) * i * 2 * Math.PI) / 60000) * 80
245            );
246        };
247
248        const addInputSeriesCallback = (color: string, freq?: number) => {
249            freq = freq ?? Math.floor((Math.random() * 30000) / 500) * 500;
250            const frequencyMarker = new DiscreteAxisMarker({
251                id: color,
252                x1: freq,
253                backgroundColor: color,
254                isEditable: true,
255                formattedValue: "Frequency",
256            });
257            frequencyMarker.stepSize = 250;
258            // hack to disable selection box while dragging
259            // @ts-ignore
260            frequencyMarker.updateAdornerInner = () => {};
261            const dataSeries = new XyDataSeries(wasmContext, {
262                containsNaN: false,
263                isSorted: true,
264                xValues,
265                yValues: makeYValues(freq),
266                metadata: { isSelected: false },
267            });
268            const lineSeries = new SplineLineRenderableSeries(wasmContext, {
269                id: color,
270                stroke: color,
271                pointMarker: new EllipsePointMarker(wasmContext, {
272                    stroke: color,
273                    fill: color,
274                    width: 5,
275                    height: 5,
276                }),
277                dataSeries,
278                onSelectedChanged: (sourceSeries: IRenderableSeries, isSelected: boolean) => {
279                    lineSeries.strokeThickness = isSelected ? 4 : 2;
280                    lineSeries.pointMarker.fill = isSelected ? "red" : color;
281                },
282            });
283            frequencyMarker.dragDelta.subscribe((args) => {
284                dataSeries.clear();
285                dataSeries.appendRange(xValues, makeYValues(frequencyMarker.x1));
286            });
287            sciChartSurface.renderableSeries.add(lineSeries);
288            sciChartSurface.annotations.add(frequencyMarker);
289        };
290
291        const timerLine = new VerticalLineAnnotation({
292            id: "timerLine",
293            stroke: "green",
294            x1: 0,
295        });
296        sciChartSurface.annotations.add(timerLine);
297
298        sciChartSurface.annotations.add(
299            new NativeTextAnnotation({
300                x1: 0.5,
301                y1: 0.02,
302                xCoordinateMode: ECoordinateMode.Relative,
303                yCoordinateMode: ECoordinateMode.Relative,
304                verticalAnchorPoint: EVerticalAnchorPoint.Top,
305                horizontalAnchorPoint: EHorizontalAnchorPoint.Center,
306                text: "Input drivers",
307                fontSize: 18,
308                textColor: appTheme.ForegroundColor,
309                opacity: 0.5,
310                wrapTo: EWrapTo.ViewRect,
311            })
312        );
313
314        sciChartSurface.chartModifiers.add(
315            new PointDragModifier(),
316            new ZoomPanModifier({ executeOn: EExecuteOn.MouseRightButton }),
317            new MouseWheelZoomModifier()
318        );
319
320        inputSurface = sciChartSurface;
321        addInputSeries = addInputSeriesCallback;
322
323        return { sciChartSurface, addInputSeries: addInputSeriesCallback };
324    };
325
326    const initHistoryChart = async (rootElement: string | HTMLDivElement) => {
327        const { sciChartSurface, wasmContext } = await SciChartSurface.create(rootElement, {
328            theme: appTheme.SciChartJsTheme,
329        });
330        const xAxis = new NumericAxis(wasmContext, {
331            visibleRange: new NumberRange(0, 30000),
332            visibleRangeLimit: new NumberRange(0, 30000),
333            drawMinorGridLines: false,
334            zoomExtentsToInitialRange: true,
335            axisAlignment: EAxisAlignment.Top,
336        });
337        xAxis.labelProvider.formatLabel = (dataValue) =>
338            xAxis.labelProvider.applyFormat(formatNumber(dataValue / 1000, xAxis.labelProvider.numericFormat, 0));
339        sciChartSurface.xAxes.add(xAxis);
340
341        sciChartSurface.yAxes.add(
342            new NumericAxis(wasmContext, {
343                visibleRange: new NumberRange(-30, 30),
344                visibleRangeLimit: new NumberRange(-100, 100),
345                axisAlignment: EAxisAlignment.Right,
346                drawMinorGridLines: false,
347                labelPrecision: 0,
348                cursorLabelPrecision: 1,
349            })
350        );
351
352        const rollover = new RolloverModifier();
353        sciChartSurface.chartModifiers.add(rollover);
354
355        const addOutputSeriesCallback = (color: string) => {
356            const lineData = new XyDataSeries(wasmContext, { containsNaN: true, isSorted: true });
357            const xarr = Array.from(Array(1500)).map((_, i) => i * 20);
358            const nanArr = xarr.map((x) => NaN);
359            lineData.appendRange(xarr, nanArr);
360            const lineSeries = new FastLineRenderableSeries(wasmContext, {
361                id: color,
362                strokeThickness: 3,
363                stroke: color,
364                dataSeries: lineData,
365                opacity: 0.8,
366            });
367
368            const dotSeries = new XyDataSeries(wasmContext, { containsNaN: true, isSorted: true });
369            const leadingDot = new XyScatterRenderableSeries(wasmContext, {
370                id: color + "dot",
371                pointMarker: new EllipsePointMarker(wasmContext, {
372                    width: 8,
373                    height: 8,
374                    strokeThickness: 2,
375                    fill: color,
376                    stroke: color,
377                }),
378                dataSeries: dotSeries,
379            });
380            rollover.includeSeries(leadingDot, false);
381            lineData.dataChanged.subscribe((data) => {
382                const changeIndex = data.changeType === EDataChangeType.Append ? lineData.count() - 1 : data.index;
383                dotSeries.clear();
384                dotSeries.append(
385                    lineData.getNativeXValues().get(changeIndex),
386                    lineData.getNativeYValues().get(changeIndex)
387                );
388            });
389            sciChartSurface.renderableSeries.add(lineSeries, leadingDot);
390        };
391
392        sciChartSurface.annotations.add(
393            new NativeTextAnnotation({
394                x1: 0.5,
395                y1: 0.02,
396                xCoordinateMode: ECoordinateMode.Relative,
397                yCoordinateMode: ECoordinateMode.Relative,
398                verticalAnchorPoint: EVerticalAnchorPoint.Top,
399                horizontalAnchorPoint: EHorizontalAnchorPoint.Center,
400                text: "Point outputs",
401                fontSize: 18,
402                textColor: appTheme.ForegroundColor,
403                opacity: 0.5,
404                wrapTo: EWrapTo.ViewRect,
405            })
406        );
407
408        sciChartSurface.chartModifiers.add(
409            new ZoomPanModifier({ enableZoom: true }),
410            new ZoomExtentsModifier(),
411            new MouseWheelZoomModifier(),
412            new RubberBandXyZoomModifier({ executeOn: EExecuteOn.MouseRightButton })
413        );
414
415        outputSurface = sciChartSurface;
416        addOutputSeries = addOutputSeriesCallback;
417
418        return { sciChartSurface, addOutputSeries: addOutputSeriesCallback };
419    };
420
421    const onAllChartsInit = () => {
422        const heatmapDataSeries = new UniformHeatmapDataSeries(mainSurface.webAssemblyContext2D, {
423            xStart: 1,
424            xStep: 1,
425            yStart: 1,
426            yStep: 1,
427            zValues: initialZValues,
428        });
429
430        const heatmapSeries = new UniformHeatmapRenderableSeries(mainSurface.webAssemblyContext2D, {
431            opacity: 0.8,
432            dataSeries: heatmapDataSeries,
433            useLinearTextureFiltering: true,
434            colorMap: new HeatmapColorMap({ minimum: -80, maximum: 80, gradientStops }),
435        });
436
437        // Add heatmap to the chart
438        mainSurface.renderableSeries.add(heatmapSeries);
439
440        const lineDataSeries = crossSectionSurface.renderableSeries.getById("h").dataSeries as XyDataSeries;
441        const vlineDataSeries = crossSectionSurface.renderableSeries.getById("v").dataSeries as XyDataSeries;
442        const lineXValues = Array.from(Array(width)).map((_, i) => i);
443        const vlineXValues = Array.from(Array(height)).map((_, i) => i);
444
445        const hline = new HorizontalLineAnnotation({
446            stroke: "#E97064",
447            strokeThickness: 3,
448            y1: height / 3,
449            isEditable: true,
450            labelPlacement: ELabelPlacement.TopLeft,
451            axisLabelStroke: "#E97064",
452            showLabel: true,
453            dragOnLabel: false,
454            onDrag: () => {
455                lineDataSeries.clear();
456                const yVals = initialZValues[Math.floor(hline.y1)];
457                if (yVals) {
458                    lineDataSeries.appendRange(lineXValues, yVals);
459                }
460            },
461        });
462
463        const vline = new VerticalLineAnnotation({
464            stroke: "#E97064",
465            strokeThickness: 3,
466            labelPlacement: ELabelPlacement.BottomLeft,
467            axisLabelStroke: "#E97064",
468            showLabel: true,
469            x1: width / 2,
470            isEditable: true,
471            onDrag: () => {
472                vlineDataSeries.clear();
473                const yVals = initialZValues.map((r) => r[Math.floor(vline.x1)]);
474                if (yVals) {
475                    vlineDataSeries.appendRange(vlineXValues, yVals);
476                }
477            },
478        });
479
480        const inputs: BoxAnnotation[] = [];
481        const outputs: CustomAnnotation[] = [];
482
483        const outputColors = ["#0bf4cd", "#f4840b", "#0bdef4", "#f6086c", "#112cce", "#9002a1"];
484
485        const addIO = new AddIOModifier();
486
487        const inputColors = ["#68bcae", "#e97064", "#47bde6", "#ae418d", "#274b92", "#634e96"];
488
489        const removeInput = (input: BoxAnnotation) => {
490            mainSurface.annotations.remove(input);
491            const inputSeries = inputSurface.renderableSeries.getById(input.id);
492            inputSurface.renderableSeries.remove(inputSeries);
493            inputSeries.delete();
494            const freqAnn = inputSurface.annotations.getById(input.id);
495            inputSurface.annotations.remove(freqAnn);
496            freqAnn?.delete();
497            inputColors.push(input.id);
498            inputs.splice(inputs.indexOf(input), 1);
499            input.delete();
500        };
501        const addInput = (x: number, y: number, w?: number, h?: number, freq?: number) => {
502            if (inputColors.length === 0) return;
503            const color = inputColors.shift();
504            w = w ?? Math.round(width / 80);
505            h = h ?? w;
506            const boxInput = new BoxAnnotation({
507                id: color,
508                x1: x - w,
509                x2: x + w,
510                y1: y - h,
511                y2: y + h,
512                isEditable: true,
513                strokeThickness: 3,
514                stroke: color,
515                fill: "transparent",
516                dragPoints: [EDraggingGripPoint.Body, EDraggingGripPoint.x2y1],
517                onClick: (args: AnnotationClickEventArgs) => {
518                    if (args.mouseArgs.button !== EExecuteOn.MouseRightButton) return;
519                    removeInput(boxInput);
520                },
521            });
522
523            boxInput.selectedChanged.subscribe((isSelected: boolean) => {
524                inputSurface.renderableSeries.getById(color).isSelected = isSelected;
525            });
526            boxInput.dragDelta.subscribe((data) => {
527                if (boxInput.x1 < 1) boxInput.x1 = 1;
528                if (boxInput.x2 >= width) boxInput.x1 = width - 1;
529                if (boxInput.y1 < 1) boxInput.y1 = 1;
530                if (boxInput.y2 >= height) boxInput.y1 = height - 1;
531            });
532
533            inputs.push(boxInput);
534            mainSurface.annotations.add(boxInput);
535            addInputSeries(color, freq);
536        };
537
538        const removeOutput = (output: CustomAnnotation) => {
539            mainSurface.annotations.remove(output);
540            const os = outputSurface.renderableSeries.getById(output.id);
541            outputSurface.renderableSeries.remove(os);
542            os?.delete();
543            // remove leading dot
544            const od = outputSurface.renderableSeries.getById(output.id + "dot");
545            outputSurface.renderableSeries.remove(od);
546            od.delete();
547            outputColors.push(output.id);
548            outputs.splice(outputs.indexOf(output), 1);
549            output.delete();
550        };
551        const addOutput = (x: number, y: number) => {
552            if (outputColors.length === 0) return;
553            const color = outputColors.shift();
554            const output = new CustomAnnotation({
555                id: color,
556                x1: x,
557                y1: y,
558                horizontalAnchorPoint: EHorizontalAnchorPoint.Center,
559                verticalAnchorPoint: EVerticalAnchorPoint.Center,
560                svgString: `<svg width="16" height="16"  xmlns="http://www.w3.org/2000/svg"><ellipse style="fill: ${color}; stroke: rgb(0, 0, 0);" cx="8" cy="8" rx="7" ry="7"></ellipse></svg>`,
561                isEditable: true,
562                dragPoints: [EDraggingGripPoint.Body],
563                onClick: (args: AnnotationClickEventArgs) => {
564                    if (args.mouseArgs.button !== EExecuteOn.MouseRightButton) return;
565                    removeOutput(output);
566                },
567            });
568            output.selectedChanged.subscribe((isSelected: boolean) => {
569                if (isSelected) {
570                    outputSurface.renderableSeries
571                        .asArray()
572                        .filter((rs) => rs.type === ESeriesType.LineSeries)
573                        .forEach((rs) => {
574                            rs.opacity = rs.id === color ? 1 : 0.3;
575                        });
576                } else {
577                    outputSurface.renderableSeries
578                        .asArray()
579                        .filter((rs) => rs.type === ESeriesType.LineSeries)
580                        .forEach((rs) => (rs.opacity = 0.8));
581                }
582                outputSurface.renderableSeries.getById(color).isSelected = isSelected;
583            });
584            // @ts-ignore
585            output.updateAdornerInner = () => {};
586            output.dragDelta.subscribe((data) => {
587                if (output.x1 < 1) output.x1 = 1;
588                if (output.x1 >= width) output.x1 = width - 1;
589                if (output.y1 < 1) output.y1 = 1;
590                if (output.y1 >= height) output.y1 = height - 1;
591            });
592            outputs.push(output);
593            mainSurface.annotations.add(output);
594            addOutputSeries(color);
595        };
596
597        mainSurface.annotations.add(hline, vline);
598
599        addIO.onAddInput = addInput;
600        addIO.onAddOutput = addOutput;
601        mainSurface.chartModifiers.add(addIO);
602
603        const dampingMarker = new AxisMarkerAnnotation({
604            id: "damping",
605            y1: 100,
606            backgroundColor: "#E97064",
607            isEditable: true,
608            formattedValue: "Damping",
609        });
610        // hack to disable selection box while dragging
611        // @ts-ignore
612        dampingMarker.updateAdornerInner = () => {};
613        mainSurface.annotations.add(dampingMarker);
614
615        mainSurface.annotations.add(
616            new NativeTextAnnotation({
617                x1: 0.5,
618                y1: 0.02,
619                xCoordinateMode: ECoordinateMode.Relative,
620                yCoordinateMode: ECoordinateMode.Relative,
621                verticalAnchorPoint: EVerticalAnchorPoint.Top,
622                horizontalAnchorPoint: EHorizontalAnchorPoint.Center,
623                text: "2D Wave simulation",
624                fontSize: 18,
625                textColor: appTheme.ForegroundColor,
626                opacity: 0.5,
627                wrapTo: EWrapTo.ViewRect,
628            })
629        );
630
631        let timerId: NodeJS.Timeout;
632        const getValue = (r: number, c: number) => {
633            if (r < 0 || r === height || c < 0 || c === width) {
634                return 0;
635            } else {
636                return initialZValues[r][c];
637            }
638        };
639        let time = 0;
640        const timerLine1 = inputSurface.annotations.getById("timerLine");
641
642        const dt = 0.3;
643        let damping = 0.99;
644        const timestep = 20;
645        const updateChart = () => {
646            damping = 1 - Math.abs(dampingMarker.y1 / 10000);
647            const newZValues: number[][] = Array.from(Array(height), (_) => Array(width));
648            for (let r = 0; r < height; r++) {
649                for (let c = 0; c < width; c++) {
650                    const a =
651                        1 *
652                        (-8 * getValue(r, c) +
653                            getValue(r - 1, c) +
654                            getValue(r + 1, c) +
655                            getValue(r, c - 1) +
656                            getValue(r, c + 1) +
657                            getValue(r - 1, c - 1) +
658                            getValue(r + 1, c - 1) +
659                            getValue(r - 1, c + 1) +
660                            getValue(r + 1, c + 1));
661                    velocities[r][c] = (velocities[r][c] + a * dt) * damping;
662                    newZValues[r][c] = initialZValues[r][c] + velocities[r][c] * dt * 2;
663                }
664            }
665            timerLine1.x1 = time;
666            const inputIndex = Math.floor(time / 500);
667            // Update inputs
668            for (const boxInput of inputs) {
669                const inputSeries = inputSurface.renderableSeries.getById(boxInput.id).dataSeries as XyDataSeries;
670                const inputY = inputSeries.getNativeYValues().get(inputIndex);
671                const inputY1 = inputSeries.getNativeYValues().get((inputIndex + 1) % 60);
672                const inputInterpolated = inputY + ((inputY1 - inputY) / 500) * (time - inputIndex * 500);
673                const irStart = Math.max(2, Math.min(Math.floor(boxInput.y1), Math.floor(boxInput.y2)) - 1);
674                const irEnd = Math.min(height, Math.max(Math.floor(boxInput.y1), Math.floor(boxInput.y2)) - 1);
675                const icStart = Math.max(2, Math.min(Math.floor(boxInput.x1), Math.floor(boxInput.x2)) - 1);
676                const icEnd = Math.min(width, Math.max(Math.floor(boxInput.x1), Math.floor(boxInput.x2)) - 1);
677                for (let r = irStart; r < irEnd; r++) {
678                    for (let c = icStart; c < icEnd; c++) {
679                        newZValues[r][c] = inputInterpolated;
680                    }
681                }
682            }
683
684            initialZValues = newZValues;
685            heatmapDataSeries.setZValues(newZValues);
686            if (!hline.isHidden) {
687                lineDataSeries.clear();
688                const yVals = initialZValues[Math.floor(hline.y1)];
689                if (yVals) {
690                    lineDataSeries.appendRange(lineXValues, yVals);
691                }
692            }
693            if (!vline.isHidden) {
694                vlineDataSeries.clear();
695                const yVals = initialZValues.map((r) => r[Math.floor(vline.x1)]);
696                if (yVals) {
697                    vlineDataSeries.appendRange(vlineXValues, yVals);
698                }
699            }
700
701            // update outputs
702            for (const output of outputs) {
703                const outputDS = outputSurface.renderableSeries.getById(output.id).dataSeries as XyDataSeries;
704                outputDS.update(time / timestep, initialZValues[Math.floor(output.y1)][Math.floor(output.x1)]);
705            }
706
707            time = (time + timestep) % 30000;
708            timerId = setTimeout(updateChart, 20);
709        };
710
711        // Buttons for chart
712        const startUpdate = () => {
713            console.log("start animation");
714            updateChart();
715        };
716
717        const stopUpdate = () => {
718            console.log("stop animation");
719            clearTimeout(timerId);
720            timerId = undefined;
721        };
722
723        mainSurface.addDeletable({
724            delete: () => stopUpdate(),
725        });
726
727        const showHelp = () => {
728            const anim = getHelpAnnotation(
729                "This heatmap is running a 2D wave simulation.  Colours correspond to wave height. Drag the Damping marker to adjust how long the waves last.",
730                mainSurface
731            );
732            mainSurface.addAnimation(anim.startAnim);
733            const anim2 = getHelpAnnotation(
734                "This shows the cross section of the waveforms at the horizontal and vertical orange lines on the heatmap.\nTry Dragging the orange lines.",
735                crossSectionSurface
736            );
737            anim.next.onNext = () => mainSurface.addAnimation(anim2.startAnim);
738            const anim3 = getHelpAnnotation(
739                "The boxes are input locations. Drag to move them around, or click, then drag in the white circle to resize.",
740                mainSurface
741            );
742            anim2.next.onNext = () => mainSurface.addAnimation(anim3.startAnim);
743            const anim4 = getHelpAnnotation(
744                "These series drive the corresponding colored inputs.  Drag the frequency markers to set a regular driving input, or click and drag to edit the series directly.",
745                inputSurface
746            );
747            anim3.next.onNext = () => mainSurface.addAnimation(anim4.startAnim);
748            const anim5 = getHelpAnnotation(
749                "This chart records the values over time at the coloured output circles on the heatmap.  You can drag those around too.  Click on one to highlight its associated series.",
750                outputSurface
751            );
752            anim4.next.onNext = () => mainSurface.addAnimation(anim5.startAnim);
753            const anim6 = getHelpAnnotation(
754                "You can add additional inputs and outputs by left clicking on the heatmap and selecting one of the options.  This is all done with annotations and a custom modifier.  Right click on an input or output to remove it.",
755                mainSurface
756            );
757            anim5.next.onNext = () => mainSurface.addAnimation(anim6.startAnim);
758        };
759        showHelp();
760
761        const clearHeatmap = () => {
762            initialZValues = Array.from(Array(height), (_) => Array(width).fill(0));
763            velocities = Array.from(Array(height), (_) => Array(width).fill(0));
764            while (inputs.length > 0) {
765                const i = inputs[0];
766                removeInput(i);
767            }
768            while (outputs.length > 0) {
769                const o = outputs[0];
770                removeOutput(o);
771            }
772        };
773
774        const twoPoint = () => {
775            stopUpdate();
776            clearHeatmap();
777            addInput(width / 4, height / 2);
778            addInput((3 * width) / 4, height / 2);
779            addOutput((3 * width) / 4, (3 * height) / 4);
780            addOutput(width / 4, height / 4);
781            startUpdate();
782        };
783
784        const interference = () => {
785            stopUpdate();
786            clearHeatmap();
787            const sep = 20;
788            const gap = 10;
789            const outer = ((height - sep) / 2 - gap) / 2;
790            addInput(width / 2, height / 2, 2, sep / 2, 0);
791            addInput(width / 2, outer, 2, outer, 0);
792            addInput(width / 2, height - outer, 2, outer, 0);
793            addInput(width / 3, height / 2, 4, height / 3, 25000);
794            addOutput((3 * width) / 4, height / 2);
795            addOutput((3 * width) / 4, height / 3);
796            addOutput((3 * width) / 4, height / 4);
797            vline.x1 = (3 * width) / 4;
798            startUpdate();
799        };
800
801        twoPoint();
802
803        return {
804            startUpdate,
805            stopUpdate,
806            showHelp,
807            twoPoint,
808            interference,
809        };
810    };
811
812    return {
813        initMainChart,
814        initCrossSectionChart,
815        inputChart,
816        initHistoryChart,
817        onAllChartsInit,
818    };
819};
820
821const getHelpAnnotation = (text: string, surface: SciChartSurface) => {
822    const ann = new NativeTextAnnotation({
823        x1: 0.5,
824        y1: 0.5,
825        xCoordinateMode: ECoordinateMode.Relative,
826        yCoordinateMode: ECoordinateMode.Relative,
827        verticalAnchorPoint: EVerticalAnchorPoint.Center,
828        horizontalAnchorPoint: EHorizontalAnchorPoint.Center,
829        text,
830        opacity: 0,
831        fontSize: 18,
832        textColor: appTheme.ForegroundColor,
833        wrapTo: EWrapTo.ViewRect,
834    });
835    const next = {
836        onNext: () => {},
837    };
838    const startAnim = new GenericAnimation<number>({
839        from: 0,
840        to: 1,
841        duration: 500,
842        onAnimate(from, to, progress) {
843            if (!surface.annotations.contains(ann)) {
844                surface.annotations.add(ann);
845            } else {
846                ann.opacity = progress;
847            }
848        },
849        onCompleted() {
850            surface.addAnimation(
851                new GenericAnimation<number>({
852                    delay: 4000,
853                    duration: 500,
854                    from: 1,
855                    to: 0,
856                    onAnimate(from, to, progress) {
857                        ann.opacity = 1 - progress;
858                    },
859                    onCompleted() {
860                        ann.delete();
861                        surface.annotations.remove(ann);
862                        if (next.onNext) next.onNext();
863                    },
864                })
865            );
866        },
867    });
868    return { startAnim, next };
869};
870
871class YPalette extends BasePaletteProvider implements IStrokePaletteProvider {
872    public strokePaletteMode: EStrokePaletteMode.GRADIENT;
873    private dataSeries: XyDataSeries;
874    private wasmContext: TSciChart;
875    private colorData: number[];
876
877    constructor(wasmContext: TSciChart, stops: TGradientStop[]) {
878        super();
879        this.wasmContext = wasmContext;
880        this.colorData = PaletteFactory.createColorMap(wasmContext, stops);
881    }
882
883    public override onAttached(parentSeries: IRenderableSeries): void {
884        this.dataSeries = parentSeries.dataSeries as XyDataSeries;
885    }
886
887    public override onDetached(): void {}
888
889    public overrideStrokeArgb(xValue: number, yValue: number, index: number): number {
890        const y = this.dataSeries.getNativeYValues().get(index);
891        const lerpFactor = (y + 75) / 150;
892        const mapIndex = this.wasmContext.NumberUtil.Constrain(
893            Math.round(lerpFactor * (this.colorData.length - 1)),
894            0,
895            this.colorData.length - 1
896        );
897        const result = this.colorData[mapIndex];
898        return result;
899    }
900}
901

See Also: Performance Demos & Showcases (11 Demos)

Realtime Angular Chart Performance Demo | SciChart.js Demo

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.js Demo

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 | 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 Analyzer Demo | SciChart.js Demo

Realtime Audio Analyzer Demo

Demonstrating the capability of SciChart.js to create a JavaScript Audio Analyzer and visualize the Fourier-Transform of an audio waveform in realtime.

Oil & Gas Explorer Angular Dashboard | SciChart.js Demo

Oil & Gas Explorer Angular Dashboard

Demonstrates how to create Oil and Gas Dashboard

Client/Server Websocket Data Streaming | SciChart.js Demo

Client/Server Websocket Data Streaming

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

Server Traffic Dashboard | 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!

Dynamic Layout Showcase | SciChart.js Demo

Dynamic Layout Showcase

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

Dragabble Event Markers | SciChart.js Demo

Dragabble Event Markers

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

Angular Population Pyramid | SciChart.js Demo

Angular Population Pyramid

Population Pyramid of Europe and Africa

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