The formatter
function gives me the total of each group of stacked bars, I am trying to do the datalabels
positioning it up if the total is positive and positioning it down if it is negative. Please any help would be very grateful.
datalabels: {
anchor: (context) =>{
const anchor = [];
let sum = ?;
if(parseFloat(sum) >=0){
anchor.push('end');
}else{
anchor.push('start');
}
return anchor;
},
align: (context) =>{
const align = [];
let sum = ?;
if(parseFloat(sum) >=0){
align.push('end');
}else{
align.push('bottom');
}
return align;
},
formatter:(value, context) =>{
const datasetArray = [];
context.chart.data.datasets.forEach((dataset) =>{
if(dataset.data[context.dataIndex] != undefined){
datasetArray.push(dataset.data[context.dataIndex]);
}
});
function totalSum(total, datapoint){
return +total + +datapoint;
}
let sum = datasetArray.reduce(totalSum);
if(context.datasetIndex === datasetArray.length - 1){
return parseFloat(sum).toFixed(2) ;
}else{
return '';
}
},
}
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With