I am trying to plot my data with Chart.js. The labels on the x-axis are Date object:
const labels = [
"1900-07-01T00:00:00.000Z",
"1900-07-02T00:00:00.000Z",
"1900-07-03T00:00:00.000Z",
"1900-07-04T00:00:00.000Z",
"1900-07-05T00:00:00.000Z",
... ...
"1900-12-28T00:00:00.000Z",
"1900-12-29T00:00:00.000Z",
"1900-12-30T00:00:00.000Z",
"1900-12-31T00:00:00.000Z"
]
The labels on the x axis should be only months e.g. July, August, … …, December. What I have achieved is:
The config for the chart is:
const data = await getDataFromS3();
const config = {
type: 'line',
options: {
animation: true,
responsive: true,
scales: {
x: {
type: 'time',
time: {
unit: 'month',
displayFormats: {
month: 'MMM'
}
}
}
},
plugins: {
legend: {
display: true
},
tooltip: {
enabled: true
}
}
},
data: {
labels: data['labels'],
datasets: data['datasets']
}
};
new Chart(
document.getElementById('acquisitions'),
config
);
For some reason, Jul is missing from the labels. Also I would like to make the months placed in the center of each segment as below:
The default behavior is to mark the tick label where the month begins. I don’t think there is an option that
changes that behavior. Fortunately, we have the axes callbacks,
where we can use for instance afterBuildTicks
to filter ticks according to our criteria.
In particular, we may set the time.unit
to something smaller than month
, i.e., day
(week
also works but results in
ticks that are not equidistant), then find those ticks that are closest to the 15th of each month and filter out the others:
options: {
// .... other options
scales: {
x: {
type: 'time',
time: {
unit: 'day',
displayFormats: {
day: 'MMM'
}
},
afterBuildTicks(ax){
const ticks = ax.ticks,
date_start = new Date(ticks[0].value),
mo_start = date_start.getMonth();
// group ticks by month => array of arrays
const monthGroups = ticks.reduce(({g, last_mo}, tick, index) => {
const mo = new Date(tick.value).getMonth();
if(mo === last_mo){
g[g.length-1].push({tick, index})
return ({g, last_mo})
}
else{
g.push([{tick, index}]);
return ({g, last_mo: mo});
}
}, {g: [[]], last_mo: mo_start}).g;
// find, for each month, the index of the tick closest to the 15th of that month
const midTicks = monthGroups.map(
(group) => {
// sort the group by the distance from the 15th of that month
group.sort(({tick: {value: date1}}, {tick: {value: date2}}) =>
Math.abs(new Date(date1).getDate() - 15) - Math.abs(new Date(date2).getDate() - 15));
return group[0].index;
}
);
// filter the ticks, keeping only one in each month, the closest to the 15th
ax.ticks = ticks.filter((tick, index) => midTicks.includes(index));
}
}
},
// .... other options
}
Included in a snippet:
const labels = Array.from({length: 184}, (_, i)=> (
new Date(Date.parse(`1900-07-01T00:00:00.000Z`) + i * 24 * 3600 * 1000)
).toISOString());
const data = {
labels,
datasets:[{
label: 'RAIN_1',
data: Array.from({length: labels.length}, (_, i) => i**2/60 + Math.round(20 * Math.random()))
}]
}
const config = {
type: 'line',
data,
options: {
animation: true,
responsive: true,
scales: {
x: {
type: 'time',
time: {
unit: 'day',
displayFormats: {
day: 'MMM'
}
},
afterBuildTicks(ax){
const ticks = ax.ticks,
date_start = new Date(ticks[0].value),
mo_start = date_start.getMonth();
// group ticks by month => array of arrays
const monthGroups = ticks.reduce(({g, last_mo}, tick, index) => {
const mo = new Date(tick.value).getMonth();
if(mo === last_mo){
g[g.length-1].push({tick, index})
return ({g, last_mo})
}
else{
g.push([{tick, index}]);
return ({g, last_mo: mo});
}
}, {g: [[]], last_mo: mo_start}).g;
// find, for each month, the index of the tick closest to the 15th of that month
const midTicks = monthGroups.map(
(group) => {
// sort the group by the distance from the 15th of that month
group.sort(({tick: {value: date1}}, {tick: {value: date2}}) =>
Math.abs(new Date(date1).getDate() - 15) - Math.abs(new Date(date2).getDate() - 15));
return group[0].index;
}
);
// filter the ticks, keeping only one in each month, the closest to the 15th
ax.ticks = ticks.filter((tick, index) => midTicks.includes(index));
}
}
},
plugins: {
legend: {
display: true
},
tooltip: {
enabled: true
}
}
}
};
new Chart("canvas", config);
<div id="container" style="height: 240px;">
<canvas id="canvas"></canvas>
</div>
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
<script src="https://cdn.jsdelivr.net/npm/chartjs-adapter-date-fns/dist/chartjs-adapter-date-fns.bundle.min.js"></script>
An alternative to changing the time.unit
is to use the option scales.x.ticks.source: "labels"
, which will use all
the labels in the afterBuildTicks
call.