Creates a React Polar Sunburst Chart using SciChart.js, with multiple levels and interaction driven animations.
Also Known As: Radial Treemap, Circular Treemap.
drawExample.ts
index.tsx
theme.ts
sunburstData.ts
SunburstMetadata.ts
SunburstPaletteProvider.ts
1import {
2 PolarColumnRenderableSeries,
3 PolarNumericAxis,
4 SciChartPolarSurface,
5 EPolarAxisMode,
6 NumberRange,
7 EAxisAlignment,
8 EPolarLabelMode,
9 PolarCursorModifier,
10 TCursorTooltipDataTemplate,
11 SeriesInfo,
12 PolarDataPointSelectionModifier,
13 TSciChart,
14 EColumnMode,
15 XyxDataSeries,
16 EMultiLineAlignment,
17 EColumnDataLabelPosition,
18 EDataPointWidthMode,
19 GenericAnimation,
20 easing,
21 DoubleAnimator,
22} from "scichart";
23import { appTheme } from "../../../theme";
24import { SunburstMetadata } from "./SunburstMetadata";
25import { SunburstPaletteProvider } from "./SunburstPaletteProvider";
26import { getDataById, getElementById, TLevelDataForChart } from "./sunburstData";
27
28const drawSeriesFn = (
29 wasmContext: TSciChart,
30 xAxis: PolarNumericAxis,
31 yAxis: PolarNumericAxis,
32 sciChartSurface: SciChartPolarSurface,
33 polarLabelMode: EPolarLabelMode,
34 dataPointSelectionModifier: PolarDataPointSelectionModifier,
35 nodeId: number[],
36 selectedElStartX: number,
37 prevNodeId: number[]
38) => {
39 const startAngleDefault = -Math.PI / 2;
40
41 const clearSeriesFn = () => {
42 dataPointSelectionModifier.selectionChanged.unsubscribeAll();
43 sciChartSurface.renderableSeries.asArray().forEach((rs) => rs.delete());
44 sciChartSurface.renderableSeries.clear();
45 };
46
47 const level = nodeId.length - 1;
48 const prevLevel = prevNodeId.length - 1;
49 const prevXVisRangeDiff = xAxis.visibleRange.diff;
50
51 const polarColumnMode = EColumnMode.StartEnd;
52 const dataPointWidth = 0.5;
53 const paletteProvider = new SunburstPaletteProvider();
54
55 const createDataFn = (input$: TLevelDataForChart) => {
56 const [xValues$, yValues$, x1Values$] = input$.data;
57 return new XyxDataSeries(wasmContext, {
58 xValues: xValues$,
59 yValues: yValues$,
60 x1Values: x1Values$,
61 metadata: input$.metadata,
62 });
63 };
64
65 const levelData = getDataById(nodeId);
66 const rootNode = getElementById(nodeId);
67
68 const redrawSeriesFn = () => {
69 clearSeriesFn();
70 for (let i = 0; i < levelData.length; i++) {
71 const rs$ = new PolarColumnRenderableSeries(wasmContext, {
72 stroke: "black",
73 // Without an explicit fill the series inherits the theme's fillPalette entry, whose alpha
74 // (0x77 in the light theme) scales the colours the SunburstPaletteProvider returns and
75 // washes the sectors out. Any opaque fill works - the palette provider replaces it per point.
76 fill: "#FFFFFFFF",
77 columnXMode: polarColumnMode,
78 dataLabels: {
79 style: {
80 fontSize: 16,
81 multiLineAlignment: EMultiLineAlignment.Center,
82 lineSpacing: 8,
83 },
84 color: "black",
85 precision: 2,
86 pointGapThreshold: 0,
87 skipNumber: 0,
88 polarLabelMode,
89 labelYPositionMode: EColumnDataLabelPosition.Position,
90 labelYPositionMultiplier: 0.5,
91 metaDataSelector: (metadata) => {
92 const md = metadata as SunburstMetadata;
93 return `${md.title} \n ${md.value}`;
94 },
95 },
96 dataSeries: createDataFn(levelData[i]),
97 strokeThickness: 2,
98 dataPointWidth,
99 dataPointWidthMode: EDataPointWidthMode.Range,
100 defaultY1: i,
101 paletteProvider: paletteProvider,
102 });
103 dataPointSelectionModifier.includeSeries(rs$, true);
104 sciChartSurface.renderableSeries.add(rs$);
105 }
106 };
107
108 const drillDownAnimationFn = (isReverse$: boolean, onCompleteFn$: () => void = () => undefined) => {
109 const xMin$ = 0; // always zero
110 const xMax0$ = prevXVisRangeDiff;
111 const xMax1$ = rootNode.value;
112
113 const startAngle0$ = isReverse$
114 ? startAngleDefault
115 : startAngleDefault + (2 * Math.PI * selectedElStartX) / xAxis.visibleRange.diff;
116 let startAngle1$: number;
117 if (isReverse$) {
118 const levelDiff$ = prevLevel - level;
119 const element$ = levelData[levelDiff$].metadata.find((a) => a.id.toString() === prevNodeId.toString());
120 startAngle1$ = startAngleDefault + (2 * Math.PI * element$.start) / rootNode.value;
121 } else {
122 startAngle1$ = startAngleDefault;
123 }
124
125 const yMin0$ = isReverse$ ? 0 : prevLevel - level;
126 const yMin1$ = isReverse$ ? level - prevLevel : 0;
127
128 const from$ = { x1: xMax0$, x2: startAngle0$, y1: yMin0$, y2: 0 };
129 const to$ = { x1: xMax1$, x2: startAngle1$, y1: yMin1$, y2: 0 };
130
131 const sweepAnimation = new GenericAnimation({
132 from: from$,
133 to: to$,
134 duration: 1000,
135 ease: easing.inOutSine,
136 onAnimate: (from, to, progress) => {
137 const xMaxCur = DoubleAnimator.interpolate(from.x1, to.x1, progress);
138 const startAngleCur = DoubleAnimator.interpolate(from.x2, to.x2, progress);
139 const yMinCur = DoubleAnimator.interpolate(from.y1, to.y1, progress);
140 const yMaxCur = yMinCur + 4;
141 xAxis.visibleRange = new NumberRange(xMin$, xMaxCur);
142 xAxis.startAngle = startAngleCur;
143 yAxis.visibleRange = new NumberRange(yMinCur, yMaxCur);
144 },
145 onCompleted: () => {
146 onCompleteFn$();
147 },
148 });
149 sciChartSurface.addAnimation(sweepAnimation);
150 };
151
152 const subscribeFn = () => {
153 dataPointSelectionModifier.selectionChanged.subscribe((args) => {
154 const selectedDataPoint = args.selectedDataPoints[0];
155 if (selectedDataPoint) {
156 const { yValue } = selectedDataPoint;
157 const md = selectedDataPoint?.metadata as SunburstMetadata;
158 const { id } = md;
159 const newId = [...id];
160 if (yValue === 1 && id.length > 1) {
161 newId.pop();
162 }
163 drawSeriesFn(
164 wasmContext,
165 xAxis,
166 yAxis,
167 sciChartSurface,
168 polarLabelMode,
169 dataPointSelectionModifier,
170 newId,
171 md.start,
172 nodeId
173 );
174 }
175 });
176 };
177
178 if (level < prevLevel) {
179 drillDownAnimationFn(true, () => {
180 redrawSeriesFn();
181 xAxis.startAngle = startAngleDefault;
182 yAxis.visibleRange = new NumberRange(0, 4);
183 subscribeFn();
184 });
185 } else if (level > prevLevel) {
186 redrawSeriesFn();
187 drillDownAnimationFn(false);
188 subscribeFn();
189 } else {
190 xAxis.startAngle = startAngleDefault;
191 xAxis.visibleRange = new NumberRange(0, rootNode.value);
192 yAxis.visibleRange = new NumberRange(0, 4);
193 redrawSeriesFn();
194 subscribeFn();
195 }
196};
197
198export const drawExample = async (rootElement: string | HTMLDivElement) => {
199 const { sciChartSurface, wasmContext } = await SciChartPolarSurface.create(rootElement, {
200 theme: appTheme.SciChartJsTheme,
201 });
202
203 const startAngle = -Math.PI / 2;
204 const totalAngle = 2 * Math.PI;
205
206 const xAxis = new PolarNumericAxis(wasmContext, {
207 isVisible: false,
208 polarAxisMode: EPolarAxisMode.Angular,
209 axisAlignment: EAxisAlignment.Top,
210 visibleRange: new NumberRange(0, 65),
211 startAngle,
212 totalAngle,
213 });
214 xAxis.polarLabelMode = EPolarLabelMode.Parallel;
215 sciChartSurface.xAxes.add(xAxis);
216
217 const yAxis = new PolarNumericAxis(wasmContext, {
218 isVisible: false,
219 polarAxisMode: EPolarAxisMode.Radial,
220 axisAlignment: EAxisAlignment.Right,
221 visibleRange: new NumberRange(0, 6),
222 flippedCoordinates: false,
223 startAngle,
224 totalAngle,
225 });
226 sciChartSurface.yAxes.add(yAxis);
227
228 const dataPointSelectionModifier = new PolarDataPointSelectionModifier({
229 allowClickSelect: true,
230 allowDragSelect: false,
231 selectionStroke: "red",
232 selectionFill: "#ff879f33",
233 });
234
235 drawSeriesFn(
236 wasmContext,
237 xAxis,
238 yAxis,
239 sciChartSurface,
240 EPolarLabelMode.Parallel,
241 dataPointSelectionModifier,
242 [0],
243 0,
244 [0]
245 );
246
247 const tooltipDataTemplate: TCursorTooltipDataTemplate = (seriesInfos: SeriesInfo[], tooltipTitle: string) => {
248 const res: string[] = [];
249 seriesInfos.forEach((si) => {
250 if (si.isHit) {
251 const md = si.pointMetadata as SunburstMetadata;
252 res.push(`Name: ${md.title}`);
253 res.push(`Value: ${md.value}`);
254 }
255 });
256 return res;
257 };
258
259 sciChartSurface.chartModifiers.add(
260 dataPointSelectionModifier,
261 new PolarCursorModifier({
262 showTooltip: true,
263 showCircularLine: false,
264 showRadialLine: false,
265 tooltipDataTemplate,
266 })
267 );
268
269 return { sciChartSurface, wasmContext };
270};
271This React Charts example demonstrates how to integrate SciChart.js's Polar Sunburst Chart using the SciChart React component.
The implementation uses React hooks pattern with the SciChartReact wrapper component. The core chart logic in drawExample.ts creates a SciChartPolarSurface and configures polar axes. The recursive drawSeriesFn handles dynamic series generation for each hierarchy level.
The chart features smooth animated transitions between levels using GenericAnimation. Interactive selection is implemented via PolarCursorModifier with custom tooltips. The SunburstPaletteProvider dynamically updates segment colors.
The example follows React best practices by encapsulating chart logic in a separate module. The SciChartReact component handles proper cleanup of WebAssembly resources when unmounting.

