Gauges and Radial Bars in Angular
Using ApexCharts with Angular
This Gauges and Radial Bars 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 TARGET: any = 12000;
private SHIPPED: any = 9300;
private SHIPPED_BY_REGION: any = { americas: 4100, europe: 3200, apac: 2000 };
private TEAMS: any = ['Sales', 'Support', 'Product'];
private GOALS: any = [72, 58, 85];
private GOALS_BY_REGION: any = {
americas: [32, 20, 38],
europe: [24, 22, 27],
apac: [16, 16, 20],
};
private SATISFACTION: any = 78;
private SATISFACTION_BY_REGION: any = { americas: 84, europe: 71, apac: 66 };
private SAME_DAY: any = 64;
private SAME_DAY_BY_REGION: any = { americas: 71, europe: 58, apac: 49 };
private pickRegion = (region: any): any => {
document.querySelectorAll('[data-region]').forEach(function (b) {
b.classList.toggle('active', b.getAttribute('data-region') === region)
})
ApexCharts.getChartByID('shipped').updateOptions({
highlightFilter: { data: region ? [this.SHIPPED_BY_REGION[region]] : null },
})
ApexCharts.getChartByID('goals').updateOptions({
highlightFilter: { data: region ? this.GOALS_BY_REGION[region] : null },
})
ApexCharts.getChartByID('satisfaction').updateOptions({
highlightFilter: { data: region ? [this.SATISFACTION_BY_REGION[region]] : null },
})
ApexCharts.getChartByID('same-day').updateOptions({
highlightFilter: { data: region ? [this.SAME_DAY_BY_REGION[region]] : null },
})
};
private wireRegions = (): any => {
document.querySelectorAll('[data-region]').forEach(function (b) {
b.addEventListener('click', function () {
this.pickRegion(b.getAttribute('data-region'))
})
})
};
public chartOptions: Partial<ChartOptions> = {
series: [this.SHIPPED],
chart: {
id: 'shipped',
type: 'gauge',
height: 300,
toolbar: { show: false },
},
labels: ['Orders shipped'],
colors: ['#0284c7'],
plotOptions: {
radialBar: {
// A count on its own domain: 0 to the month's target.
min: 0,
max: this.TARGET,
dataLabels: {
value: {
fontSize: '28px',
formatter: (v) => {
return Number(v).toLocaleString()
},
},
},
},
},
tooltip: {
y: {
formatter: (v) => {
return Number(v).toLocaleString()
},
},
},
title: {
text: 'Orders shipped this month, target 12,000',
style: { fontSize: '13px' },
},
highlightFilter: { data: [this.SHIPPED_BY_REGION.americas] },
};
public chartOptions2: Partial<ChartOptions> = {
series: this.GOALS,
chart: {
id: 'goals',
type: 'radialBar',
height: 340,
toolbar: { show: false },
},
labels: this.TEAMS,
colors: ['#0284c7', '#f59e0b', '#e11d48'],
plotOptions: {
radialBar: {
hollow: { size: '38%' },
track: { margin: 4 },
dataLabels: {
name: { fontSize: '13px' },
value: { fontSize: '20px' },
total: {
show: true,
label: 'Average',
// While a region is picked the totals read its parts (the default
// highlightFilter.dataLabels.total: 'part'), so this averages the
// region's contributions; cleared, the teams' progress.
formatter: (w) => {
var t = w.globals.seriesTotals
return (
Math.round(
t.reduce(function (a, b) {
return a + b
}, 0) / t.length,
) + '%'
)
},
},
},
},
},
legend: { show: true, position: 'bottom' },
title: {
text: 'Quarterly goal reached, by team',
style: { fontSize: '13px' },
},
highlightFilter: { data: this.GOALS_BY_REGION.americas },
};
public chartOptions3: Partial<ChartOptions> = {
series: [this.SATISFACTION],
chart: {
id: 'satisfaction',
type: 'gauge',
height: 300,
toolbar: { show: false },
},
labels: ['Satisfaction'],
colors: ['#0f766e'],
plotOptions: {
radialBar: {
dataLabels: {
value: {
fontSize: '28px',
// The centre reads the region; while one is picked, a second line
// under it reads the whole (opts.lane is 'whole' for that line).
formatter: (v, w, opts) => {
return (opts && opts.lane === 'whole' ? 'company ' : '') + v + '%'
},
},
},
},
},
title: { text: 'Customer satisfaction', style: { fontSize: '13px' } },
highlightFilter: {
data: [this.SATISFACTION_BY_REGION.americas],
// Made for a part that can pass its whole: two lanes, never overlapping.
radialBar: { indicator: 'lanes' },
},
};
public chartOptions4: Partial<ChartOptions> = {
series: [this.SAME_DAY],
chart: {
id: 'same-day',
type: 'gauge',
height: 300,
toolbar: { show: false },
},
labels: ['Same-day resolution'],
colors: ['#ea580c'],
plotOptions: {
radialBar: {
shape: 'needle',
needle: { length: '78%' },
ticks: {
show: true,
major: { count: 6, length: 6 },
minor: { count: 1, length: 3 },
labels: { show: true, fontSize: '10px' },
},
dataLabels: { value: { fontSize: '22px' } },
},
},
title: { text: 'Tickets resolved the same day', style: { fontSize: '13px' } },
highlightFilter: { data: [this.SAME_DAY_BY_REGION.americas] },
};
ngAfterViewInit() {
(window as any).ApexCharts.setLicense('APEX-eyJleHBpcnlEYXRlIjoiMjEyNi0wNy0wNCIsImlzc3VlRGF0ZSI6IjIwMjYtMDctMjgiLCJwbGFuIjoicHJlbWl1bSIsImRvbWFpbnMiOlsiYXBleGNoYXJ0cy5jb20iLCIxMjcuMC4wLjEiLCJsb2NhbGhvc3QiXSwic2lnIjoieVBmb1VCc0Z3TU9ZdUEyaEZkR0I2Y1FtZ0JITUtXcVdJSjB2NVRESXRZbFR3eDJMUmh6R2x0RUc3VXJ4X0s3b25ZMWRZb2Z2VGItN01ydFYyNDVyOWcifQ==');
this.wireRegions()
function pickRegion(region) {
document.querySelectorAll('[data-region]').forEach(function (b) {
b.classList.toggle('active', b.getAttribute('data-region') === region)
})
ApexCharts.getChartByID('shipped').updateOptions({
highlightFilter: { data: region ? [this.SHIPPED_BY_REGION[region]] : null },
})
ApexCharts.getChartByID('goals').updateOptions({
highlightFilter: { data: region ? this.GOALS_BY_REGION[region] : null },
})
ApexCharts.getChartByID('satisfaction').updateOptions({
highlightFilter: { data: region ? [this.SATISFACTION_BY_REGION[region]] : null },
})
ApexCharts.getChartByID('same-day').updateOptions({
highlightFilter: { data: region ? [this.SAME_DAY_BY_REGION[region]] : null },
})
}
function wireRegions() {
document.querySelectorAll('[data-region]').forEach(function (b) {
b.addEventListener('click', () => {
this.pickRegion(b.getAttribute('data-region'))
})
})
}
}
ngOnDestroy() {
// no cleanup needed
}
}