Demonstrates simple and advanced Custom Filters, with realtime updates using SciChart.js, High Performance JavaScript Charts
drawExample.ts
angular.ts
theme.ts
1import { appTheme } from "../../../theme";
2
3import {
4 BaseDataSeries,
5 NumericAxis,
6 EAutoRange,
7 EAxisAlignment,
8 ELabelAlignment,
9 FastColumnRenderableSeries,
10 FastLineRenderableSeries,
11 EllipsePointMarker,
12 LegendModifier,
13 NumberRange,
14 SciChartSurface,
15 XyCustomFilter,
16 XyDataSeries,
17 XyFilterBase,
18 XyScatterRenderableSeries,
19} from "scichart";
20
21// A custom filter which calculates the frequency distribution of the original data
22class AggregationFilter extends XyFilterBase {
23 private bins: Map<number, number> = new Map<number, number>();
24 private binWidthProperty = 1;
25
26 constructor(originalSeries: BaseDataSeries, binWidth: number, dataSeriesName: string) {
27 super(originalSeries, { dataSeriesName });
28 this.binWidthProperty = binWidth;
29 this.filterAll();
30 }
31
32 public get binWidth() {
33 return this.binWidthProperty;
34 }
35
36 public set binWidth(value: number) {
37 this.binWidthProperty = value;
38 this.filterAll();
39 }
40
41 protected filterAll() {
42 this.clear();
43 this.bins.clear();
44 this.filter(0, this.getOriginalCount());
45 }
46
47 protected override filterOnAppend(count: number): void {
48 // Overriding this so we do not have to reprocess the entire series on append
49 this.filter(this.getOriginalCount() - count, count);
50 }
51
52 protected filter(start: number, count: number): void {
53 const numUtil = this.originalSeries.webAssemblyContext.NumberUtil;
54 for (let i = start; i < start + count; i++) {
55 const bin = numUtil.RoundDown(this.getOriginalYValues().get(i), this.binWidth);
56 if (this.bins.has(bin)) {
57 const newVal = this.bins.get(bin) + 1;
58 this.bins.set(bin, newVal);
59 } else {
60 this.bins.set(bin, 1);
61 }
62 }
63 // Map data is unsorted, so we must sort it before recreating the output series
64 const keys = Array.from(this.bins.keys()).sort((a, b) => a - b);
65 this.clear();
66 const yValues: number[] = [];
67 for (const key of keys) {
68 yValues.push(this.bins.get(key));
69 }
70 this.appendRange(keys, yValues);
71 }
72
73 protected override onClear() {
74 this.clear();
75 this.bins.clear();
76 }
77}
78
79let lastX = 0;
80// Straight line data
81const getData = (n: number) => {
82 const xValues: number[] = [];
83 const yValues: number[] = [];
84 for (let i = 0; i < n; i++) {
85 xValues.push(lastX);
86 yValues.push(50 + lastX / 1000);
87 lastX++;
88 }
89 return { xValues, yValues };
90};
91
92export const drawExample = async (rootElement: string | HTMLDivElement) => {
93 // Define some constants
94 const numberOfPointsPerTimerTick = 500; // 1,000 points every timer tick
95 const timerInterval = 10; // timer tick every 10 milliseconds
96 const maxPoints = 100_000; // max points for a single series before the demo stops
97
98 const { sciChartSurface, wasmContext } = await SciChartSurface.create(rootElement, {
99 theme: appTheme.SciChartJsTheme,
100 });
101 const rawXAxis = new NumericAxis(wasmContext, { id: "rawX", isVisible: false, autoRange: EAutoRange.Always });
102 const aggXAxis = new NumericAxis(wasmContext, {
103 id: "aggX",
104 axisTitle: "Value",
105 autoRange: EAutoRange.Always,
106 labelPrecision: 0,
107 });
108 sciChartSurface.xAxes.add(rawXAxis, aggXAxis);
109
110 const rawYAxis = new NumericAxis(wasmContext, {
111 autoRange: EAutoRange.Always,
112 axisTitle: "Raw Data",
113 id: "rawY",
114 labelPrecision: 0,
115 labelStyle: { alignment: ELabelAlignment.Right },
116 });
117 const aggYAxis = new NumericAxis(wasmContext, {
118 axisTitle: "Frequency (Aggregated)",
119 id: "aggY",
120 autoRange: EAutoRange.Always,
121 axisAlignment: EAxisAlignment.Left,
122 growBy: new NumberRange(0, 0.5),
123 labelPrecision: 0,
124 });
125 sciChartSurface.yAxes.add(aggYAxis, rawYAxis);
126
127 const dataSeries = new XyDataSeries(wasmContext, { dataSeriesName: "Original Data" });
128
129 // Create a simple custom filter. We just have to specify the filter function and this will be applied efficiently to data changes
130 const gaussFilter = new XyCustomFilter(dataSeries, { dataSeriesName: "Custom Filter: Original x Gaussian Random" });
131 // This function exploits the central limit theorem to approximate a normal distribution
132 const gaussianRand = () => {
133 let rand = 0;
134 for (let i = 0; i < 6; i += 1) {
135 rand += Math.random() + 0.5;
136 }
137 return rand / 6;
138 };
139 gaussFilter.filterFunction = (i, y) => y * gaussianRand();
140
141 // Add the randomised data using a custom filter which takes original data * random value
142 sciChartSurface.renderableSeries.add(
143 new XyScatterRenderableSeries(wasmContext, {
144 pointMarker: new EllipsePointMarker(wasmContext, {
145 width: 3,
146 height: 3,
147 strokeThickness: 0,
148 fill: appTheme.VividOrange,
149 opacity: 0.77,
150 }),
151 stroke: appTheme.VividOrange,
152 dataSeries: gaussFilter,
153 xAxisId: "rawX",
154 yAxisId: "rawY",
155 })
156 );
157
158 // Add the original data to the chart
159 sciChartSurface.renderableSeries.add(
160 new FastLineRenderableSeries(wasmContext, {
161 dataSeries,
162 stroke: appTheme.VividTeal,
163 strokeThickness: 3,
164 xAxisId: "rawX",
165 yAxisId: "rawY",
166 })
167 );
168
169 // Pass the randomised data into the aggregation filter.
170 const aggFilter = new AggregationFilter(gaussFilter, 5, "Custom Filter: Aggregation");
171
172 // Plot the aggregation filter as a column chart
173 sciChartSurface.renderableSeries.add(
174 new FastColumnRenderableSeries(wasmContext, {
175 id: "col",
176 fill: appTheme.VividSkyBlue + "33",
177 stroke: appTheme.MutedSkyBlue,
178 dataSeries: aggFilter,
179 xAxisId: "aggX",
180 yAxisId: "aggY",
181 cornerRadius: 10,
182 })
183 );
184
185 let timerId: NodeJS.Timeout;
186
187 // Function called when the user clicks stopUpdate button
188 const stopUpdate = () => {
189 clearTimeout(timerId);
190 timerId = undefined;
191 lastX = 0;
192 };
193
194 // Function called when the user clicks startUpdate button
195 const startUpdate = () => {
196 if (timerId) {
197 stopUpdate();
198 dataSeries.clear();
199 }
200 const updateFunc = () => {
201 if (dataSeries.count() >= maxPoints) {
202 stopUpdate();
203 return;
204 }
205
206 // Get the next N random walk x,y values
207 const { xValues, yValues } = getData(numberOfPointsPerTimerTick);
208 // Append these to the dataSeries. This will cause the chart to redraw
209 dataSeries.appendRange(xValues, yValues);
210
211 timerId = setTimeout(updateFunc, timerInterval);
212 };
213
214 dataSeries.clear();
215
216 timerId = setTimeout(updateFunc, timerInterval);
217 };
218
219 sciChartSurface.chartModifiers.add(new LegendModifier());
220
221 return { wasmContext, sciChartSurface, controls: { startUpdate, stopUpdate } };
222};
223This example demonstrates advanced use of custom filters within the SciChart.js library integrated into an Angular standalone component. The purpose is to showcase real-time data transformations and aggregations by applying custom filtering algorithms to streaming data.
The application leverages Angular event binding with the SciChart Angular component to handle asynchronous chart initialization and real-time updates via the Angular lifecycle hooks onInit and onDelete as documented in Component Lifecycle - Angular. A custom aggregation filter is implemented by extending the SciChart.js XyFilterBase, which computes frequency distributions from data series. Additionally, a Gaussian randomization filter is applied using the XyCustomFilter functionality, aligning with the guidelines provided in the SciChart.js Custom Filters Documentation.
The example supports high-frequency real-time updates by using a timer-based mechanism to append data points to the series, triggering dynamic updates in the rendered line, scatter, and column charts. This real-time data management is optimized using WebAssembly acceleration, a technique further detailed in the Adding Realtime Updates | JavaScript Chart Documentation - SciChart.
Angular integration is achieved using the scichart-angular component, which simplifies embedding complex charts into Angular applications. The use of lifecycle hooks ensures that the chart initialization and cleanup are performed efficiently, following Angular best practices as described in Component Lifecycle - Angular. Developers are encouraged to review the Getting Started with SciChart JS guide for additional context on setting up high-performance charts and consult the Performance Tips & Tricks | JavaScript Chart Documentation for strategies on optimizing real-time updates in their applications.

Chart with Linear Trendline, Moving Average and Ratio Filters with filter chaining

How to use a ScaleOffsetFilter to convert data to a percentage change, with realtime updates, rescale on pan

Demonstrates how to add draggable thresholds which change the series color in the chart in SciChart.js