Explore the React Polar Line Chart example to create data labels, line interpolation, gradient palette stroke and startup animations. Try the SciChart Demo.

Try the React Polar Spline Line Chart example to see SciChart's GPU-accelerated rendering in action. Choose a cubic spline or polar interpolation. View demo.

Create a React Multi-Cycle Polar Chart to plot data over multiple cycles and visualize patterns over time. This example shows surface temperature by month.

Try the React Polar Bar Chart example to render bars in a polar layout with gradient fills and animations. Use SciChart for seamless integration with React.

Create a React Polar Colum Category chart visualizing UK consumer price changes. Try the demo with a custom positive/negative threshold fill and stroke.

Create a React Polar Range Column Chart with SciChart. This example displays monthly minimum and maximum temperatures within a Polar layout. Try the demo.

View the React Windrose Chart example to display directional data with stacked columns in a polar layout. Try the polar chart demo with customizable labels.

View the React Radial Column Chart example to see the difference that SciChart has to offer. Switch radial and angular axes and add interactive modifiers.

This React Stacked Radial Bar Chart example shows Olympic medal data by country. Try the demo for yourself with async initialization and theme application.

The React Polar Area Chart example, also known as Nightingale Rose Chart, renders an area series with polar coordinates with interactive legend controls.

Try the React Stacked Radial Mountain Chart example to show multiple datasets on a polar layout with a stacked mountain series and animated transitions.

Create a React Polar Chart with regular and interpolated error bands. Enhance a standard chart with shaded areas to show upper and lower data boundaries.

Build a React Polar Scatter Chart with this example to render multiple scatter series on radial and angular axes. Try the flexible SciChart demo today.

View the React Polar Radar Chart example. Also known as the Spider Radar Chart, view the scalability and stability that SciChart has to offer. Try demo.

Create React Gauge Charts, including a React Circular Gauge Dashboard, with React-friendly initialization and responsive design. Give the SciChart demo a go.

View React Arc Gauge Charts alongside FIFO Scrolling Charts, all on the same dashboard with real-time, high-performance data rendering. Try the demo.

Try SciChart's React Polar Heatmap example to combine a polar heatmap with a legend component. Supports responsive design and chart and legend separation.

No description available for this example yet

Create a React Polar Partial Arc that bends from a full Polar Circle to a Cartesian-like arc. Try the demo to display an arc segment with Polar coordinates.

Create a React Polar Axis Label with SciChart. This demo shows the various label modes for Polar Axes – all optimised for pan, zoom, and mouse wheel.

View the React Polar Map Example using the SciChartReact component. Display geographic data as color-coded triangles on a polar coordinate system. Try demo.