Chart.js xaxis formatter changes the value shown in the chart - chart.js

I'm creating a simple visualization that shows the evolution of the Temperature in the past 24h. It is shown in the picture.
In the x axis I'm showing the hours and I'm trying to format them so that 10 -> 10:00, and so on.
However, when I include the ticks.callback in the options, the chat shows as follows: it always starts at 0:00 while the correct chat, at this particular time starts at 12.
ticks: {
callback: function(value, index, ticks) {
return `${value}:00`;
}
}
As you can see I've been able to format the y axis, but there is something different with the x axis.
This is the whole configuration of the chart, in case the error is there:
const labels = this.extractLabels(this.props.data);
const dataPoints = this.extractData(this.props.data);
const data = {
labels: labels,
datasets: [{
label: 'Avg. Temperature',
data: dataPoints,
fill: false,
borderColor: 'rgb(75, 192, 192)',
tension: 0.1
}]
};
const options = {
scales: {
y: {
beginAtZero: true,
grace: '5%',
title: {
display: true,
text: 'Temperature',
font: {
size: 16
}
},
ticks: {
callback: function(value, index, ticks) {
return value + ' ÂșC';
}
}
},
x: {
title: {
display: true,
text: 'Date',
font: {
size: 16
}
},
ticks: {
callback: function(value, index, ticks) {
return `${value}:00`;
}
}
}
}
};
const config = {
type: 'line',
data: data,
options: options,
};
this.temperatureChart = new Chart(this.chartRef.current, config);

As per the tip in the docs:
The category axis, which is the default x-axis for line and bar charts, uses the index as internal data format. For accessing the label, use this.getLabelForValue(value). API: getLabelForValue
so to get the right display value your callback needs to be this:
ticks: {
callback: function(value, index, ticks) {
return `${this.getLabelForValue(value)}:00`;
}
}

You can edit the labels, but in my opinion a better solution would be to implement a Time cartesian axis. This would allow you to add more data without making changes to the labels. You would need to include a time adapter to make it work.
config:
const config = {
type: 'line',
data: data,
options: {
scales: {
xAxis: {
type: 'time',
ticks: {
source: 'labels', // get ticks from given labels
},
time: {
minUnit: 'minute', // smallest time format
displayFormats: {
minute: "HH:mm",
hour: "dd/MM HH:mm",
day: "dd/MM",
week: "dd/MM",
month: "MMMM yyyy",
quarter: 'MMMM yyyy',
year: "yyyy",
}
}
},
},
}
};
Here is a fiddle to show you how this would look: JSFiddle

Related

How to use Chartjs to plot a single row of colored bars with a time based x axis

