Demonstrates how to use a ScaleOffsetFilter to convert data to a Percentage Change with realtime updates, using SciChart.js, High Performance JavaScript Charts
drawExample.ts
index.tsx
RandomWalkGenerator.ts
theme.ts
1import { appTheme } from "../../../theme";
2import { RandomWalkGenerator } from "../../../ExampleData/RandomWalkGenerator";
3import {
4 SciChartSurface,
5 NumericAxis,
6 NumberRange,
7 EAutoRange,
8 XyDataSeries,
9 XyScaleOffsetFilter,
10 FastLineRenderableSeries,
11 HitTestInfo,
12 XySeriesInfo,
13 SeriesInfo,
14 ZoomPanModifier,
15 ZoomExtentsModifier,
16 RolloverModifier,
17 TextAnnotation,
18 EHorizontalAnchorPoint,
19 EVerticalAnchorPoint,
20 ECoordinateMode,
21 EAnnotationLayer,
22 ENumericFormat,
23} from "scichart";
24
25// Custom formatNumber function to avoid conflicts
26const customFormatNumber = (value: number, format: ENumericFormat, precision: number) => {
27 return value.toFixed(precision);
28};
29
30const getScaleValue = (dataSeries: XyDataSeries, zeroXValue: number) => {
31 const dataLength = dataSeries.count();
32 let zeroIndex = -1;
33 for (let i = 0; i < dataLength; i++) {
34 const xValue = dataSeries.getNativeXValues().get(i);
35 if (xValue >= zeroXValue) {
36 zeroIndex = i;
37 break;
38 }
39 }
40 if (zeroIndex === -1) {
41 return 1;
42 }
43 return 100 / dataSeries.getNativeYValues().get(zeroIndex);
44};
45
46class TransformedSeries extends FastLineRenderableSeries {
47 public originalSeries: XyDataSeries;
48
49 public override getSeriesInfo(hitTestInfo: HitTestInfo): SeriesInfo {
50 const info = new XySeriesInfo(this, hitTestInfo);
51 if (this.originalSeries && info.dataSeriesIndex !== undefined) {
52 info.yValue = this.originalSeries.getNativeYValues().get(info.dataSeriesIndex);
53 }
54 return info;
55 }
56}
57
58export const drawExample = async (rootElement: string | HTMLDivElement, usePercentage: boolean) => {
59 const { sciChartSurface, wasmContext } = await SciChartSurface.create(rootElement, {
60 theme: appTheme.SciChartJsTheme,
61 });
62
63 const xAxis = new NumericAxis(wasmContext);
64 sciChartSurface.xAxes.add(xAxis);
65
66 const yAxis = new NumericAxis(wasmContext, {
67 autoRange: EAutoRange.Always,
68 labelPostfix: usePercentage ? "%" : "",
69 labelPrecision: usePercentage ? 0 : 1,
70 growBy: new NumberRange(0.1, 0.1),
71 });
72
73 yAxis.labelProvider.formatCursorLabel = (value: number) => customFormatNumber(value, ENumericFormat.Decimal, 1);
74 sciChartSurface.yAxes.add(yAxis);
75
76 const lineSeries = new TransformedSeries(wasmContext, {
77 strokeThickness: 3,
78 stroke: appTheme.VividSkyBlue,
79 });
80 sciChartSurface.renderableSeries.add(lineSeries);
81
82 const data0 = new RandomWalkGenerator().Seed(1337).getRandomWalkSeries(100);
83 const dataSeries1 = new XyDataSeries(wasmContext, { xValues: data0.xValues, yValues: data0.yValues });
84
85 const transform1 = new XyScaleOffsetFilter(dataSeries1, { offset: -100 });
86
87 xAxis.visibleRangeChanged.subscribe(
88 (args) => (transform1.scale = getScaleValue(dataSeries1, args.visibleRange.min))
89 );
90
91 if (usePercentage) {
92 lineSeries.dataSeries = transform1;
93 lineSeries.originalSeries = dataSeries1;
94 } else {
95 lineSeries.dataSeries = dataSeries1;
96 }
97
98 const lineSeries2 = new TransformedSeries(wasmContext, {
99 strokeThickness: 3,
100 stroke: appTheme.VividOrange,
101 });
102 sciChartSurface.renderableSeries.add(lineSeries2);
103
104 const data1 = new RandomWalkGenerator().Seed(0).getRandomWalkSeries(100);
105 const dataSeries2 = new XyDataSeries(wasmContext, { xValues: data1.xValues, yValues: data1.yValues });
106
107 const transform2 = new XyScaleOffsetFilter(dataSeries2, { offset: -100 });
108 xAxis.visibleRangeChanged.subscribe(
109 (args) => (transform2.scale = getScaleValue(dataSeries2, args.visibleRange.min))
110 );
111
112 if (usePercentage) {
113 lineSeries2.dataSeries = transform2;
114 lineSeries2.originalSeries = dataSeries2;
115 } else {
116 lineSeries2.dataSeries = dataSeries2;
117 }
118
119 sciChartSurface.chartModifiers.add(new ZoomPanModifier({ enableZoom: true }));
120 sciChartSurface.chartModifiers.add(new ZoomExtentsModifier());
121 sciChartSurface.chartModifiers.add(new RolloverModifier({ rolloverLineStroke: appTheme.VividTeal }));
122
123 sciChartSurface.annotations.add(
124 new TextAnnotation({
125 text: "Toggle between original data & Percentage Changed on chart",
126 fontSize: 16,
127 textColor: appTheme.ForegroundColor,
128 x1: 0.5,
129 y1: 0,
130 opacity: 0.77,
131 horizontalAnchorPoint: EHorizontalAnchorPoint.Center,
132 xCoordinateMode: ECoordinateMode.Relative,
133 yCoordinateMode: ECoordinateMode.Relative,
134 })
135 );
136
137 const watermarkText = usePercentage ? "Percentage Changed" : "Original Data";
138 sciChartSurface.annotations.add(
139 new TextAnnotation({
140 text: watermarkText,
141 fontSize: 32,
142 textColor: appTheme.ForegroundColor,
143 x1: 0.5,
144 y1: 0.5,
145 opacity: 0.23,
146 horizontalAnchorPoint: EHorizontalAnchorPoint.Center,
147 verticalAnchorPoint: EVerticalAnchorPoint.Center,
148 xCoordinateMode: ECoordinateMode.Relative,
149 yCoordinateMode: ECoordinateMode.Relative,
150 annotationLayer: EAnnotationLayer.BelowChart,
151 })
152 );
153
154 return { sciChartSurface, wasmContext };
155};
156
Chart with Linear Trendline, Moving Average and Ratio Filters with filter chaining
Demonstrates simple and advanced Custom Filters for data transformation and aggregation, with realtime updates
Demonstrates how to add draggable thresholds which change the series color in the chart in SciChart.js