This demo uses imperative chart updates. The generated code is a faithful Angular translation: open it in CodeSandbox to run and tweak.

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 REGION_LABELS: any = ['Africa', 'Europe', 'Americas', 'Asia', 'Oceania'];

  private REGION_COLORS: any = ['#F2994A', '#2D9CDB', '#27AE60', '#EB5757', '#9B51E0'];

  private REGION_COUNT: any = [100, 170, 150, 140, 40];

  private FINISH_LABELS: any = [
          'Sub-3:00',
          '3:00-3:30',
          '3:30-4:00',
          '4:00-4:30',
          '4:30-5:00',
          '5:00+',
        ];

  private FINISH_COLORS: any = [
          '#1E8E5A',
          '#35B37E',
          '#86C82B',
          '#F2C037',
          '#F2994A',
          '#EB5757',
        ];

  private FINISH_COUNT: any = [45, 120, 165, 140, 85, 45];

  private LIGHT: any = { theme: { mode: 'light' } };

  private BEATS: any = [
          {
            selector: '#sb-step-1',
            view: this.LIGHT,
            this.chartOptions: {
              series: this.REGION_COUNT,
              labels: this.REGION_LABELS,
              colors: this.REGION_COLORS,
              plotOptions: { unit: { layout: 'grouped', transition: 'flow' } },
            },
            announce: 'The field by region: five clusters of finishers',
          },
          {
            selector: '#sb-step-2',
            view: this.LIGHT,
            this.chartOptions: {
              series: this.REGION_COUNT,
              labels: this.REGION_LABELS,
              colors: this.REGION_COLORS,
              plotOptions: { unit: { layout: 'packed', transition: 'flow' } },
            },
            announce:
              'One field: the same finishers packed together, smallest region centred',
          },
          {
            selector: '#sb-step-3',
            view: this.LIGHT,
            this.chartOptions: {
              series: this.FINISH_COUNT,
              labels: this.FINISH_LABELS,
              colors: this.FINISH_COLORS,
              plotOptions: { unit: { layout: 'columns', transition: 'flow' } },
            },
            announce: 'Regrouped by finish time: the finishers flow into six bars',
          },
        ];

  private steps: any = document.querySelectorAll('.sb-step');

  private dots: any = document.querySelectorAll('.sb-dot');

  private chip: any = document.getElementById('sb-chip');

  private prevBtn: any = document.getElementById('sb-prev');

  private nextBtn: any = document.getElementById('sb-next');

  private scroller: any = document.getElementById('sb-scroller');

  private current: any = 0;

  private scrollToBeat = (i: any): any => {
          var step = document.getElementById('sb-step-' + (i + 1))
          if (!step || !this.scroller) return
          var sRect = step.getBoundingClientRect()
          var cRect = this.scroller.getBoundingClientRect()
          this.scroller.scrollTop +=
            sRect.top - cRect.top - (this.scroller.clientHeight - step.clientHeight) / 2
        };

  private goToBeat = (i: any): any => {
          i = Math.max(0, Math.min(this.BEATS.length - 1, i))
          this.chart.storyboard.goTo(i)
          this.scrollToBeat(i)
        };

  public chartOptions: Partial<ChartOptions> = {
          series: [100, 170, 150, 140, 40],
          chart: {
            id: 'marathonStory',
            type: 'unit',
            height: 420,
            fontFamily: 'Helvetica, Arial, sans-serif',
            animations: {
              enabled: true,
              speed: 850,
            },
            toolbar: { show: false },
          },
          labels: ['Africa', 'Europe', 'Americas', 'Asia', 'Oceania'],
          colors: ['#F2994A', '#2D9CDB', '#27AE60', '#EB5757', '#9B51E0'],
          legend: {
            position: 'bottom',
          },
          plotOptions: {
            unit: {
              layout: 'grouped',
              transition: 'flow',
              // A fixed dot size keeps every dot the SAME size in all three layouts, so
              // dots only move and recolour across the story (they never resize). The
              // size is set by the tightest layout: 600 dots must fit the packed field
              // and the widest region cluster, so the dots are small on purpose.
              size: 2.3,
              spacing: 1.15,
            },
          },
        };
  ngAfterViewInit() {
    (window as any).ApexCharts.setLicense('APEX-eyJpc3N1ZURhdGUiOiIyMDI2LTA0LTE2IiwiZXhwaXJ5RGF0ZSI6IjIwNTItMDQtMTciLCJwbGFuIjoiZW50ZXJwcmlzZSIsImRvbWFpbnMiOlsiYXBleGNoYXJ0cy5jb20iLCIxMjcuMC4wLjEiXX0=');

    this.chart.addEventListener('beatChange', (c, info) => {
            this.current = info.index
            this.steps.forEach(function (el, i) {
              el.classList.toggle('is-active', i === info.index)
            })
            this.dots.forEach(function (dot, i) {
              dot.classList.toggle('is-active', i === info.index)
            })
            this.chip.textContent = 'Beat ' + (info.index + 1) + ' of ' + this.BEATS.length
            this.prevBtn.disabled = info.index === 0
            this.nextBtn.disabled = info.index === this.BEATS.length - 1
          })
    this.dots.forEach(function (dot, i) {
            dot.addEventListener('click', () => {
              this.goToBeat(i)
            })
          })
    this.prevBtn.addEventListener('click', () => {
            this.goToBeat(this.current - 1)
          })
    this.nextBtn.addEventListener('click', () => {
            this.goToBeat(this.current + 1)
          })
    window.addEventListener('load', () => {
            // scroller binds the observer to the story column, so the story is driven by
            // that panel's own scroll (not the page/iframe viewport).
            this.chart.storyboard.bind({ beats: this.BEATS, scroller: '#sb-scroller' })
          })
  }

  ngOnDestroy() {
    // no cleanup needed
  }
}
Marathon Storyboard - Angular Unit Charts | ApexCharts.js | ApexCharts.js