Gauges and Radial Bars with One Region Highlighted in Angular

Using ApexCharts with Angular

This Gauges and Radial Bars with One Region Highlighted 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';

import 'apexcharts/features/highlight-filter';

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