Creates a Angular Animated Bar Chart using SciChart.js that displays animated ATP Year-end Top Ten rankings from 1990 to 2024..
drawExample.ts
angular.ts
theme.ts
atp-rankings.ts
1import {
2 SciChartSurface,
3 NumericAxis,
4 FastRectangleRenderableSeries,
5 EColumnMode,
6 EColumnYMode,
7 Thickness,
8 EAxisAlignment,
9 ETextAlignment,
10 ETitlePosition,
11 IPointMetadata,
12 easing,
13 EDataLabelSkipMode,
14 EVerticalTextPosition,
15 EHorizontalTextPosition,
16 NumberRange,
17 XyxDataSeries,
18 ColumnAnimation,
19 XyDataSeries,
20 DefaultPaletteProvider,
21 TSciChart,
22 parseColorToUIntArgb,
23} from "scichart";
24
25import { appTheme } from "../../../theme";
26import { data } from "./atp-rankings";
27
28type ATPMetadata = IPointMetadata & {
29 rank: number;
30 name: string;
31 country: string;
32};
33class CountryPaletteProvider extends DefaultPaletteProvider {
34 private colorMap: Map<string, { stroke: number; fill: number }> = new Map<
35 string,
36 { stroke: number; fill: number }
37 >();
38
39 constructor(wasmContext: TSciChart) {
40 super();
41 const countries = [
42 "SE",
43 "CS",
44 "US",
45 "EC",
46 "AT",
47 "HR",
48 "NL",
49 "UA",
50 "RU",
51 "ZA",
52 "AU",
53 "GB",
54 "CL",
55 "SK",
56 "BR",
57 "CH",
58 "CZ",
59 "AR",
60 "RS",
61 "JP",
62 "ES",
63 "YU",
64 "FR",
65 "CA",
66 "BG",
67 "BE",
68 "GR",
69 "IT",
70 "NO",
71 "DE",
72 "PL",
73 "DK",
74 ];
75 const max = countries.length - 1;
76 for (let i = 0; i < countries.length; i++) {
77 const country = countries[i];
78 const stroke = parseColorToUIntArgb(appTheme.SciChartJsTheme.getStrokeColor(i, max, wasmContext));
79 const fill = parseColorToUIntArgb(appTheme.SciChartJsTheme.getFillColor(i, max, wasmContext));
80 this.colorMap.set(country, { stroke, fill });
81 }
82 }
83
84 public overrideStrokeArgb(
85 xValue: number,
86 yValue: number,
87 index: number,
88 opacity?: number,
89 metadata?: IPointMetadata
90 ): number | undefined {
91 const country = (metadata as ATPMetadata).country;
92 return this.colorMap.get(country).stroke;
93 }
94
95 public overrideFillArgb(
96 xValue: number,
97 yValue: number,
98 index: number,
99 opacity?: number,
100 metadata?: IPointMetadata
101 ): number | undefined {
102 const country = (metadata as ATPMetadata).country;
103 return this.colorMap.get(country).fill;
104 }
105}
106
107export const drawExample = async (rootElement: string | HTMLDivElement) => {
108 const { sciChartSurface, wasmContext } = await SciChartSurface.create(rootElement, {
109 theme: appTheme.SciChartJsTheme,
110 });
111
112 // Setup axes
113 sciChartSurface.xAxes.add(
114 new NumericAxis(wasmContext, {
115 visibleRange: new NumberRange(0, 15),
116 isVisible: false,
117 })
118 );
119
120 sciChartSurface.yAxes.add(
121 new NumericAxis(wasmContext, {
122 visibleRange: new NumberRange(0.5, 10.5),
123 axisTitle: "Rank",
124 axisTitleStyle: {
125 fontSize: 18,
126 },
127 axisAlignment: EAxisAlignment.Left,
128 drawMajorBands: false,
129 drawLabels: true,
130 drawMinorGridLines: false,
131 drawMajorGridLines: false,
132 drawMinorTickLines: false,
133 drawMajorTickLines: false,
134 keepLabelsWithinAxis: false,
135 autoTicks: false,
136 flippedCoordinates: true,
137 majorDelta: 1,
138 labelPrecision: 0,
139 labelStyle: {
140 fontSize: 22,
141 fontFamily: "Arial",
142 },
143 })
144 );
145
146 sciChartSurface.yAxes.get(0).axisRenderer.hideOverlappingLabels = false;
147
148 sciChartSurface.title = [`ATP Year-end Top 10 in ${data[0].year.toString()}`];
149
150 const topMargin = 30;
151 const rightMargin = 30;
152 const bottomMargin = 30;
153 const leftMargin = 30;
154
155 sciChartSurface.padding = new Thickness(topMargin, rightMargin, bottomMargin, leftMargin);
156
157 const xValues: number[] = [];
158 const x1Values: number[] = [];
159 const yValues: number[] = [];
160 const metadata: ATPMetadata[] = [];
161
162 for (const element of data[0].top10) {
163 xValues.push(0);
164 yValues.push(element.rank);
165 x1Values.push(16 - element.rank);
166 metadata.push({ isSelected: false, ...element });
167 }
168
169 // setup data
170 const dataSeriesA = new XyxDataSeries(wasmContext, { xValues, yValues, x1Values, metadata });
171 const dataSeriesB = new XyxDataSeries(wasmContext);
172
173 const rectangleSeries = new FastRectangleRenderableSeries(wasmContext, {
174 dataSeries: dataSeriesA,
175 columnXMode: EColumnMode.StartEnd,
176 columnYMode: EColumnYMode.CenterHeight,
177 defaultY1: 1,
178 strokeThickness: 4,
179 opacity: 0.3,
180 paletteProvider: new CountryPaletteProvider(wasmContext),
181 dataLabels: {
182 skipMode: EDataLabelSkipMode.ShowAll,
183 verticalTextPosition: EVerticalTextPosition.Center,
184 horizontalTextPosition: EHorizontalTextPosition.Center,
185 style: {
186 fontFamily: "Arial",
187 fontSize: 16,
188 },
189 color: appTheme.ForegroundColor,
190 metaDataSelector: (md) => {
191 const metadata = md as ATPMetadata;
192 return `${metadata.name.toString()} (${metadata.country.toString()})`;
193 },
194 //updateTextInAnimation: true
195 },
196 });
197 sciChartSurface.renderableSeries.add(rectangleSeries);
198 // Setup animations
199
200 const updateData = (i: number, curDataSeries: XyxDataSeries, nextDataSeries: XyxDataSeries) => {
201 sciChartSurface.title = [`ATP Year-end Top 10 in ${data[i].year.toString()}`];
202 nextDataSeries.clear();
203 const cur: ATPMetadata[] = [];
204 const next = data[i].top10;
205 // Series animations work by animating values at the same index, so it is important to preserve the order of entries, which may be totally unrelated to the display order
206 for (let p = 0; p < curDataSeries.count(); p++) {
207 // Look at all existing entries
208 const e = curDataSeries.getMetadataAt(p) as ATPMetadata;
209 // see if they should still be on the chart in the next period
210 const eNext = next.find((n) => n.name === e.name);
211 if (eNext) {
212 // Add to next data with new value
213 nextDataSeries.append(0, eNext.rank, 16 - eNext.rank, { isSelected: false, ...eNext });
214 } else {
215 if (curDataSeries.getNativeYValues().get(p) > 0) {
216 // If they are currently in view, set them to be out of view in next period
217 nextDataSeries.append(0, 12, 0, e);
218 }
219 }
220 // track all the current entries
221 cur.push(e);
222 }
223 for (const element of next) {
224 // Find entries that are completely new
225 const isNew = cur.find((e) => e.name === element.name) === undefined;
226 if (isNew) {
227 // add out of view in current data, and with new value in next data
228 curDataSeries.append(0, 12, 0, { isSelected: false, ...element });
229 nextDataSeries.append(0, element.rank, 16 - element.rank, { isSelected: false, ...element });
230 }
231 }
232 //Create an animation which will call the update for the following period when it completes
233 const animation = new ColumnAnimation({
234 duration: 1000,
235 ease: easing.inOutQuart,
236 dataSeries: nextDataSeries as any as XyDataSeries,
237 onCompleted: () => {
238 if (i < data.length - 2) {
239 updateData(i + 1, curDataSeries, nextDataSeries);
240 }
241 },
242 });
243 rectangleSeries.runAnimation(animation);
244 };
245
246 sciChartSurface.titleStyle = {
247 color: appTheme.ForegroundColor,
248 fontSize: 30,
249 alignment: ETextAlignment.Center,
250 position: ETitlePosition.Top,
251 placeWithinChart: false,
252 padding: Thickness.fromString("40 0 0 0"),
253 };
254 updateData(1, dataSeriesA, dataSeriesB);
255
256 return { sciChartSurface, wasmContext };
257};
258This Angular standalone component demonstrates an animated tennis rankings visualization using SciChart.js. The implementation uses the scichart-angular package for seamless integration with Angular's component system.
The chart is configured through the drawExample function passed to the ScichartAngularComponent. It creates a SciChartSurface with a FastRectangleRenderableSeries using EColumnYMode.CenterHeight for proper column sizing.
The component animates yearly ranking changes with smooth transitions using ColumnAnimation. A custom palette provider colors columns by player nationality, and data labels display player information. The flipped Y-axis correctly represents ranking positions.
The example follows Angular best practices by using standalone components and proper dependency management. The scichart-angular wrapper handles chart lifecycle management, including WebAssembly context creation and cleanup.

Demonstrates how to run Dataset Animations with JavaScript.

Demonstrates how to run Style Transition Animations with JavaScript.

Demonstrates how to run Startup Animations with JavaScript.

Demonstrates how to run Generic Animation with JavaScript.