chart.jsng2-charts

How to assign yAxisID to ChartJs datapoint


I have following dataset:

[
    {data: [100, 50, 0.5], backgroundColor:'#506f86', borderColor: '#506f86', hoverBackgroundColor:'#384e5e', hoverBorderColor: '#384e5e', label: 'Service'},
    {data: [80, 20, 0.25], backgroundColor:'#9ec5fe', borderColor: '#9ec5fe', hoverBackgroundColor:'#6ea8fe', hoverBorderColor: '#6ea8fe', label: 'Maintenance'},
    {data: [60, 20, 0.33], backgroundColor:'#ffc107', borderColor: '#ffc107', hoverBackgroundColor:'#cc9a06', hoverBorderColor: '#cc9a06', label: 'Monitoring'}
]

Here, datapoints are as follows:

data[0] - Sales
data[1] - Gross Profit $
data[2] - Gross Profit %

I have following scales defined:

scales: {
      dollar: {
          type: 'linear',
          position: 'left',
          ticks: {
            callback: (label, index, labels) => {
              return `$ ${label}`;
            }
          }
      },
      percent: {
          type: 'linear',
          position: 'right',
          grid: {
              display: false,
          },
          ticks: {
            color: '#de703c',
            callback: function(value, index, values) {
              return (value + '%');
            }
          }
      },
      xAxis: {
          type: 'category',
          axis: 'x',
          labels: ['Sales', 'Gross Profit $', 'Gross Profit %'],
      },
    },

This is what the chart looks like

I would like the third datapoint "Gross Profit %" in the dataset to be mapped to yAxisID "percent" on the right.


Solution

  • The reason why the values for the Gross Profit % are not working as the values are too small for the y-axis (dollar) compared to other values.

    The proposed hack way is by showing the percentage value as a whole number (rate * maxValue) instead of a decimal (0.25).

    Formula:

    rate = x / maxValue
    x = rate * maxValue
    

    The implementation will be:

    1. Record the data index for Gross Profit % which is 2. Also get the max value for the dataset.
    grossProfitPctBarIndex = 2;
    // Or
    // this.grossProfitPctBarIndex = this.barChartLabels.length - 1;
    
    this.maxValue = this.barChartData
          .map((x) => x.data)
          .reduce((acc: number[], cur: number[]) => {
            acc = acc.concat(...cur);
    
            return acc;
          }, [])
          .sort((a: number, b: number) => b - a)[0];
    
    1. The values for the data index for Gross Profit % to be multiplied by maxValue.
    ratioToValue(ratio: number) {
      return ratio * this.maxValue;
    }
    
    this.barChartData.forEach((x) => {
      x.data[this.grossProfitPctBarIndex] = this.ratioToValue(
            x.data[this.grossProfitPctBarIndex]
          );
    });
    
    1. For the tooltip, you should override the tooltip callback to show the bar value for Gross Profit % in decimal (0.25) instead of the whole number.
    valuetoRatio(value: number) {
      return value / this.maxValue;
    }
    
    barChartOptions = {
      ...,
      plugins: {
        tooltip: {
          callbacks: {
            label: (context: any) => {
              let label = context.dataset.label || '';
    
              if (label) {
                label += ': ';
              }
    
              if (context.dataIndex == this.grossProfitPctBarIndex) {
                if (context.parsed.y !== null) label += this.valuetoRatio(context.parsed.y);
              } else {
                 label += context.parsed.y;
              }
    
              return label;
            },
          },
        },
      },
    };
    
    1. For the onclick event, you should implement the same concept in (3) to show the value in decimal for Gross Profit %.
    barChartOptions = {
      ...,
      onClick: (evt: any, elements: any, chart: any) => {
        const bars = chart.getElementsAtEventForMode(
          evt,
          'nearest',
          { intersect: true },
          true
        );
        if (bars.length === 0) return; // no bars
    
        const bar = bars[0];
    
        // Get index and label text
    
        const index = bar.index;
        const label = chart.data.labels[index];
        let selectedDataset = chart.data.datasets[bar.datasetIndex];
    
        console.log('Selected label:', label);
        console.log('Selected sub-label:', selectedDataset.label);
        console.log(
          "Selected sub-label's value:",
          bar.index == this.grossProfitPctBarIndex
            ? this.valuetoRatio(selectedDataset.data[index])
            : selectedDataset.data[index]
        );
      },
    };
    

    Note that you need to migrate the onClick function from the normal function to the arrow function in order to allow passing the grossProfitPctBarIndex. You may refer to Arrow functions do not create their own this binding.

    Result

    enter image description here

    Demo @ Stacklitz