angulartypescriptapexcharts

ApexCharts TypeError: Cannot read properties of undefined (reading 'length') Fill Property


I am developing an app using Angular, the case is that when I want to put the fill property inside the ApexChartsOptions it throws an "TypeError: Cannot read properties of undefined (reading 'length')" but I dont know why.

HTML:

<apx-chart
          class="chart"
          [series]="emodChartConfig.routingOverviewSeries"
          [chart]="emodChartConfig.routingOverviewChartOptions.chart"
          [stroke]="emodChartConfig.routingOverviewChartOptions.stroke"
          [tooltip]="emodChartConfig.routingOverviewChartOptions.tooltip"
          [fill]="emodChartConfig.routingOverviewChartOptions.fill"
          [legend]="emodChartConfig.routingOverviewChartOptions.legend"
          [xaxis]="emodChartConfig.overviewXAxis"
          [yaxis]="emodChartConfig.routingOverviewChartOptions.yaxis"
          [plotOptions]="emodChartConfig.routingOverviewChartOptions.plotOptions"
          [dataLabels]="emodChartConfig.routingOverviewChartOptions.dataLabels"
></apx-chart>

TS(Angular):

async loadOverviewData() {
    this.monthlyEMODService.getOverviewMonthlyEMOD().subscribe({
      next: (data) => {
        const categories = data.map((item) => IdToUn[item.departmentId]);
        const routingData = data.map((item) => item.emod);
        const totalData = data.map((item) => item.pureEMOD);
        const totalBudget = data.map((item) => item.totalBudget);
        const routingBudget = data.map((item) => item.routingBudget);

        this.routingOverviewSeries = [
          {
            name: 'EMOD',
            type: 'column',
            data: [...routingData],
          },
          {
            name: 'Objetivo',
            type: 'line',
            data: [...routingBudget],
          },
        ];

        this.routingOverviewChartOptions = {

          dataLabels: {
            enabled: true,
            enabledOnSeries: [0],
            style: {
              fontSize: '14px',
              fontWeight: 'bold',
              colors: [
                function (opts: any) {
                  
                  const routingBudget = opts.w.config.series[1].data; // Asumiendo que 'Objetivo' es la segunda serie
                  const routingData = opts.w.config.series[0].data; // Asumiendo que 'EMOD' es la primera serie
                            
                  if (routingData[opts.dataPointIndex] > routingBudget[opts.dataPointIndex])
                    return '#00FF00'; // Verde
                  else
                    return '#FF0000'; // Rojo

                },
              ],
            },
          },
          plotOptions: {
            bar: {
              distributed: true,
            },
          },
          fill: {
            colors: [function(opts:any) {
              const routingBudget = opts.w.config.series[1].data; // Asumiendo que 'Objetivo' es la segunda serie
              const routingData = opts.w.config.series[0].data; // Asumiendo que 'EMOD' es la primera serie
                        
              if (routingData[opts.dataPointIndex] > routingBudget[opts.dataPointIndex]) {
                return '#00FF00'; // Verde
              } else {
                return '#FF0000'; // Rojo
              }
            }]
          },
     }

}

As you can see, i have the exact same thing in dataLabels and in the Fill property, however in the Fill property is not working but in the datalabels property it is.

I´ve tried doing a console.log(opts.dataPointIndex); for example and it only gives me: 0 and 1 and then it fails and displays the error in the console, like if the array only had 2 elements. I want to know what am i doing wrong if im literally doing the same thing in both properties.

This is the fetched data:

[
  {
    "departmentId": 1,
    "emod": 64.24,
    "pureEMOD": 59.59,
    "noCEDISEMOD": null,
    "noCEDISPureEMOD": null,
    "totalBudget": 82,
    "routingBudget": 70
  },
  {
    "departmentId": 2,
    "emod": 84.25,
    "pureEMOD": 73.05,
    "noCEDISEMOD": null,
    "noCEDISPureEMOD": null,
    "totalBudget": 82,
    "routingBudget": 86
  },
  {
    "departmentId": 3,
    "emod": 90.32,
    "pureEMOD": 79.77,
    "noCEDISEMOD": null,
    "noCEDISPureEMOD": null,
    "totalBudget": 85,
    "routingBudget": 87
  },
  {
    "departmentId": 4,
    "emod": 75.61,
    "pureEMOD": 67.6,
    "noCEDISEMOD": null,
    "noCEDISPureEMOD": null,
    "totalBudget": 82,
    "routingBudget": 87
  },
  {
    "departmentId": 5,
    "emod": 89.03,
    "pureEMOD": 81.18,
    "noCEDISEMOD": null,
    "noCEDISPureEMOD": null,
    "totalBudget": 82,
    "routingBudget": 85
  },
  {
    "departmentId": 6,
    "emod": 87.12,
    "pureEMOD": 70.93,
    "noCEDISEMOD": null,
    "noCEDISPureEMOD": null,
    "totalBudget": 82,
    "routingBudget": 82
  },
  {
    "departmentId": 7,
    "emod": 79.66,
    "pureEMOD": 70.91,
    "noCEDISEMOD": 91.23,
    "noCEDISPureEMOD": 70.91,
    "totalBudget": 82,
    "routingBudget": 82
  }
]

ApexCharts Fill Documentation


Solution

  • You are getting this error because the colors property was not specified for stroke property.

    This could be a bug I am not sure. But the fix is to apply the same function to the stroke also, so that both will have the same color.

    conditionalColor = function (opts: any) {
      const routingBudget = opts.w.config.series[1].data; // Asumiendo que 'Objetivo' es la segunda serie
      const routingData = opts.w.config.series[0].data; // Asumiendo que 'EMOD' es la primera serie
    
      if (routingData[opts.dataPointIndex] > routingBudget[opts.dataPointIndex]) {
        return '#00FF00'; // Verde
      } else {
        return '#FF0000'; // Rojo
      }
    };
    ...
    
    ...  
    ngOnInit() {
    this.getOverviewMonthlyEMOD().subscribe({
      next: (data: any[]) => {
        this.routingOverviewChartOptions = {
          ...
          stroke: {
            width: [0, 1.5, 2], // Configuración para columnas y líneas
            colors: [this.conditionalColor],
          },
          ...
          fill: {
            colors: [this.conditionalColor],
          },
          ...
    

    Full Code:

    import { Component } from '@angular/core';
    import {
      ApexChart,
      ApexStroke,
      ApexTooltip,
      ApexLegend,
      ApexPlotOptions,
      ApexYAxis,
      ApexDataLabels,
      ApexAxisChartSeries,
      ApexXAxis,
      ApexFill,
      NgApexchartsModule,
    } from 'ng-apexcharts';
    import { of } from 'rxjs';
    
    @Component({
      selector: 'app-root',
      imports: [NgApexchartsModule],
      templateUrl: './app.component.html',
      styleUrls: ['./app.component.css'],
    })
    export class AppComponent {
      public idToUn: { [key: number]: string } = {
        1: 'UN1',
        2: 'UN2',
        3: 'UN3',
        4: 'UN4',
        5: 'UN5',
        6: 'CEDIS',
        7: 'PLANTA',
      };
    
      getOverviewMonthlyEMOD() {
        return of([
          {
            departmentId: 1,
            emod: 64.24,
            pureEMOD: 59.59,
            noCEDISEMOD: null,
            noCEDISPureEMOD: null,
            totalBudget: 82,
            routingBudget: 70,
          },
          {
            departmentId: 2,
            emod: 84.25,
            pureEMOD: 73.05,
            noCEDISEMOD: null,
            noCEDISPureEMOD: null,
            totalBudget: 82,
            routingBudget: 86,
          },
          {
            departmentId: 3,
            emod: 90.32,
            pureEMOD: 79.77,
            noCEDISEMOD: null,
            noCEDISPureEMOD: null,
            totalBudget: 85,
            routingBudget: 87,
          },
          {
            departmentId: 4,
            emod: 75.61,
            pureEMOD: 67.6,
            noCEDISEMOD: null,
            noCEDISPureEMOD: null,
            totalBudget: 82,
            routingBudget: 87,
          },
          {
            departmentId: 5,
            emod: 89.03,
            pureEMOD: 81.18,
            noCEDISEMOD: null,
            noCEDISPureEMOD: null,
            totalBudget: 82,
            routingBudget: 85,
          },
          {
            departmentId: 6,
            emod: 87.12,
            pureEMOD: 70.93,
            noCEDISEMOD: null,
            noCEDISPureEMOD: null,
            totalBudget: 82,
            routingBudget: 82,
          },
          {
            departmentId: 7,
            emod: 79.66,
            pureEMOD: 70.91,
            noCEDISEMOD: 91.23,
            noCEDISPureEMOD: 70.91,
            totalBudget: 82,
            routingBudget: 82,
          },
        ]);
      }
      public routingOverviewSeries: ApexAxisChartSeries = [];
      public overviewXAxis: ApexXAxis = { categories: [] };
      public routingOverviewChartOptions: {
        chart: ApexChart;
        stroke: ApexStroke;
        tooltip: ApexTooltip;
        fill: ApexFill;
        legend: ApexLegend;
        plotOptions: ApexPlotOptions;
        yaxis: ApexYAxis;
        dataLabels: ApexDataLabels;
      } = {
        chart: { type: 'line' },
        stroke: {},
        tooltip: {},
        fill: {},
        legend: { show: false },
        plotOptions: { bar: { distributed: true } },
        yaxis: {},
        dataLabels: {},
      };
    
      conditionalColor = function (opts: any) {
        const routingBudget = opts.w.config.series[1].data; // Asumiendo que 'Objetivo' es la segunda serie
        const routingData = opts.w.config.series[0].data; // Asumiendo que 'EMOD' es la primera serie
    
        if (routingData[opts.dataPointIndex] > routingBudget[opts.dataPointIndex]) {
          return '#00FF00'; // Verde
        } else {
          return '#FF0000'; // Rojo
        }
      };
      ngOnInit() {
        this.getOverviewMonthlyEMOD().subscribe({
          next: (data: any[]) => {
            console.log(data);
            const categories = data.map((item) => this.idToUn[item.departmentId]);
            const routingData = data.map((item) => item.emod);
            const totalBudget = data.map((item) => item.totalBudget);
            const routingBudget = data.map((item) => item.routingBudget);
    
            this.routingOverviewSeries = [
              {
                name: 'EMOD',
                type: 'column',
                data: [...routingData],
              },
              {
                name: 'Objetivo',
                type: 'line',
                data: [...routingBudget],
              },
            ];
    
            this.overviewXAxis = {
              categories: [...categories],
            };
    
            this.routingOverviewChartOptions = {
              chart: {
                animations: {
                  enabled: false,
                },
                type: 'line', // Tipo principal
                height: '100%', // Altura de la gráfica
                width: '100%', // Ancho de la gráfica
                toolbar: {
                  show: true,
                  tools: {
                    download: true, // Deshabilita la descarga de la gráfica
                    selection: false, // Deshabilita la selección de la gráfica
                    zoom: false, // Deshabilita el zoom de la gráfica
                    zoomin: false, // Deshabilita el zoom in de la gráfica
                    zoomout: false, // Deshabilita el zoom out de la gráfica
                    pan: false, // Deshabilita el pan de la gráfica
                    reset: false, // Deshabilita el reset de la gráfica
                  },
                  export: {
                    png: {
                      filename: 'grafica-emod', // Nombre del archivo
                    },
                  },
                },
              },
              stroke: {
                width: [0, 1.5, 2], // Configuración para columnas y líneas
                colors: [this.conditionalColor],
              },
              tooltip: {
                enabled: true,
                custom: ({ series, seriesIndex, dataPointIndex, w }) => {
                  const serie = series[0]; // Obtiene la serie del EMOD series[1] es el objetivo budget
                  const xaxis = w.globals.categoryLabels[dataPointIndex]; // Obtiene las etiquetas del eje X
                  const value = serie[dataPointIndex]; // Obtiene el valor real del punto de la serie
                  const color =
                    value >= totalBudget[dataPointIndex] ? 'green' : 'red'; // Cambia el color según el valor
                  const isHistorical = series.length === 3; // Si los datos de la serie son 3 (EMOD, Objetivo, EMOD Año pasado)
                  const Actual =
                    isHistorical && series[2][dataPointIndex] !== null // Si es histórico y el valor no es nulo
                      ? `<br><span>EMOD Actual: ${series[2][dataPointIndex]}</span>`
                      : '';
                  return `
                    <div style="padding: 5px; color: white; background-color: ${color}; border-radius: 5px;">
                      <span>${xaxis}</span>
                      <br><span>EMOD${
                        isHistorical && series[2][dataPointIndex] !== null
                          ? ' YTD'
                          : ''
                      }: ${value}</span>
                      ${Actual}
                      <br><span>Objetivo: ${totalBudget[dataPointIndex]}</span>
                    </div>
                  `;
                },
              },
              legend: {
                show: false,
              },
              plotOptions: {
                bar: {
                  distributed: true,
                },
              },
              fill: {
                colors: [this.conditionalColor],
              },
              yaxis: {
                labels: {
                  show: false,
                },
              },
              dataLabels: {
                enabled: true,
                enabledOnSeries: [0],
                style: {
                  fontSize: '14px',
                  fontWeight: 'bold',
                  colors: [
                    function (opts: any) {
                      const routingBudget = opts.w.config.series[1].data; // Asumiendo que 'Objetivo' es la segunda serie
                      const routingData = opts.w.config.series[0].data; // Asumiendo que 'EMOD' es la primera serie
    
                      if (
                        routingData[opts.dataPointIndex] >
                        routingBudget[opts.dataPointIndex]
                      )
                        return '#00FF00'; // Verde
                      else return '#FF0000'; // Rojo
                    },
                  ],
                },
              },
            };
          },
          error: (err) => {
            console.error('Error al cargar los datos de EMOD Mensual', err);
          },
        });
      }
    }
    

    Stackblitz Demo