I am trying to compare 2 datasets in chart.js. There are large differences in values so in order to compare the values a have decided to graph the data on 2 different y axes.
1 of the datasets has some negative values and the base of the graph starts from a negative value. The other dataset only has positive values and the base of the graph starts from 0.
Is there any setting will allow me to align the 0's on the y axes (when working with 2 different axes)?
My code for chart scales is as follows:
y: {
beginAtZero: true,
grid: {
display: false,
drawBorder: false,
drawOnChartArea: false,
},
ticks: {
display: true,
},
},
},
y1: {
beginAtZero: true,
position: 'right',
grid: {
display: false,
drawBorder: false,
drawOnChartArea: false,
},
ticks: {
display: true,
},
},
}
Example graph:
The same as for your previous question, while there's no specific
option for what you want to achieve, it can be done by setting the y
and y1
axes scriptable min
and max
. In this case, the
important parameter that needs to be set using a proportionality formula is min
, while max
can be set to the maximum value of data to avoid
the additional space to the next multiple of the axis step:
function computeMin(which, data){
// computes min for scale `which` (0 -> 'y', 1 -> 'y1') such that the
// level of 0 is the same for all datasets
// note: there's no negative-only dataset
const dataValues = data.datasets.map(dataset=>dataset.data),
dataMin = dataValues.map(data => Math.min(0, ...data)),
dataMax = dataValues.map(data => Math.max(0, ...data)),
ratios = dataMin.map((min_i, i) => -min_i/(-min_i + dataMax[i])),
bestRatio = Math.max(...ratios);
if(ratios[which] < bestRatio){
const delta = (dataMax[which] * bestRatio - dataMin[which] * (bestRatio - 1))/(bestRatio - 1);
return dataMin[which] + delta;
}
return dataMin[which]
}
used as:
options: {
........... other options
scales: {
y: {
.......... other y axis options
ticks: {
display: true,
includeBounds: false
},
max: ({chart}) => computeMax(0, chart.data)
},
y1: {
.......... other y1 axis options
ticks: {
display: true,
includeBounds: false
},
max: ({chart}) => computeMax(1, chart.data)
}
}
}
In a demo snippet:
function computeMin(which, data){
// computes min for scale `which` (0 -> 'y', 1 -> 'y1') such that the
// level of 0 is the same for all datasets
// note: no negative-only datasets are not supported, to support those
// see https://jsfiddle.net/oruvz3dx/, https://jsfiddle.net/oruvz3dx/1
const dataValues = data.datasets.map(dataset=>dataset.data),
dataMin = dataValues.map(data => Math.min(0, ...data)),
dataMax = dataValues.map(data => Math.max(0, ...data)),
ratios = dataMin.map((min_i, i) => -min_i/(-min_i + dataMax[i])),
bestRatio = Math.max(...ratios);
if(ratios[which] < bestRatio){
const delta = (dataMax[which] * bestRatio - dataMin[which] * (bestRatio - 1))/(bestRatio - 1);
return dataMin[which] + delta;
}
return dataMin[which]
}
const config = {
type: "bar",
data: {
labels: ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'],
datasets: [
{
label: 2023,
data: [50, -59, -80, -81, -56, 55, 65, 108, 80, -41, 56, 55],
},
{
label: 2024,
data: [20, 65, 80, 102, 55, 56, 65, 59, 80, 81, 56, 55].map(x => x * 10),
yAxisID: 'y1'
},
]
},
options: {
maintainAspectRatio: false,
scales: {
y: {
grid: {
display: false,
drawBorder: false,
drawOnChartArea: false
},
ticks: {
display: true,
includeBounds: false
},
min: ({chart}) => computeMin(0, chart.data),
max: ({chart}) => Math.max(...chart.data.datasets[0].data)
},
y1: {
position: 'right',
grid: {
display: false,
drawBorder: false,
drawOnChartArea: false,
},
ticks: {
display: true,
includeBounds: false
},
min: ({chart}) => computeMin(1, chart.data),
max: ({chart}) => Math.max(...chart.data.datasets[1].data)
},
}
}
};
new Chart('myChart', config);
<div style="height: 300px">
<canvas id="myChart">
</canvas>
</div>
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
This solution is adapted to the stated parameters of the problem: there are only two datasets, none of which has negative-only values.
A generalisation of this solution, that allows for multiple datasets with negative-only, positive-only and mixed positive-negative values, is set in this fiddle; here's a version of that fiddle that caches the computed min
and max
values, to avoid repeated computation.