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

Angular charts guide
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
  }
}