How to Create a Custom Logarithmic Axis in Chart.js - chart.js

I'm trying to refactor an old Flex app using Chart.js and I would like to replicate this linear/log axis. Notice the clean minimal spacing on the yAxis.
Here is my Chart.js code.
var mtbsoData = [];
var mtbsoLables = [];
for (let i = 0; i <= 10; i++) {
mtbsoData.push(i * i * i * i);
mtbsoLables.push(i.toString());
}
var ctxMtbso = document.getElementById("chartMtbso").getContext('2d');
var chartMtbso = new Chart(ctxMtbso, {
type: 'bar',
data: {
labels: mtbsoLables,
datasets: [{
label: 'label',
data: mtbsoData,
backgroundColor: 'rgba(0, 0, 255, .6)',
borderColor: 'rgba(0, 0, 255, 1)',
borderWidth: 1
}]
},
options: {
title: {
text: 'Title',
display: true,
fontSize: 24
},
scales: {
xAxes: [{
display: true,
ticks: {
beginAtZero: true
},
scaleLabel: {
display: true,
labelString: 'X Axis'
}
}],
yAxes: [{
type: 'logarithmic',
ticks: {
min: 0,
max: 1000,
callback: function (value, index, values) {
return value + ' years';
}
},
scaleLabel: {
display: true,
labelString: 'Y Axis'
}
}]
},
tooltips: {
callbacks: {
label: function (tooltipItem, data) {
var label = data.datasets[tooltipItem.datasetIndex].label || '';
if (label) {
label += ': ';
}
label += tooltipItem.yLabel.toFixed(2);
return label;
}
}
},
legend: {
display: false
}
}
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/2.7.2/Chart.bundle.min.js"></script>
<canvas id="chartMtbso"></canvas>
Here's a screenshot. Notice the crowded ticks on the yAxis.
It seems from the documentation that the only ticks properties that you can apply are the "min" and "max" properties.
Can anybody suggest a solution?

I fixed it.
Modify the yAxes callback as shown:
yAxes: [{
type: 'logarithmic',
ticks: {
autoSkip: true,
min: 0,
callback: function (value, index, values) {
if( value==10 || value==100 || value==1000 || value==10000 || value==100000 || value==1000000){
return value + ' years';
}
}
},
scaleLabel: {
display: true,
labelString: 'Mean Time Between Stock-Out'
}
}]
Then you only return a tick if the value is a power of ten.

Related

How to show label for second y-axis in charts.js?

I have two y-axis in my charts.js scatter chart, and all my data is displayed correctly.
However, whilst the label for the first y-axis is showing, the label for the second y-axis is not.
Here's my code:
window.laChart = new Chart(ctx, {
type: 'scatter',
data: {
datasets: allDatasets
},
options: {
scales: {
xAxes: [{
scaleLabel: {
display: true,
labelString: X_AXIS_LABEL
},
ticks: {
min: 0,
max: 18,
stepSize: 1,
// Format the x-axis values
callback: function(value, index, values) {
return value;// + ".000";
}
},
gridLines: {
display: false
}
}],
yAxes: [
{
ticks: {
beginAtZero: true,
min: 0,
max: 100
},
position: "left",
id: "y-axis-1",
type: "linear"
},
{
ticks: {
beginAtZero: true,
min: 0,
suggestedMax: 10
},
position: "right",
id: "y-axis-2",
type: "linear"
},
{
gridLines: {
display: false
},
ticks: {
maxTicksLimit: 3, // This will give us a top tick, middle tick and bottom tick.
callback: function(value, index) {
if (index === 1) { // This is the middle tick.
return Y1_AXIS_LABEL;
}
else {
return '';
}
}
}
}]
}
}
And here's a screenshot with the second y-axis label missing:
How do I make the label for the second y-axis appear?
SOLUTION
LeeLenalee's answer worked, but it left me with rotated labelStrings. I adapted his answer to solve this issue with this final code:
yAxes: [
{
ticks: {
beginAtZero: true,
min: 0,
max: 100
},
scaleLabel: {
display: false,
labelString: Y1_AXIS_LABEL
},
position: "left",
id: "y-axis-1",
type: "linear"
},
{
ticks: {
beginAtZero: true,
min: 0
},
scaleLabel: {
display: false,
labelString: Y2_AXIS_LABEL
},
position: "right",
id: "y-axis-2",
type: "linear"
},
// Change the orientation of the first y-axis label.
{
gridLines: {
display: false
},
ticks: {
maxTicksLimit: 3, // This will give us a top tick, middle tick and bottom tick.
callback: function(value, index) {
if (index === 1) { // This is the middle tick.
return Y1_AXIS_LABEL;
}
else {
return '';
}
}
},
position: "left"
},
// Change the orientation of the second y-axis label.
{
gridLines: {
display: false
},
ticks: {
maxTicksLimit: 3, // This will give us a top tick, middle tick and bottom tick.
callback: function(value, index) {
if (index === 1) { // This is the middle tick.
return Y2_AXIS_LABEL;
}
else {
return '';
}
}
},
position: "right"
}
]
Final screenshot:
What you did is implement a new scale as replacement for the scale label, this is not the way you should do it because the scale will only be on the left. You have to use the scalelabel as you did with the X axis for both yAxes like this:
yAxes: [
{
ticks: {
beginAtZero: true,
min: 0,
max: 100
},
scaleLabel: {
display: true,
labelString: Y1_AXIS_LABEL
},
position: "left",
id: "y-axis-1",
type: "linear"
},
{
ticks: {
beginAtZero: true,
min: 0,
suggestedMax: 10
},
scaleLabel: {
display: true,
labelString: Y2_AXIS_LABEL
},
position: "right",
id: "y-axis-2",
type: "linear"
}]

Display character after y-axis data value in onhover pop up Chart.js

I'm new to Chart.js and I'm having a little bit of trouble customizing the onHover() pop up. I want to add a percent symbol after the y axis data value on the pop up, so what would be displayed will look something like this, label: value%. I have tried using scaleLabel and ticks with now luck.
Right now this is how it looks:
and here is my code:
let chart = new Chart(ctx, {
type: 'line',
data: {
labels: dates,
datasets: [
{
label: 'Casos confirmados',
backgroundColor: 'rgb(224, 224, 224)',
borderColor: 'rgb(0, 0, 0)',
data: confirmedCases
}
]
},
options: {
scales: {
yAxes: [{
gridLines: {
drawTicks: true,
},
ticks: {
display : true,
// Include a percent sign in the ticks
callback: function(value, index, values) {
return value + '%';
}
},
scaleLabel: {
display: true,
labelString: '%'
}
}]
}
}
});
If anyone could give me any pointers I would greatly appreciate it.
You can define tooltips.callbacks.label function inside the chart options as follows:
tooltips: {
callbacks: {
label: (tooltipItem, data) => data.datasets[tooltipItem.datasetIndex].label + ': ' + tooltipItem.yLabel + '%'
}
},
Please take a look at your amended code and see how it works.
let chart = new Chart('myChart', {
type: 'line',
data: {
labels: ['A', 'B', 'C', 'D', 'E', ],
datasets: [{
label: 'Casos confirmados',
backgroundColor: 'rgb(224, 224, 224)',
borderColor: 'rgb(0, 0, 0)',
data: [5, 6, 4, 3, 6]
}]
},
options: {
tooltips: {
callbacks: {
label: (tooltipItem, data) => data.datasets[tooltipItem.datasetIndex].label + ': ' + tooltipItem.yLabel + '%'
}
},
scales: {
yAxes: [{
ticks: {
beginAtZero: true,
callback: value => value + '%'
},
scaleLabel: {
display: true,
labelString: '%'
}
}]
}
}
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/2.9.4/Chart.min.js"></script>
<canvas id="myChart" height="90"></canvas>

Always show last tooltip on all datasets, leave the rest to display on hover? ChartJS

I'm a little stuck on how I would achieve this since it's a little unorthodox in relation to what the plugin does.
I'm attempting for each dataset to keep the last tooltip open at all times and leave the rest to be displayed on hover.
Currently, I'm using this to keep them all displayed:
Chart.plugins.register({
beforeRender: function (chart) {
if (chart.config.options.showAllTooltips) {
chart.pluginTooltips = [];
chart.config.data.datasets.forEach(function (dataset, i) {
console.log(chart);
chart.getDatasetMeta(i).data.forEach(function (sector, j) {
console.log(sector);
chart.pluginTooltips.push(new Chart.Tooltip({
_chart: chart.chart,
_chartInstance: chart,
_data: chart.data,
_options: chart.options.tooltips,
_active: [sector]
}, chart));
});
});
chart.options.tooltips.enabled = false;
}
},
afterDraw: function (chart, easing) {
if (chart.config.options.showAllTooltips) {
if (!chart.allTooltipsOnce) {
if (easing !== 1) {
return;
}
chart.allTooltipsOnce = true;
}
chart.options.tooltips.enabled = true;
Chart.helpers.each(chart.pluginTooltips, function (tooltip) {
tooltip.initialize();
tooltip.update();
tooltip.pivot();
tooltip.transition(easing).draw();
});
chart.options.tooltips.enabled = false;
}
}
});
Is there a way to only do this for the last tooltip in each dataset?
I've come across a way to hide tooltips:
filter: function (tooltipItem, data) {
var label = data.labels[tooltipItem.index];
if (label === labels[labels.length - 1]) {
console.log(tooltipItem);
} else {
return true;
}
}
But this just doesn't display them whatsoever since it's returning false.
Ultimately it will look like this, with the other tooltips displaying on hover of the corresponding node:
Any help greatly appreciated.
I would try to use chartjs-plugin-datalabels for such task.
You can try it out directly on their pages - scriptable interactions
setup
Setting some random data:
var DATA_COUNT = 8;
var labels = [];
Utils.srand(100);
for (var i = 0; i < DATA_COUNT; ++i) {
labels.push('' + i);
}
var DATA_COUNT = 8;
var labels = [];
Utils.srand(100);
for (var i = 0; i < DATA_COUNT; ++i) {
labels.push('' + i);
}
config
Here I did some changes
{
type: 'line',
data: {
labels: labels,
datasets: [{
label: 'France',
backgroundColor: Utils.color(0),
borderColor: Utils.color(0),
data: Utils.numbers({
count: DATA_COUNT,
min: 10,
max: 100
}),
datalabels: {
align: function(context) {
return context.active ? 'start' : 'center';
}
}
}, {
label: 'Canada',
backgroundColor: Utils.color(1),
borderColor: Utils.color(1),
data: Utils.numbers({
count: DATA_COUNT,
min: 0,
max: 100
})
}, {
label: 'USA',
backgroundColor: Utils.color(2),
borderColor: Utils.color(2),
data: Utils.numbers({
count: DATA_COUNT,
min: 0,
max: 100
}),
datalabels: {
align: function(context) {
return context.active ? 'end' : 'center';
}
}
}]
},
options: {
plugins: {
datalabels: {
backgroundColor: function(context) {
return context.active ? context.dataset.backgroundColor : 'white';
},
borderColor: function(context) {
return context.dataset.backgroundColor;
},
borderRadius: function(context) {
return context.active ? 0 : 1;
},
borderWidth: 1,
color: function(context) {
return context.active ? 'white' : context.dataset.backgroundColor;
},
font: {
weight: 'bold'
},
formatter: function(value, context) {
value = Math.round(value * 100) / 100;
if (context.dataIndex === context.dataset.data.length - 1) {
return context.dataset.label + '\n' + value + '%';
} else {
return context.active
? context.dataset.label + '\n' + value + '%'
: ''
}
},
offset: 8,
padding: 0,
textAlign: 'center'
}
},
// Core options
aspectRatio: 5 / 3,
layout: {
padding: {
bottom: 16,
right: 40,
left: 8,
top: 40
}
},
hover: {
mode: 'index',
intersect: false
},
elements: {
line: {
fill: false
}
},
scales: {
yAxes: [{
stacked: true
}]
}
}
}
The main part here is:
formatter: function(value, context) {
value = Math.round(value * 100) / 100;
if (context.dataIndex === context.dataset.data.length - 1) {
return context.dataset.label + '\n' + value + '%';
} else {
return context.active
? context.dataset.label + '\n' + value + '%'
: ''
}
},
The formatter: where I show the label permanently only for context.dataIndex === context.dataset.data.length - 1.
In other cases if it is inactive I show only empty box in other case (hover) I show the information.
How does it look?
No hover
Hover with mouse
So this is the entire hmtl file with all relevant code. As I said, I am no programmer. This effort is from loads of trial and error, including several bits of code from stackoverflow questions.
<script>
$(document).ready(function () {
showtempGraph();
});
function showtempGraph()
{
{
$.post("temperaturedata.php",
function (data)
{
console.log(data);
var temptime = [];
var temp = [];
for (var i in data) {
temptime.push(data[i].timestamp);
temp.push(data[i].temperature);
}
var tempmin = Math.min(...temp);
var tempmax = Math.max(...temp);
var charttempdata = {
labels: temptime,
datasets: [
{
label: 'Temperatur',
pointRadius: 3,
backgroundColor: 'rgba(26, 137, 245, 0.2)',
borderColor: 'rgba(26, 137, 245, 1)',
hoverBackgroundColor: 'rgba(255, 255, 255, 1)',
hoverBorderColor: 'rgba(255, 255, 255, 1)',
pointBackgroundColor: 'rgba(12, 68, 122, 1)',
pointHoverBorderColor: "rgba(255, 255, 255, 0.8)",
data: temp
}
]
};
var graphtempTarget = $("#tempgraphCanvas");
var linetempGraph = new Chart(graphtempTarget, {
type: 'line',
data: charttempdata,
options: {
maintainAspectRatio: false,
tooltips: {
enabled: true,
custom: function(tooltip) {
if (!tooltip) return;
tooltip.displayColors = false;
},
callbacks: {
title: function(tooltipItems, tempdata) {
return 'Klokken: ' + tooltipItems[0].xLabel;
},
label: function(tooltipItem, tempdata) {
return 'Temperatur: ' + Number(tooltipItem.yLabel).toFixed(2) + '°C';
}
}
},
legend: {
display: false,
},
responsive: true,
scales: {
xAxes: [{
type: 'time',
time: {
displayFormats: {
hour: 'HH:mm'
},
tooltipFormat: 'HH:mm',
},
unit : 'day',
gridLines: {
color: '#999999',
lineWidth: 1
},
ticks: {
fontColor: "#fff",
}
}],
yAxes: [
{
type: 'linear',
position: 'left',
gridLines: {
color: '#999999',
lineWidth: 1
},
ticks: {
fontColor: "#fff",
}
}, {
type: 'linear',
position: "right",
afterUpdate: function(scaleInstance) {
console.dir(scaleInstance);
},
gridLines: {
color: '#999999)',
lineWidth: 0.5
},
ticks: {
stepSize: tempmax - tempmin,
min: tempmin,
max: tempmax,
mirror: true,
padding: -10,
fontColor: "#fff",
fontSize: 18,
callback: function(value) {
return value + '°C';
}
},
scaleLabel: {
display: true,
labelString: '°C',
fontColor: "#fff",
fontSize: 14
}
}]
},
}
});
});
}
}
</script>

How to put labels at 4 points of the day?

This question is two-fold (two ways to solve)
I want to make a chart that plots 24 hours worth of data, not starting at midnight, and with ticks (and associated grid) at the four cardinal points of the day.
I don't care if the x axis is 'time' or anything else, as long as it looks fine. I've seen a category chart with the labels arbitrarily shifted, but I can only reproduce this with one data point per label, and this needs multiple data points between the labels. If I label all the data points I don't know how to skip labels such that only "0:00', '6:00', '12:00', and '18:00' are visible.
It's supposed to look like this:
This is how far I've come:
function generateData() {
function randomNumber(min, max) {
return Math.random() * (max - min) + min;
}
function randomPoint(date) {
return {
t: date.valueOf(),
y: randomNumber(0, 100)
};
}
var date = moment("2000-01-01T"+"08:30");
var now = moment();
var data = [];
while (data.length <=48) {
data.push(randomPoint(date));
date = date.clone().add(30, 'minute')
}
return data;
}
var cfg = {
data: {
datasets: [{
label: 'CHRT - Chart.js Corporation',
backgroundColor: 'red',
borderColor: 'red',
data: generateData(),
type: 'line',
pointRadius: 0,
fill: false,
lineTension: 0,
borderWidth: 2
}]
},
options: {
scales: {
xAxes: [{
type: 'time',
time: {
round: 'minute',
unit: 'minute',
stepSize: 360,
displayFormats: {
minute: 'kk:mm'
}
}
}],
yAxes: [{
gridLines: {
drawBorder: false
}
}]
},
tooltips: {
intersect: false,
mode: 'index'
}
}
};
var chart = new Chart('chart1', cfg);
<script src="https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.18.1/moment.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/2.9.3/Chart.min.js"></script>
<canvas id="chart1" height="60"></canvas>
The problem here is that the first tick is at 08:30 not at 12:00.
I've tried to set the min value of the axis, but this either shifts part of the data out of view (min: moment("2000-01-01T12:00")), or creates a gap before the data starts (min: moment("2000-01-01T06:00")), both of which are unacceptable.
function generateData() {
function randomNumber(min, max) {
return Math.random() * (max - min) + min;
}
function randomPoint(date) {
return {
t: date.valueOf(),
y: randomNumber(0, 100)
};
}
var date = moment("2000-01-01T"+"08:30");
var now = moment();
var data = [];
while (data.length <=48) {
data.push(randomPoint(date));
date = date.clone().add(30, 'minute')
}
return data;
}
var cfg = {
data: {
datasets: [{
label: 'CHRT - Chart.js Corporation',
backgroundColor: 'red',
borderColor: 'red',
data: generateData(),
type: 'line',
pointRadius: 0,
fill: false,
lineTension: 0,
borderWidth: 2
}]
},
options: {
scales: {
xAxes: [{
type: 'time',
time: {
round: 'minute',
unit: 'minute',
stepSize: 360,
displayFormats: {
minute: 'kk:mm'
}
},
ticks: {
min: moment("2000-01-01T06:00"),
}
}],
yAxes: [{
gridLines: {
drawBorder: false
}
}]
},
tooltips: {
intersect: false,
mode: 'index'
}
}
};
var chart = new Chart('chart1', cfg);
<script src="https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.18.1/moment.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/2.9.3/Chart.min.js"></script>
<canvas id="chart1" height="60"></canvas>
Took me some time, but I think I got the answer.
You can define the ticks with and display them with options.scales.xAxes[0].ticks.source = 'labels', but the hardest part was to get the next timestamp of 0:00, 6:00, 12:00 or 18:00.
That's why the code for the first label is quite complex. If you want to know anything, just let me know and I can explain it.
Should work with any time you want.
Complete code (same as JSBin with preview here):
let data = {
datasets: [{
label: 'CHRT - Chart.js Corporation',
backgroundColor: 'red',
borderColor: 'red',
type: 'line',
pointRadius: 0,
pointHoverRadius: 0,
fill: false,
lineTension: 0,
borderWidth: 2,
data: []
}]
}
function randomNumber(min, max) {
return Math.round(Math.random() * (max - min) + min)
}
// temp variable for data.datasets[0].data
let datasetData = []
// Fill first dataset
datasetData[0] = {
x: moment("2000-01-01T08:30"),
y: randomNumber(0, 100)
}
// Fill remaining datasets
for (let i = 1; i < 48; i++) {
datasetData[i] = {
x: moment(datasetData[i-1].x).add(30, 'minutes'),
y: randomNumber(0, 100)
}
}
data.datasets[0].data = datasetData
// Fill first label
data.labels = [
moment(datasetData[0].x).hour(moment(datasetData[0].x).hour() + (6 - moment(datasetData[0].x).hour() % 6) % 6).minutes(0)
]
// Fill remaining labels
for (let i = 1; i < 4; i++) {
data.labels.push(moment(data.labels[i-1]).add(6, 'hours'))
}
let options = {
responsive: true,
scales: {
xAxes: [{
type: 'time',
time: {
unit: 'hour',
displayFormats: {
hour: 'kk:mm'
}
},
ticks: {
source: 'labels'
}
}],
yAxes: [{
gridLines: {
drawBorder: false
}
}]
},
tooltips: {
intersect: false,
mode: 'index',
callbacks: {
title: function(tooltipItem, data) {
return moment(tooltipItem[0].label).format('YYYY-MM-DD, HH:mm')
}
}
}
}
let chart = new Chart('chart1', {
type: 'line',
data: data,
options: options
});

ChartJS with multiple axis custom tooltip not working

I'm using the latest version of the chartJS on my Angular 5 application but when I try to call the custom tooltip using the options and the custom function nothing happens.
I added a console.log to see if the function gets called but I do not see it.
Here is my options object.
this.chartOptions = {
responsive: true,
maintainAspectRatio: false,
title: {
display: true,
text: '',
fontStyle: 'bold'
},
animation: {
duration: 0
},
hover: {
animationDuration: 0,
mode: 'nearest',
intersect: true
},
responsiveAnimationDuration: 0,
elements: {
line: {
tension: 0
},
point: {
radius: 5
}
},
scales: {
xAxes: [
{
scaleLabel: {
display: true,
labelString: 'Year',
fontStyle: 'bold'
}
}
],
yAxes: [
{
id: 'y-axis-1',
display: true,
position: 'left',
type: 'linear',
scaleLabel: {
display: true,
labelString: (this.chartUnit === 'U') ? 'Sales (Units)' : 'Sales (USD)',
fontStyle: 'bold'
},
stacked: false,
ticks: {
callback: function(value, index, values) {
const prefix = self.chartUnit === 'D' ? '$' : self.chartUnit === 'P' ? '%' : '';
const numValue = value.toLocaleString();
if (self.chartUnit === 'P') {
return numValue + prefix;
} else {
return prefix + numValue;
}
},
fontStyle: 'bold'
}
},
{
id: 'y-axis-2',
display: true,
position: 'right',
type: 'linear',
gridLines: {
display: true,
drawBorder: true,
drawOnChartArea: false
},
scaleLabel: {
display: true,
labelString: (this.chartUnit === 'U') ? 'Total Sales (Units)' : 'Total Sales (USD)',
fontStyle: 'bold'
},
stacked: false,
ticks: {
callback: function(value, index, values) {
const prefix = self.chartUnit === 'D' ? '$' : self.chartUnit === 'P' ? '%' : '';
const numValue = value.toLocaleString();
if (self.chartUnit === 'P') {
return numValue + prefix;
} else {
return prefix + numValue;
}
},
fontStyle: 'bold'
}
}
]
},
tooltip: {
mode: 'nearest',
custom: function() {
console.log('hello');
}
}
}