I have some time based data I want a graphical representation of, and was hoping to use Chartjs to plot this.
The data looks something like the following:
Time State
--------------
7am up
9am down
10.45am out
17.35 up
Also, each "state" will have its own color, so I would use this as a bar color when using a bar graph
up = red
down = yellow
out = green
The end result I am after is a simple one row bar like the following...
I thought I may be able to use a Chartjs horizontal stacked bar chart (https://www.chartjs.org/docs/latest/charts/bar.html#horizontal-bar-chart) to do this somehow, but I just can't work out how to get this working.
Some (not working) experimental code is as follows:
private createChart(): void {
if (this.chart !== undefined) {
return;
}
Chart.register(BarController, PointElement, Tooltip, Legend, TimeScale, LinearScale, CategoryScale, LinearScale, BarElement);
const options: ChartOptions = {
plugins: {
legend: {
display: false,
},
title: {
display: false,
},
},
indexAxis: 'y',
responsive: true,
maintainAspectRatio: false,
scales: {
y: {
stacked: true,
type: 'time',
display: true,
// position: 'bottom',
time: {
unit: 'minute',
displayFormats: {
minute: 'h:mm a'
}
}
},
x: {
stacked: false,
}
}
};
this.chart = new Chart(this.canvasRef.nativeElement, {
type: 'bar',
data: this.chartData,
options
});
this.chart.config.options.scales.y.min = new Date('2023-02-13T06:19:31.842Z').getTime();
const labels = [
'up',
'down'
// 'Dataset 2'
];
const d1 = new Date('2023-02-13T06:20:32.842Z').getTime();
const d2 = new Date('2023-02-13T06:21:33.842Z').getTime();
this.chartData = {
labels,
datasets: [{
label: 'up',
data: [{x: 10, y: d1}],
backgroundColor: 'red',
},
{
label: 'down',
data: [{x: 20, y: d2}],
backgroundColor: 'green',
}
]
};
this.chart.update();
}
In the above I have tried various combinations of labels, x values, y values, data shapes, but I only even get an empty graph.
Perhaps this is not really possible (I am trying to use the wrong component).
How can I achieve this using chartjs?
Update
Using example from #winner_joiner below, I have put a copy of it at plunkr and have tried to use the time in the x axis, but can see it is still not plotting the bars using the dates as the length
Well your code basically works, here a slightly modified version of your code.
After your comments and updated question, I reworke the example (seen below). Although it is possible to do with chart.js the question is, maybe for this specific task a different library or solution would be better/more convenient.
Update Chart, with some similar values from your question:
(I'm using here momentjs, since it is recommend usually needed form date/time actions in chartjs, as mentioned in the documentation)
const d0 = moment.duration('07:00:00').asMinutes();
const d1 = moment.duration('09:00:00').asMinutes();
const d2 = moment.duration('10:45:00').asMinutes();
const d3 = moment.duration('17:35:00').asMinutes();
const d4 = moment.duration('19:00:00').asMinutes();
let values = [d0, d1, d2, d3, d4];
let data = {
labels: [''],
datasets: [{
label: 'up',
axis: 'y',
data: [d1],
backgroundColor: 'red',
},{
label: 'down',
axis: 'y',
data: [d2],
backgroundColor: 'yellow',
},{
label: 'out',
axis: 'y',
data: [d3],
backgroundColor: 'green',
},{
label: 'up',
axis: 'y',
data: [d4],
backgroundColor: 'red',
}
]
};
const config = {
data,
type: 'bar',
options:{
plugins: {
tooltip: {
mode: 'dataset',
callbacks: {
label: function(item){
return moment().startOf('day').add({ minute: item.raw}).format('HH:mm');
}
}
},
legend: {
display: false,
},
title: {
display: false,
},
},
indexAxis: 'y',
responsive: true,
maintainAspectRatio: false,
scales: {
x: {
min: d0,
ticks: {
callback: function(value, index, ticks) {
return moment().startOf('day').add({ minute: value}).format('HH:mm');
}
},
afterBuildTicks: axis => axis.ticks = values.map(v => ({ value: v }))
},
y: {
stacked: true
},
}
}};
new Chart(document.getElementById("chart"), config);
<script src="//cdn.jsdelivr.net/npm/chart.js"></script>
<script src="//cdn.jsdelivr.net/npm/moment#^2"></script>
<script src="//cdn.jsdelivr.net/npm/chartjs-adapter-moment#^1"></script>
<div class="chart" style="height:184px; width:350px;">
<canvas id="chart" ></canvas>
</div>

Stacking area line charts with different x-values in data

We use the chart-js-react to build area charts in our service. Our chart will display logs information which is time-based data like, {x:"2022-06-27T21:00:00Z", y:25}.
Since the time could be a random value in each data point, so there are some issue when we want to display a few series with different X-values(different time), as shown in the image. We got overlapping and blank area when the X-value(timestamp) is missing in another series.
Update: checked this Issue: https://github.com/chartjs/Chart.js/issues/7695. I notice they did have some missing/ different X-value. but looks the x-value is not random. when I made the x-values mis-match. same issue: https://codepen.io/derrickwanglf/pen/RwMRMXM
TO Simply demonstrate our issue, I mock some data(real number as x-value) here.
https://codepen.io/derrickwanglf/pen/gOeMvEL
Do you know how can we stack the area chart even the x-values are random?
d0 = [{x:1, y:1}, {x:2, y:11},{x:3, y:1},{x:4, y:11},{x:5, y:10}, {x:6, y:8},{x:7, y:15},{x:8, y:2}]
d1= [{x:1, y:1}, {x:2, y:5}, {x:4, y:1}, {x:5, y:3}, {x:5.5, y:4},{x:7, y:5},{x:8, y:3}]
const ctx = document.getElementById("chart").getContext("2d");
const chart = new Chart(ctx, {
type: "line",
data: {
datasets: [
{
backgroundColor: "rgba(50,200,50,0.6)",
borderColor: "rgba(50,200,50,0.6)",
data: d0,
fill: "stack"
},
{
backgroundColor: "rgba(200,50,50,0.6)",
borderColor: "rgba(200,50,50,0.6)",
data: d1,
fill: "stack"
}
]
},
options: {
scales: {
x: {
type: "linear"
},
y: {
stacked: true,
}
},
elements: {
line: {
tension: 0.2
},
point: {
radius: 0
}
},
tooltips: {
mode: "nearest",
intersect: false,
axis: "x"
},
hover: {
mode: "nearest",
intersect: false,
axis: "x"
}
}
});

Conditional in ChartJS axes tick callback function isn't returning the expected labels

I have a chart containing data for each day of the year and I'm wanting to show the x-axis simply as months.
I've set up the following callback function which (crudely) grabs the month from the set of labels, checks to see whether it already exists and if not, returns it as an axis label
let rollingLabel;
...
function(label, index, labels) {
let _label = label.replace(/[0-9]/g, '');
if (rollingLabel != _label) {
rollingLabel = _label;
return rollingLabel;
}
}
However, it's only returning two of the expected four labels.
What's confusing me more is that if I add console.log(rollingLabel) within the conditional I can see that the variable is updating how I'd expect but it's not returning the value, or it is and the chart isn't picking it up for whatever reason. Even more confusing is that if I uncomment line 48 // return _label the chart updates with all the labels so I don't believe it's an issue with max/min settings for the chart.
If anyone has any ideas I'd be most grateful. I've been staring at it for hours now!
The expected output for the below snippet should have the following x-axis labels:
Aug | Sep | Oct | Nov
const canvas = document.getElementById('chart');
const ctx = canvas.getContext('2d');
let data = [
1,6,3,11,5,1,2,6,2,10,5,8,1,1,2,4,5,2,3,1
];
let labels = [
"Aug 1","Aug 2","Aug 3","Aug 4","Aug 5","Sep 1","Sep 2","Sep 3","Sep 4","Sep 5","Oct 1","Oct 2","Oct 3","Oct 4","Oct 5","Nov 1","Nov 2", "Nov 3","Nov 4","Nov 5"
];
let rollingLabel;
chart = new Chart(ctx, {
type: "line",
data: {
datasets: [
{
backgroundColor: '#12263A',
data: data,
pointRadius: 0
}
],
labels: labels,
},
options: {
legend: {
display: false
},
responsive: false,
scales: {
xAxes: [
{
gridLines: {
display: false
},
ticks: {
display: true,
autoSkip: true,
callback: function(label, index, labels) {
let _label = label.replace(/[0-9]/g, '');
if (rollingLabel != _label) {
rollingLabel = _label;
return rollingLabel;
}
// return _label;
}
}
}
]
},
tooltips: {
mode: "index",
intersect: false
},
hover: {
mode: "index",
intersect: false
}
}
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/2.9.3/Chart.min.js"></script>
<canvas id="chart"></canvas>
You need to define ticks.autoSkip: false on the x-axis to make it work as expected:
autoSkip: If true, automatically calculates how many labels can be shown and hides labels accordingly. Labels will be rotated up to maxRotation before skipping any. Turn autoSkip off to show all labels no matter what.
Please take a look at your amended code below:
let data = [
1,6,3,11,5,1,2,6,2,10,5,8,1,1,2,4,5,2,3,1
];
let labels = [
"Aug 1","Aug 2","Aug 3","Aug 4","Aug 5","Sep 1","Sep 2","Sep 3","Sep 4","Sep 5","Oct 1","Oct 2","Oct 3","Oct 4","Oct 5","Nov 1","Nov 2", "Nov 3","Nov 4","Nov 5"
];
let rollingLabel;
chart = new Chart('chart', {
type: "line",
data: {
datasets: [
{
backgroundColor: '#12263A',
data: data,
pointRadius: 0
}
],
labels: labels,
},
options: {
legend: {
display: false
},
responsive: false,
scales: {
xAxes: [
{
gridLines: {
display: false
},
ticks: {
display: true,
autoSkip: false,
callback: function(label, index, labels) {
let _label = label.replace(/[0-9]/g, '');
if (rollingLabel != _label) {
rollingLabel = _label;
return rollingLabel;
}
}
}
}
]
},
tooltips: {
mode: "index",
intersect: false
},
hover: {
mode: "index",
intersect: false
}
}
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/2.9.3/Chart.min.js"></script>
<canvas id="chart"></canvas>
I found a easy solution via the chart.js documentation.
const config = {
type: 'line',
data: data,
options: {
responsive: true,
plugins: {
title: {
display: true,
text: 'Chart with Tick Configuration'
}
},
scales: {
x: {
ticks: {
// For a category axis, the val is the index so the lookup via getLabelForValue is needed
callback: function(val, index) {
// Hide the label of every 2nd dataset
return index % 2 === 0 ? this.getLabelForValue(val) : '';
},
color: 'red',
}
}
}
},
};
The callback function decides what labels will be shown. Current setup shows every 2nd label, if you want to show every 3rd for example you would change:
return index % 2 === 0 ? this.getLabelForValue(val) : '';
to:
return index % 3 === 0 ? this.getLabelForValue(val) : '';

Is it possible to define data attributes for each dataset value in a Chart.js chart?

Is it possible to define HTML5 data attributes for each dataset value in a Chart.js chart? The goal is to enhance the default tooltip information (e.g. X-axis value & Y-axis value) with additional information related to the target dataset value.
In the example below, a chart is displayed that plots the total number of diapers used by 6 children. When the tooltip displays, it says the name of the child and the number of diapers. I would like the tooltip to display age and gender, as well, and figured the tooltip callback could get these from data attributes attached to each dataset value.
I've reviewed the Chart.js documentation and searched online forums, like StackOverflow, without success.
var myChart = new Chart(document.getElementById('myChart'), {
type: 'bar',
data: {
labels: ['Gabriel', 'Zelie', 'Santiago', 'Serafina', 'Berenice', 'Pia'],
datasets: [{ label: 'Diapers', data: [1050, 1224, 1382, 1166, 1471, 0] }]
},
options: {
legend: { display: false },
scales: { yAxes: [{ ticks: { beginAtZero: true } }] },
title: { display: true, text: 'Total Diapers Used' },
tooltips: {
titleFontSize: 18,
callbacks: {
label: function(tooltipItem, data) {
var defaultLabel = data.datasets[tooltipItem.datasetIndex].label + ': ' + tooltipItem.yLabel;
var age = "Age: <age>";
var gender = "Gender: <gender>";
var labels = [defaultLabel, age, gender];
return labels;
}
}
}
}
});
</script>
It depends how you structure your data. In my example I made an object that contains all the data.
let dataObject = {
Gabriel: {
diapers: 40,
age: 4
},
// Same structure here...
Zelie: {...},
Santiago: {...},
Serafina: {...},
Berenice: {...},
Pia: {...}
}
This way you can easily access your data in your tooltip with
tooltips: {
callbacks: {
label: function(tooltipItem, data){
return ['Name: '+tooltipItem.label, 'Diapers: '+tooltipItem.value, 'Age: '+dataObject[tooltipItem.label].age]
}
}
}
You should get all the information you need in the jsbin.
https://jsbin.com/qiyeyohavo/1/edit?html,js,output

Format Y axis of Chart.JS as Time

I have this chart below, I want to scale the Y axis using the TIME (the time isn't time of day it is total hours,mins, seconds so could be over 24 hours) just can't seem to get it to work, all I get is a blank screen, sure it's a syntax error but can't spot it! Thanks
var ctx = document.getElementById("myChart3").getContext('2d');
var myChart3 = new Chart(ctx, {
type: 'line',
data: {
labels: ["2010", "2011", "2012", "2013", "2014", "2015", "2016", "2017"],
datasets: [
{
label: "Time",
backgroundColor: ["#3e95cd", "#8e5ea2", "#3cba9f", "#e8c3b9", "#c45850", "#565452", "#321456", "#129864", "#326812", "#215984"],
data: ["11:46:07", "11:41:14", "11:55:26", "12:14:58", "11:54:55", "11:54:04", "12:28:29", "12:35:18"]
}
]
},
options: {
scales: {
yAxes: [{
type: 'time',
time: {
displayFormats: {
minutes: 'h:mm:ss a'
}
}
}]
}
}
});
Time scale works only for X axis.
It can only be placed on the X axis.
But for Y you can use a linear scale and express each time as date in milliseconds since 1970-01-01 (how the usual Date object does).
PLUNKER or use the following example:
$(function(){
const ctx = document.getElementById('myChart').getContext('2d');
let years = ["2010", "2011", "2012", "2013", "2014", "2015", "2016", "2017"];
let times = ["11:46:07", "11:41:14", "11:55:26", "12:14:58", "11:54:55", "11:54:04", "12:28:29", "12:35:18"];
let data = years.map((year, index) => ({
x: moment(`${year}-01-01`),
y: moment(`1970-02-01 ${times[index]}`).valueOf()
}));
let bckColors = ["#3e95cd", "#8e5ea2", "#3cba9f", "#e8c3b9", "#c45850", "#565452", "#321456", "#129864", "#326812", "#215984"];
let myChart = new Chart(ctx, {
type: 'line',
data: {
datasets: [
{
label: "Time",
backgroundColor: 'rgba(188, 229, 214, 0.7)',
pointBackgroundColor: bckColors,
data: data,
pointBorderWidth: 2,
pointRadius: 5,
pointHoverRadius: 7
}
]
},
options: {
scales: {
xAxes: [
{
type: 'time',
position: 'bottom',
time: {
displayFormats: {
years: 'YYYY'
},
unit: 'year'
}
}
],
yAxes: [
{
type: 'linear',
position: 'left',
ticks: {
min: moment('1970-02-01 00:00:00').valueOf(),
max: moment('1970-02-01 23:59:59').valueOf(),
stepSize: 3.6e+6,
beginAtZero: false,
callback: value => {
let date = moment(value);
if(date.diff(moment('1970-02-01 23:59:59'), 'minutes') === 0) {
return null;
}
return date.format('h A');
}
}
}
]
}
}
});
});
<html>
<head>
<script data-require="jquery" data-semver="3.1.1" src="https://ajax.googleapis.com/ajax/libs/jquery/3.1.1/jquery.min.js"></script>
<script src="https://npmcdn.com/moment#2.14.1"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/2.5.0/Chart.min.js"></script>
</head>
<body>
<canvas id="myChart" width="500" height="300"></canvas>
</body>
</html>
Explanation
You can have the years on X axis which can be a linear, time or category scale.
In this example X axis is a time scale.
The following code is used to generate values for X and Y axis:
let data = years.map((year, index) => ({
x: moment(`${year}-01-01`),
y: moment(`1970-02-01 ${times[index]}`).valueOf()
}));
For X axis I used moment js to create a date on the first day of the corresponding year.
For Y axis I used moment js to create the date in milliseconds since 1970-01-01. In this case all hours are combined with a day to form a date. 1970-02-01 in order to prevent an edge cases that may happen for 1970-01-01. Then these milliseconds, since 1970-01-01, are used with the Y axis linear scale.
Y axis tick.callback is used to format the corresponding milliseconds to an hour. Thus using the format h A to obtain for example 1 AM, 1 PM, 12 AM, 12 PM, ....