Angular Animated Bar Chart Example

Creates a Angular Animated Bar Chart using SciChart.js that displays animated ATP Year-end Top Ten rankings from 1990 to 2024..

Fullscreen

Edit

 Edit

Docs

drawExample.ts

angular.ts

theme.ts

atp-rankings.ts

Copy to clipboard
Minimise
Fullscreen
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};
258

Animated Columns Chart - Angular

Overview

This 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.

Technical Implementation

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.

Features and Capabilities

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.

Integration and Best Practices

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.

SciChart Ltd, 16 Beaufort Court, Admirals Way, Docklands, London, E14 9XL.