This demo uses imperative chart updates. The generated code is a faithful Angular translation: open it in CodeSandbox to run and tweak.
import { Component, AfterViewInit, OnDestroy, ViewChild } from '@angular/core';
import {
ChartComponent,
ApexAxisChartSeries,
ApexNonAxisChartSeries,
ApexChart,
ApexXAxis,
ApexYAxis,
ApexTitleSubtitle,
ApexDataLabels,
ApexStroke,
ApexFill,
ApexLegend,
ApexTooltip,
ApexMarkers,
ApexPlotOptions,
ApexResponsive,
ApexGrid,
ApexAnnotations,
ApexStates,
ApexTheme,
NgApexchartsModule,
} from 'ng-apexcharts';
export type ChartOptions = {
series?: ApexAxisChartSeries | ApexNonAxisChartSeries;
chart?: ApexChart;
xaxis?: ApexXAxis;
yaxis?: ApexYAxis | ApexYAxis[];
title?: ApexTitleSubtitle;
subtitle?: ApexTitleSubtitle;
dataLabels?: ApexDataLabels;
stroke?: ApexStroke;
fill?: ApexFill;
legend?: ApexLegend;
tooltip?: ApexTooltip;
markers?: ApexMarkers;
plotOptions?: ApexPlotOptions;
responsive?: ApexResponsive[];
grid?: ApexGrid;
annotations?: ApexAnnotations;
states?: ApexStates;
theme?: ApexTheme;
colors?: string[];
labels?: any;
};
@Component({
selector: 'app-chart',
standalone: true,
imports: [NgApexchartsModule],
templateUrl: './chart.component.html',
})
export class AppChart implements AfterViewInit, OnDestroy {
@ViewChild('chart') chart!: ChartComponent;
private g = (name: any, score: any, sales: any): any => {
return { name: name, value: score, z: sales }
};
private gameSeries: any = [
{
name: 'Action-Adventure',
data: [
this.g('Red Dead Redemption 2', 97, 55),
this.g('God of War', 94, 23),
this.g('Horizon Zero Dawn', 89, 24),
this.g('Marvel’s Spider-Man', 87, 20),
this.g('Uncharted 4', 93, 16),
this.g('Ghost of Tsushima', 83, 13),
this.g('The Last of Us Part II', 93, 10),
this.g('Assassin’s Creed Odyssey', 83, 12),
],
},
{
name: 'RPG',
data: [
this.g('Skyrim', 94, 60),
this.g('The Witcher 3', 93, 50),
this.g('Cyberpunk 2077', 86, 30),
this.g('Elden Ring', 96, 25),
this.g('Baldur’s Gate 3', 96, 15),
this.g('Dark Souls III', 89, 10),
this.g('Persona 5', 93, 9),
this.g('Final Fantasy VII Remake', 87, 7),
],
},
{
name: 'Racing',
data: [
this.g('Mario Kart 8 Deluxe', 92, 62),
this.g('Forza Horizon 5', 92, 45),
this.g('Forza Horizon 4', 92, 24),
this.g('Gran Turismo 7', 87, 12),
this.g('Burnout Paradise', 88, 8),
this.g('F1 23', 82, 5),
this.g('Need for Speed Heat', 72, 4),
this.g('Dirt Rally 2.0', 84, 3),
],
},
];
public chartOptions: Partial<ChartOptions> = {
series: this.gameSeries,
chart: {
id: 'gameBubbleSwarm',
type: 'unit',
height: 460,
fontFamily: 'inherit',
animations: {
enabled: true,
speed: 700,
},
},
colors: ['#2a78d6', '#eb6834', '#1baf7a'],
legend: {
position: 'bottom',
markers: { radius: 12 },
},
plotOptions: {
unit: {
layout: 'scatter',
scatter: {
orientation: 'horizontal',
spread: 'swarm',
sizeField: 'z',
sizeRange: [5, 26],
xMin: 70,
xMax: 100,
tickAmount: 6,
laneLabelWidth: 128,
xTitle: 'Metacritic score',
},
},
},
tooltip: {
enabled: true,
},
};
ngAfterViewInit() {
(window as any).ApexCharts.setLicense('APEX-eyJleHBpcnlEYXRlIjoiMjEyNi0wNy0wNCIsImlzc3VlRGF0ZSI6IjIwMjYtMDctMjgiLCJwbGFuIjoicHJlbWl1bSIsImRvbWFpbnMiOlsiYXBleGNoYXJ0cy5jb20iLCIxMjcuMC4wLjEiLCJsb2NhbGhvc3QiXSwic2lnIjoieVBmb1VCc0Z3TU9ZdUEyaEZkR0I2Y1FtZ0JITUtXcVdJSjB2NVRESXRZbFR3eDJMUmh6R2x0RUc3VXJ4X0s3b25ZMWRZb2Z2VGItN01ydFYyNDVyOWcifQ==');
}
ngOnDestroy() {
// no cleanup needed
}
}