[Chartjs]-How to position positive value on top and negative value on bottom of stacked bar in chart js

2👍

It’s unclear at this point how the bars are stacked – there are many unknown options. A possible combination based on the visual appearance of the image chart could be the following:

const N = 10;
const dataGenerator = (plus = 600, minus = 400, pNull = 1) =>
    Array.from({length: N}, ()=>Math.random() < pNull ?
        Math.round(Math.random()*(plus+minus)-minus)/4 : null)
const ctx1 = document.getElementById('chart1');
new Chart(ctx1, {
    type: "bar",
    plugins: [ChartDataLabels],
    data: {
        labels: Array.from({length: N}, (_, i)=>'l'+(i+1)),
        datasets: [
            {
                data: dataGenerator(),
                stack: 'a',
            },
            {
                data: dataGenerator() ,
                stack: 'a',
            },
            {
                data: dataGenerator(100, 50, 0.5),
                stack: 'a',
            },
        ]
    },
    options: {
        indexAxis: 'x',
        layout: {
            padding: {
                top: 20,
                bottom: 20
            }
        },
        animation: {
            duration: 0
        },
        scales: {
            x: {
                ticks:{
                    display: false
                }
            },
            y: {
                stacked: true,
                beginAtZero: true
            }
        },
        plugins:{
            legend:{
                display: false
            },
            datalabels:{
                formatter: (value, context) => {
                    const {dataIndex, datasetIndex, chart} = context;
                    const dataForDataIndex = chart.data.datasets.map(
                        dataset=>dataset.data[dataIndex] ?? 0
                    );
                    const total = dataForDataIndex.reduce((s, x)=>s+x)
                    // the index of the dataset that contains the last point (at dataIndex)
                    // with the same sign as the total - that is the one that should carry
                    // the total label
                    const datasetIndexLast = dataForDataIndex.findLastIndex(x => x * total > 0);
                    context.total = total;
                    return datasetIndexLast === datasetIndex ? total.toFixed(2) : null
                },
                anchor: (context) => {
                    return context.total > 0 ? 'end' : 'start'
                },
                align: (context) => {
                    return context.total > 0 ? 'top' : 'bottom';
                },

                clip: false
            }
        }
    }
});
<div style="height:500px">
    <canvas id="chart1"></canvas>
</div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/4.3.0/chart.umd.js"
        integrity="sha512-CMF3tQtjOoOJoOKlsS7/2loJlkyctwzSoDK/S40iAB+MqWSaf50uObGQSk5Ny/gfRhRCjNLvoxuCvdnERU4WGg=="
        crossOrigin="anonymous" referrerpolicy="no-referrer"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/chartjs-plugin-datalabels/2.2.0/chartjs-plugin-datalabels.min.js" integrity="sha512-JPcRR8yFa8mmCsfrw4TNte1ZvF1e3+1SdGMslZvmrzDYxS69J7J49vkFL8u6u8PlPJK+H3voElBtUCzaXj+6ig==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>

The idea behind this is to have datalabels enabled for all bars from the dataset, but choose the one that is correctly positioned to represent the sum from the formatter – namely the last dataset whose value (for the current dataIndex) has the same sign as the sum. Things might be different if the order option were used.

The code also uses the fact that the context object is shared, for the same datasetIndex, between the formatter, align and anchor methods, formatter being always the first — if formatter returns null the others are not even called. Thus, it saves the sum for the current datasetIndex in the context object to be used by the only one point (per datasetIndex) for which align and anchor are called. For a safer solution, that doesn’t use this undocumented fact, one may recompute the sum in align and anchor, or cache the sum using an external object, which can be a solution to optimise the computation by only computing the sum once, for the first time the formatter is called for a dataset.

0👍

You can use the chartjs-plugin-datalabels as shown here.

Chart.defaults.set('plugins.datalabels', {
  color: '#FE777B'
});

var chart = new Chart(ctx, {
  options: {
    plugins: {
      // Change options for ALL labels of THIS CHART
      datalabels: {
        color: '#36A2EB',
        anchor: ()=>/*your condition here*/
      }
    }
  },
  data: {
    datasets: [{
      // Change options only for labels of THIS DATASET
      datalabels: {
        color: '#FFCE56'
      }
    }]
  }
});

Also checkout the positioning property.

Leave a comment