Demonstrates the Decoupled Render Loop. Even with 1 Million Points and a complex Gradient Palette causing frame drops on the main chart, the SVG Cursor remains smooth at 60 FPS because it does not trigger a WebGL redraw.
drawExample.ts
index.tsx
theme.ts
1import {
2 SciChartSurface,
3 NumericAxis,
4 XyDataSeries,
5 XyScatterRenderableSeries,
6 EllipsePointMarker,
7 CursorModifier,
8 ZoomExtentsModifier,
9 MouseWheelZoomModifier,
10 DefaultPaletteProvider,
11 IPointMarkerPaletteProvider,
12 parseColorToUIntArgb,
13 RolloverModifier,
14 ENumericFormat,
15 NumberRange,
16 ChartModifierBase2D,
17 XySeriesInfo,
18 EXyDirection,
19} from "scichart";
20import { appTheme } from "../../../theme";
21
22// Helper to extract RGB channels from a UInt ARGB color
23const extractColorChannels = (uintColor: number) => {
24 return {
25 a: (uintColor >> 24) & 0xff,
26 r: (uintColor >> 16) & 0xff,
27 g: (uintColor >> 8) & 0xff,
28 b: uintColor & 0xff,
29 };
30};
31
32class GradientPaletteProvider extends DefaultPaletteProvider implements IPointMarkerPaletteProvider {
33 private readonly minY: number;
34 private readonly maxY: number;
35
36 // Color Channel Data
37 private startColor: { r: number; g: number; b: number; a: number };
38 private endColor: { r: number; g: number; b: number; a: number };
39
40 constructor(minY: number, maxY: number, startHex: string, endHex: string) {
41 super();
42 this.minY = minY;
43 this.maxY = maxY;
44
45 // Pre-calculate color channels to avoid expensive parsing per point
46 this.startColor = extractColorChannels(parseColorToUIntArgb(startHex));
47 this.endColor = extractColorChannels(parseColorToUIntArgb(endHex));
48 }
49
50 public override overridePointMarkerArgb(xValue: number, yValue: number, index: number) {
51 // Calculate the fraction (0 to 1) of the current Y value within the range
52 let fraction = (yValue - this.minY) / (this.maxY - this.minY);
53 if (fraction < 0) fraction = 0;
54 if (fraction > 1) fraction = 1;
55
56 // Lerp
57 const r = Math.floor(this.startColor.r + (this.endColor.r - this.startColor.r) * fraction);
58 const g = Math.floor(this.startColor.g + (this.endColor.g - this.startColor.g) * fraction);
59 const b = Math.floor(this.startColor.b + (this.endColor.b - this.startColor.b) * fraction);
60
61 // Reassemble into UInt ARGB
62 const colorUint = (255 << 24) | (r << 16) | (g << 8) | b;
63
64 return {
65 stroke: colorUint,
66 fill: colorUint,
67 };
68 }
69}
70
71export const drawExample = async (rootElement: string | HTMLDivElement) => {
72 const { wasmContext, sciChartSurface } = await SciChartSurface.create(rootElement, {
73 theme: appTheme.SciChartJsTheme,
74 });
75
76 sciChartSurface.xAxes.add(
77 new NumericAxis(wasmContext, {
78 labelFormat: ENumericFormat.Engineering,
79 })
80 );
81 sciChartSurface.yAxes.add(
82 new NumericAxis(wasmContext, {
83 growBy: new NumberRange(0.01, 0.01),
84 })
85 );
86
87 // 1. Data Generation (Bounded Cloud)
88 const count = 1_000_000;
89 const xValues = new Float64Array(count);
90 const yValues = new Float64Array(count);
91
92 const Y_MIN = -500;
93 const Y_MAX = 500;
94
95 for (let i = 0; i < count; i++) {
96 xValues[i] = i;
97 yValues[i] = Y_MIN + Math.random() * (Y_MAX - Y_MIN);
98 }
99
100 // 2. Create Scatter Series with Gradient Palette
101 const scatterSeries = new XyScatterRenderableSeries(wasmContext, {
102 dataSeries: new XyDataSeries(wasmContext, {
103 xValues,
104 yValues,
105 containsNaN: false,
106 isSorted: true,
107 dataSeriesName: "Bounded Cloud Series",
108 }),
109 pointMarker: new EllipsePointMarker(wasmContext, {
110 width: 1,
111 height: 1,
112 strokeThickness: 0.5,
113 }),
114 paletteProvider: new GradientPaletteProvider(
115 Y_MIN,
116 Y_MAX,
117 appTheme.Indigo, // Start (Low Y)
118 appTheme.VividOrange // End (High Y)
119 ),
120 });
121 sciChartSurface.renderableSeries.add(scatterSeries);
122
123 sciChartSurface.chartModifiers.add(
124 new ZoomExtentsModifier(),
125 new MouseWheelZoomModifier({
126 xyDirection: EXyDirection.XDirection,
127 })
128 );
129
130 // 3. State Management for Tool Modifiers
131 let activeModifier: ChartModifierBase2D | null = null;
132 let isCursorMode = true;
133 let isSvgMode = true;
134
135 const rebuildActiveModifier = () => {
136 if (activeModifier) {
137 sciChartSurface.chartModifiers.remove(activeModifier);
138 activeModifier.delete();
139 activeModifier = null;
140 }
141
142 const accentColor = "#5e7dba";
143
144 if (isCursorMode) {
145 activeModifier = new CursorModifier({
146 isSvgOnly: isSvgMode,
147 showTooltip: true,
148 crosshairStroke: accentColor,
149 axisLabelFill: accentColor,
150 tooltipContainerBackground: accentColor,
151 });
152 } else {
153 activeModifier = new RolloverModifier({
154 isSvgOnly: isSvgMode,
155 showTooltip: true,
156 showAxisLabel: true,
157 rolloverLineStroke: accentColor,
158 tooltipDataTemplate: (seriesInfo: XySeriesInfo): string[] => {
159 const valuesWithLabels: string[] = [];
160 const xySeriesInfo = seriesInfo as XySeriesInfo;
161 valuesWithLabels.push(`X: ${xySeriesInfo.formattedXValue}`);
162 valuesWithLabels.push(`Y: ${xySeriesInfo.formattedYValue}`);
163 return valuesWithLabels;
164 },
165 });
166 (activeModifier as RolloverModifier).rolloverLineAnnotation.axisLabelFill = accentColor;
167 }
168
169 sciChartSurface.chartModifiers.add(activeModifier);
170 };
171 rebuildActiveModifier();
172
173 // 4. Control Functions
174 const setSvgMode = (useSvg: boolean) => {
175 isSvgMode = useSvg;
176 rebuildActiveModifier();
177 };
178 const toggleUseCursorOrRollover = (useCursor: boolean) => {
179 isCursorMode = useCursor;
180 rebuildActiveModifier();
181 };
182
183 return {
184 sciChartSurface,
185 controls: {
186 setSvgMode,
187 toggleUseCursorOrRollover,
188 },
189 };
190};
191When rendering massive datasets (like the 1 Million Point Scatter chart in this demo), the WebGL rendering engine may drop below 60 FPS due to the sheer volume of data and pixel shading required. We have made it possible to use paletteProvider with much bigger datasets while retaining performance. And even if you push this to its limit, you can still get smooth tooltip behaviour thanks to SVG cursor.
In standard charting libraries, the Cursor or Tooltip is often part of the main render loop. This means if the chart draws at 15 FPS, your mouse cursor lags at 15 FPS.
SciChart.js solves this with a Decoupled Render Loop.
This demo allows you to toggle between isSvgOnly: true (SVG Mode) and isSvgOnly: false (Native Mode) on the Cursor and Rollover Modifiers.
Result: The chart might be static or updating slowly, but your interaction remains buttery smooth at 60 FPS.
The key property is isSvgOnly on the modifier options:
new CursorModifier({
isSvgOnly: true, // Independent render loop
showTooltip: true,
// ...
});
We also implement a custom GradientPaletteProvider which calculates color per-point based on Y-Value. This simulates a "heavy" rendering load to better illustrate the performance benefits of the SVG cursor.

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

This demo showcases the incredible performance of our Angular Chart by loading 500 series with 500 points (250k points) instantly!

This demo showcases the incredible performance of our JavaScript Chart by loading a million points instantly.

This demo showcases the realtime performance of our Angular Chart by animating several series with thousands of data-points at 60 FPS

See the frequency of recordings with the Angular audio spectrum analyzer example from SciChart. This real-time audio visualizer demo uses a Fourier Transform.

Demonstrates how to create Oil and Gas Dashboard

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

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

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

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

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

Population Pyramid of Europe and Africa