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};
156This example demonstrates how to transform raw data into a real-time percentage change chart using SciChart.js with JavaScript. It converts random walk data into percentage-based visualizations and offers dynamic interactivity with high performance.
The implementation generates two random walk data series that are transformed using the XyScaleOffsetFilter to calculate percentage changes. By subscribing to the x-axis visible range changes, the chart dynamically adjusts the filter scale in real time, as detailed in the Adding Realtime Updates documentation. Furthermore, a custom series extending the fast line renderable series is implemented to override tooltip behavior, providing precise display of original values—techniques similar to those covered in Tutorial 07 - Adding Tooltips and Legends.
The example incorporates real-time data transformation and interactive features such as zooming and panning using modifiers like the ZoomPanModifier. It also provides a toggle mechanism to switch between the original data and its percentage change view, ensuring an engaging and informative user experience. Data simulation is achieved via the RandomWalkGenerator, echoing strategies used in the Realtime JavaScript Chart Performance Demo.
Focusing on JavaScript, the example emphasizes clean integration with SciChart.js by adhering to event-driven update patterns and performance optimization techniques. By limiting updates to essential parameters and leveraging efficient WebGL rendering, the implementation serves as a robust template for developers aiming to build interactive, high-performance charts with SciChart.js.

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