I don't see a simple solution to this. Each dataset data
should be an array of data points, while the labels
array should provide x axis values for those data points if the x axis is of type category
,
which is the default for a chart of type line
.
Let's say that our data
looks like this:
const data = {
datasets: [{
data: [10, 20, 30, 40, 50, 60, 70, 80]
}],
labels: ['January', 'February', 'February', 'March', 'April', 'May', 'June', 'June'],
};
by which we are defining the fact that two categories, the one for February
and the one for June
have two points while the others one point; other possible conventions might be used, if one wants to avoid duplication of text prone to typos.
To have a better control of the point positions and of the labels of the x axis you may employ a (possibly hidden) linear axis, together with setting relevant x
values in the datasets data
.
There are two possibilities: the first, if you want the categories to have the same size, then the points in the categories with two points will be closer together.
In this case one may use a secondary hidden linear x axis to position the points, while showing a uniform category x axis with the labels without repetitions:
const data = {
datasets: [{
data: [10, 20, 30, 40, 50, 60, 70, 80]
}],
labels: ['January', 'February', 'February', 'March', 'April', 'May', 'June', 'June'],
};
let xi = 0;
data.datasets.forEach(dataset => {
dataset.data = dataset.data.map((y, i) => {
let x = xi + 0.5;
if(data.labels[i] === data.labels[i-1]){
x += 1/6;
}
else if(data.labels[i] === data.labels[i+1]){
x -= 1/6;
xi -= 1;
}
xi += 1;
return ({x, y});
})
});
const options = {
maintainAspectRatio: false,
plugins: {
tooltip:{
callbacks: {
title: ([{dataIndex}]) => data.labels[dataIndex]
}
},
legend: {
display: false,
}
},
scales: {
x: {
display: false,
type: 'linear',
},
x2:{
type: 'category',
offset: true,
labels: [... new Set(data.labels)],
grid: {
color: 'rgba(0,0,0,0.4)',
offset: true
}
},
y: {
beginAtZero: true,
},
},
};
new Chart('myChart', {type: 'line', options, data});
<canvas style="height: 160px" id="myChart"></canvas>
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
The second case, if you want to keep points equidistant and have the categories with variable widths, such that a category with two points will be twice the size of a one point one.
In this case, we should keep only the linear x axis visible, but modify its labels to correspond to the original data.labels
. A second, similar, linear x axis might be the solution to show grid lines separating the categories:
const data = {
datasets: [{
data: [10, 20, 30, 40, 50, 60, 70, 80]
}],
labels: ['January', 'February', 'February', 'March', 'April', 'May', 'June', 'June'],
};
data.datasets.forEach(dataset => {
dataset.data = dataset.data.map((y, i) => ({x: i+0.5, y}));
});
const options = {
maintainAspectRatio: false,
plugins: {
tooltip:{
callbacks: {
title: ([{dataIndex}]) => data.labels[dataIndex]
}
},
legend: {
display: false,
}
},
scales: {
x: {
type: 'linear',
min: 0,
max: data.labels.length,
ticks: {
stepSize: 0.5,
callback: (value) => {
const idx = Math.floor(value),
label = data.labels[idx];
if(Math.abs(Math.round(value) - value) < 1e-6){
if(label === data.labels[idx - 1]){
return label;
}
else{
return null;
}
}
else{
if(label === data.labels[idx - 1] || label === data.labels[idx + 1]){
return null;
}
return label;
}
}
}
},
x2:{
type: "linear",
min: 0,
max: data.labels.length,
ticks: {
display: false,
stepSize: 0.5,
callback(value, ...args){
if(value === this.max || value === this.min){
return '';
}
if(Math.abs(Math.round(value) - value) < 1e-6){
const idx = Math.floor(value),
label = data.labels[idx];
if(label === data.labels[idx - 1]){
return null;
}
else{
return '';
}
}
else{
return null;
}
}
},
grid: {
color: 'rgba(0,0,0,0.4)',
drawTicks: false
//offset: true
}
},
y: {
beginAtZero: true,
//display: false,
},
},
};
new Chart('myChart', {type: 'line', options, data});
<canvas style="height: 160px" id="myChart"></canvas>
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>