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.
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:
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];
maxValue
.ratioToValue(ratio: number) {
return ratio * this.maxValue;
}
this.barChartData.forEach((x) => {
x.data[this.grossProfitPctBarIndex] = this.ratioToValue(
x.data[this.grossProfitPctBarIndex]
);
});
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;
},
},
},
},
};
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