Filter by a Product in Angular

Using ApexCharts with Angular

This Filter by a Product 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 PRODUCTS: any = ['Laptops', 'Phones', 'Tablets', 'Audio'];

  private REGIONS: any = ['North', 'South'];

  private QUARTERS: any = ['Q1', 'Q2', 'Q3', 'Q4'];

  private REVENUE: any = {
        Laptops: { North: [42, 38, 45, 61], South: [30, 28, 33, 44] },
        Phones: { North: [55, 49, 52, 70], South: [48, 51, 47, 66] },
        Tablets: { North: [18, 15, 17, 26], South: [22, 19, 21, 25] },
        Audio: { North: [12, 14, 13, 21], South: [9, 11, 10, 16] },
      };

  private sum = (a: any): any => {
        return a.reduce(function (s, v) {
          return s + v
        }, 0)
      };

  private productTotal = (p: any): any => {
        return this.sum(
          this.REGIONS.map(function (r) {
            return this.sum(this.REVENUE[p][r])
          }),
        )
      };

  private regionQuarters = (r: any, onlyProduct: any): any => {
        return this.QUARTERS.map(function (q, k) {
          return this.sum(
            this.PRODUCTS.filter(function (p) {
              return !onlyProduct || p === onlyProduct
            }).map(function (p) {
              return this.REVENUE[p][r][k]
            }),
          )
        })
      };

  private picked: any = null;

  private productSeries = (): any => {
        var totals = this.PRODUCTS.map(this.productTotal)
        var s = { name: 'Revenue', data: totals }
        // On the source chart the pick is the whole of its own column, so it stays
        // solid while the others fade.
        if (this.picked) {
          s.highlightData = this.PRODUCTS.map(function (p, j) {
            return p === this.picked ? totals[j] : null
          })
        }
        return [s]
      };

  private quarterSeries = (): any => {
        return this.REGIONS.map(function (r) {
          var s = { name: r, data: this.regionQuarters(r) }
          if (this.picked) s.highlightData = this.regionQuarters(r, this.picked)
          return s
        })
      };

  private pickProduct = (index: any): any => {
        var product = this.PRODUCTS[index]
        this.picked = this.picked === product ? null : product
        var readout = document.getElementById('readout')
        if (readout) {
          readout.innerHTML = this.picked
            ? 'Filtered by <b>' + this.picked + '</b>. Click it again to clear.'
            : 'Click a product to filter the quarters by it.'
        }
        // Let the click handler unwind before both charts redraw.
        Promise.resolve().then(function () {
          ApexCharts.getChartByID('products').updateSeries(this.productSeries())
          ApexCharts.getChartByID('quarters').updateSeries(this.quarterSeries())
        })
      };

  public chartOptions: Partial<ChartOptions> = {
          series: this.productSeries(),
          chart: {
            id: 'products',
            type: 'bar',
            height: 320,
            toolbar: { show: false },
            events: {
              dataPointSelection: (e, ctx, opts) => {
                this.pickProduct(opts.dataPointIndex)
              },
            },
          },
          colors: ['#0f766e'],
          plotOptions: {
            bar: { horizontal: true, barHeight: '64%', borderRadius: 3 },
          },
          states: { active: { filter: { type: 'none' } } },
          dataLabels: { enabled: false },
          xaxis: {
            categories: this.PRODUCTS,
            tickAmount: 3,
            labels: {
              formatter: (v) => {
                return '$' + v + 'k'
              },
            },
          },
          // On the source chart the pick is its own column, so a tooltip row for the
          // part would only repeat the total.
          highlightFilter: {
            tooltip: { show: false },
            dataLabels: { value: 'whole' },
          },
          legend: { show: false },
          title: { text: 'Revenue by product', style: { fontSize: '13px' } },
          tooltip: {
            y: {
              formatter: (v) => {
                return '$' + v + 'k'
              },
            },
          },
        };

  public chartOptions2: Partial<ChartOptions> = {
          series: this.quarterSeries(),
          chart: {
            id: 'quarters',
            type: 'bar',
            height: 320,
            toolbar: { show: false },
          },
          colors: ['#2563eb', '#f59e0b'],
          plotOptions: {
            bar: { columnWidth: '70%', borderRadius: 3 },
          },
          dataLabels: { enabled: false },
          stroke: { show: true, width: 1, colors: ['transparent'] },
          xaxis: { categories: this.QUARTERS },
          yaxis: {
            labels: {
              formatter: (v) => {
                return '$' + v + 'k'
              },
            },
          },
          legend: { position: 'bottom' },
          title: { text: 'Revenue by quarter', style: { fontSize: '13px' } },
          tooltip: {
            shared: true,
            intersect: false,
            y: {
              formatter: (v) => {
                return '$' + v + 'k'
              },
            },
          },
        };
  ngAfterViewInit() {
    (window as any).ApexCharts.setLicense('APEX-eyJleHBpcnlEYXRlIjoiMjEyNi0wNy0wNCIsImlzc3VlRGF0ZSI6IjIwMjYtMDctMjgiLCJwbGFuIjoicHJlbWl1bSIsImRvbWFpbnMiOlsiYXBleGNoYXJ0cy5jb20iLCIxMjcuMC4wLjEiLCJsb2NhbGhvc3QiXSwic2lnIjoieVBmb1VCc0Z3TU9ZdUEyaEZkR0I2Y1FtZ0JITUtXcVdJSjB2NVRESXRZbFR3eDJMUmh6R2x0RUc3VXJ4X0s3b25ZMWRZb2Z2VGItN01ydFYyNDVyOWcifQ==');

    function pickProduct(index) {
          var product = this.PRODUCTS[index]
          this.picked = this.picked === product ? null : product
          var readout = document.getElementById('readout')
          if (readout) {
            readout.innerHTML = this.picked
              ? 'Filtered by <b>' + this.picked + '</b>. Click it again to clear.'
              : 'Click a product to filter the quarters by it.'
          }
          // Let the click handler unwind before both charts redraw.
          Promise.resolve().then(function () {
            ApexCharts.getChartByID('products').updateSeries(this.productSeries())
            ApexCharts.getChartByID('quarters').updateSeries(this.quarterSeries())
          })
        }
  }

  ngOnDestroy() {
    // no cleanup needed
  }
}