Pie, Donut and Polar Area in Angular
Using ApexCharts with Angular
This Pie, Donut and Polar Area example wires ApexCharts into Angular through the ng-apexcharts component.
Install it with npm install ng-apexcharts apexcharts, then mount the chart with <apx-chart [series]="series" [chart]="chart"></apx-chart>.
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 CHANNELS: any = ['Organic', 'Paid search', 'Social', 'Email', 'Referral'];
private SESSIONS: any = [4200, 2600, 1900, 1100, 700];
private SESSIONS_BY_DEVICE: any = {
mobile: [2500, 1300, 1500, 400, 300],
desktop: [1400, 1100, 300, 600, 330],
tablet: [300, 200, 100, 100, 70],
};
private ORDERS: any = [300, 240, 160, 180, 120];
private ORDERS_BY_DEVICE: any = {
mobile: [140, 100, 100, 60, 45],
desktop: [130, 120, 45, 100, 60],
tablet: [30, 20, 15, 20, 15],
};
private ORDER_VALUE: any = [66, 85, 49, 76, 95];
private ORDER_VALUE_BY_DEVICE: any = {
mobile: [52, 70, 41, 60, 76],
desktop: [81, 98, 66, 84, 109],
tablet: [70, 85, 55, 80, 95],
};
private pickDevice = (device: any): any => {
document.querySelectorAll('[data-device]').forEach(function (b) {
b.classList.toggle('active', b.getAttribute('data-device') === device)
})
// A pick is an ordinary update; null clears it.
ApexCharts.getChartByID('sessions').updateOptions({
highlightFilter: { data: device ? this.SESSIONS_BY_DEVICE[device] : null },
})
ApexCharts.getChartByID('orders').updateOptions({
highlightFilter: { data: device ? this.ORDERS_BY_DEVICE[device] : null },
})
ApexCharts.getChartByID('order-value').updateOptions({
highlightFilter: { data: device ? this.ORDER_VALUE_BY_DEVICE[device] : null },
})
};
private wireDevices = (): any => {
document.querySelectorAll('[data-device]').forEach(function (b) {
b.addEventListener('click', function () {
this.pickDevice(b.getAttribute('data-device'))
})
})
};
public chartOptions: Partial<ChartOptions> = {
series: this.SESSIONS,
chart: {
id: 'sessions',
type: 'donut',
height: 360,
toolbar: { show: false },
},
labels: this.CHANNELS,
colors: ['#0284c7', '#0f766e', '#f59e0b', '#e11d48', '#64748b'],
plotOptions: {
pie: {
donut: {
size: '58%',
labels: {
show: true,
value: {
formatter: (v) => {
return Number(v).toLocaleString()
},
},
total: {
show: true,
label: 'Sessions',
formatter: (w) => {
return w.globals.seriesTotals
.reduce(function (a, b) {
return a + b
}, 0)
.toLocaleString()
},
},
},
},
},
},
// No slice borders: the faded tints alone keep the slices apart.
stroke: { width: 0 },
dataLabels: { enabled: true },
legend: { position: 'bottom' },
title: { text: 'Sessions by channel', style: { fontSize: '13px' } },
tooltip: {
y: {
formatter: (v) => {
return Number(v).toLocaleString()
},
},
},
highlightFilter: { data: this.SESSIONS_BY_DEVICE.mobile },
};
public chartOptions2: Partial<ChartOptions> = {
series: this.ORDERS,
chart: {
id: 'orders',
type: 'polarArea',
height: 360,
toolbar: { show: false },
},
labels: this.CHANNELS,
colors: ['#0284c7', '#0f766e', '#f59e0b', '#e11d48', '#64748b'],
fill: { opacity: 0.85 },
stroke: { width: 0 },
legend: { position: 'bottom' },
title: { text: 'Orders by channel', style: { fontSize: '13px' } },
highlightFilter: { data: this.ORDERS_BY_DEVICE.mobile },
};
public chartOptions3: Partial<ChartOptions> = {
series: this.ORDER_VALUE,
chart: {
id: 'order-value',
type: 'polarArea',
height: 360,
toolbar: { show: false },
},
labels: this.CHANNELS,
colors: ['#0284c7', '#0f766e', '#f59e0b', '#e11d48', '#64748b'],
fill: { opacity: 0.85 },
stroke: { width: 0 },
yaxis: {
labels: {
formatter: (v) => {
return '$' + v
},
},
},
legend: { position: 'bottom' },
title: {
text: 'Average order value by channel',
style: { fontSize: '13px' },
},
tooltip: {
y: {
formatter: (v) => {
return '$' + v
},
},
},
highlightFilter: { data: this.ORDER_VALUE_BY_DEVICE.mobile },
};
ngAfterViewInit() {
(window as any).ApexCharts.setLicense('APEX-eyJleHBpcnlEYXRlIjoiMjEyNi0wNy0wNCIsImlzc3VlRGF0ZSI6IjIwMjYtMDctMjgiLCJwbGFuIjoicHJlbWl1bSIsImRvbWFpbnMiOlsiYXBleGNoYXJ0cy5jb20iLCIxMjcuMC4wLjEiLCJsb2NhbGhvc3QiXSwic2lnIjoieVBmb1VCc0Z3TU9ZdUEyaEZkR0I2Y1FtZ0JITUtXcVdJSjB2NVRESXRZbFR3eDJMUmh6R2x0RUc3VXJ4X0s3b25ZMWRZb2Z2VGItN01ydFYyNDVyOWcifQ==');
this.wireDevices()
function pickDevice(device) {
document.querySelectorAll('[data-device]').forEach(function (b) {
b.classList.toggle('active', b.getAttribute('data-device') === device)
})
// A pick is an ordinary update; null clears it.
ApexCharts.getChartByID('sessions').updateOptions({
highlightFilter: { data: device ? this.SESSIONS_BY_DEVICE[device] : null },
})
ApexCharts.getChartByID('orders').updateOptions({
highlightFilter: { data: device ? this.ORDERS_BY_DEVICE[device] : null },
})
ApexCharts.getChartByID('order-value').updateOptions({
highlightFilter: { data: device ? this.ORDER_VALUE_BY_DEVICE[device] : null },
})
}
function wireDevices() {
document.querySelectorAll('[data-device]').forEach(function (b) {
b.addEventListener('click', () => {
this.pickDevice(b.getAttribute('data-device'))
})
})
}
}
ngOnDestroy() {
// no cleanup needed
}
}