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

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