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 cellCount: any = 10000;

  private DAY: any = 86400000;

  private shapeFor = (cells: any): any => {
          if (cells <= 10000) return { rows: 50, cols: cells / 50 }
          return { rows: 100, cols: cells / 100 }
        };

  private formatLat = (lat: any): any => {
          var r = Math.round(lat)
          if (r > 0) return r + '°N'
          if (r < 0) return -r + '°S'
          return '0°'
        };

  private generateHeatmap = (cells: any): any => {
          var shape = this.shapeFor(cells)
          // Columns are consecutive days ending today, anchored to UTC midnight so
          // every day is exactly 86400000 ms and the seasonal phase never drifts.
          var now = new Date()
          var end = Date.UTC(now.getUTCFullYear(), now.getUTCMonth(), now.getUTCDate())
          var start = end - (shape.cols - 1) * this.DAY
  
          // Deterministic PRNG so the field looks the same on every reload.
          var seed = 1013904223
          function rand() {
            seed = (seed * 1103515245 + 12345) & 0x7fffffff
            return seed / 0x7fffffff
          }
  
          var series = new Array(shape.rows)
          for (var r = 0; r < shape.rows; r++) {
            var lat = 90 - (r / (shape.rows - 1)) * 180
            var absLat = Math.abs(lat) / 90
            // Warm at the equator (~27C), cold at the poles (~-30C).
            var baseTemp = 27 - 57 * Math.pow(absLat, 1.05)
            // Seasonal swing: tiny at the equator, large toward the poles.
            var swing = 2 + 22 * absLat
            var hemi = lat >= 0 ? 1 : -1
            var data = new Array(shape.cols)
            for (var c = 0; c < shape.cols; c++) {
              var t = start + c * this.DAY
              var d = new Date(t)
              var doy = (t - Date.UTC(d.getUTCFullYear(), 0, 0)) / this.DAY
              // Northern hemisphere peaks in mid-July; southern is the opposite.
              var seasonal = swing * Math.sin((2 * Math.PI * doy) / 365 - 1.87) * hemi
              var temp = baseTemp + seasonal + (rand() - 0.5) * 3
              data[c] = { x: t, y: Math.round(temp) }
            }
            series[r] = { name: this.formatLat(lat), data: data }
          }
          // Rows read north (top) -> south (bottom); ApexCharts draws the last series
          // on top, so reverse before returning.
          return series.reverse()
        };

  private heatmapData: any = this.generateHeatmap(this.cellCount);

  private domNodeCount = (): any => {
          var el = document.querySelector('#chart')
          return el ? el.getElementsByTagName('*').length : 0
        };

  private lastRenderMs: any = 0;

  private updateStats = (): any => {
          var el = document.getElementById('stats')
          if (!el || typeof this.chart === 'undefined') return
          var active = this.chart.getActiveRenderer ? this.chart.getActiveRenderer() : 'svg'
          el.innerHTML =
            'Cells: <b>' +
            this.cellCount.toLocaleString() +
            '</b> &nbsp;·&nbsp; Active renderer: <b>' +
            active.toUpperCase() +
            '</b> &nbsp;·&nbsp; DOM nodes in chart: <b>' +
            this.domNodeCount().toLocaleString() +
            '</b> &nbsp;·&nbsp; Render: <b>' +
            (this.lastRenderMs > 0 ? this.lastRenderMs + ' ms' : 'change a control to time it') +
            '</b>'
        };

  private rebuild = (): any => {
          this.cellCount = parseInt(document.getElementById('cellCount').value, 10)
          var mode = document.getElementById('rendererMode').value
  
          this.heatmapData = this.generateHeatmap(this.cellCount)
          this.chartOptions.chart.renderer = mode
          this.chartOptions.series = this.heatmapData
          this.chartOptions.title.text = 'Heatmap: ' + this.cellCount.toLocaleString() + ' cells'
  
          var t = performance.now()
          this.chart.destroy()
          this.chart = new ApexCharts(document.querySelector('#chart'), this.chartOptions)
          this.chart.render().then(function () {
            this.lastRenderMs = Math.round(performance.now() - t)
            this.updateStats()
          })
        };

  public chartOptions: Partial<ChartOptions> = {
          series: this.heatmapData,
          chart: {
            height: 500,
            type: 'heatmap',
            renderer: 'canvas',
            rendererThreshold: 8000,
            animations: { enabled: false },
            toolbar: { show: false },
          },
          title: {
            text: 'Heatmap: 10,000 cells',
            align: 'left',
          },
          subtitle: {
            text: 'Daily mean surface temperature (°C) by latitude, cold (blue) to warm (red)',
            align: 'left',
          },
          dataLabels: { enabled: false },
          // No cell stroke: at high cell counts each cell is ~1px, and a stroke that
          // wide would cover the fill entirely (cells look blank). Solid fills only.
          stroke: { show: false },
          plotOptions: {
            heatmap: {
              // Flat temperature buckets (no within-bucket shading) on a cold-to-warm
              // scale, so a cell's color reads as a temperature straight off the legend.
              enableShades: false,
              colorScale: {
                ranges: [
                  { from: -60, to: -25, color: '#313695', name: 'below -25' },
                  { from: -25, to: -15, color: '#4575b4', name: '-25 to -15' },
                  { from: -15, to: -5, color: '#74add1', name: '-15 to -5' },
                  { from: -5, to: 2, color: '#abd9e9', name: '-5 to 2' },
                  { from: 2, to: 9, color: '#fee090', name: '2 to 9' },
                  { from: 9, to: 16, color: '#fdae61', name: '9 to 16' },
                  { from: 16, to: 23, color: '#f46d43', name: '16 to 23' },
                  { from: 23, to: 60, color: '#d73027', name: 'above 23' },
                ],
              },
            },
          },
          xaxis: {
            type: 'datetime',
            labels: { datetimeUTC: true, style: { fontSize: '12px' } },
            axisTicks: { show: false },
            axisBorder: { show: false },
          },
          yaxis: {
            labels: { style: { fontSize: '12px' } },
          },
        };
  ngAfterViewInit() {
    (window as any).ApexCharts.setLicense('APEX-eyJpc3N1ZURhdGUiOiIyMDI2LTA0LTE2IiwiZXhwaXJ5RGF0ZSI6IjIwNTItMDQtMTciLCJwbGFuIjoiZW50ZXJwcmlzZSIsImRvbWFpbnMiOlsiYXBleGNoYXJ0cy5jb20iLCIxMjcuMC4wLjEiXX0=');

    requestAnimationFrame(() => {
            if (this.chart && this.chart.chart?.w && this.chart.chart?.w.globals && this.chart.chart?.w.globals.series) {
              this.updateStats()
            } else {
              requestAnimationFrame(poll)
            }
          })
    document.getElementById('rerender').addEventListener('click', this.rebuild)
    document.getElementById('rendererMode').addEventListener('change', this.rebuild)
    document.getElementById('cellCount').addEventListener('change', this.rebuild)
  }

  ngOnDestroy() {
    // no cleanup needed
  }
}
Canvas Renderer (High-Density) - Angular Heatmap Charts | ApexCharts.js | ApexCharts.js