Chart.js customization, two Y-Axis overlay, chart area padding, odd tick padding - chart.js

The Chart.js plugin is super, excellent, beautiful and easily customizable.
But even all this did not help me solve a few problems.
I have to create pixel perfect chart according to design shown on picture 1
I hope for your help!!
How to make those lines that without signatures be longer than those with signatures
I did the indentation from the scales to the graphs using the tickMarkLength parameter, but maybe it’s possible somehow under another, because you can see the overlap of one scale on another.
How to make the grid lines of the left and right scales coincide?
I set beforeUpdate .stepSize, but in spite of the fact that I specify that there should be 8 intervals, sometimes 8, then 9.
There is a link to my current code:
function data_generation(values_obj) {
let max_val=-900;
let min_val=0;
Object.keys(values_obj).forEach(function(key) {
chart_object={};
chart_object.label= values_obj[key].name;
chart_object.data= Object.values(values_obj[key].data);
chart_object.backgroundColor= values_obj[key].color;
if(key == 'TempOutdoor') {
chart_object.yAxisID = 'right-y-axis';
chart_object.backgroundColor= "transparent";
chart_object.pointRadius= 4;
chart_object.lineTension= 0;
chart_object.pointBackgroundColor="#FFF";
chart_object.pointBorderColor= "#60AD5E";
chart_object.borderColor= "#60AD5E";
chart_object.pointBorderWidth= 2;
chart_object.type= 'line';
} else {
chart_object.yAxisID = 'left-y-axis';
chart_object.lineTension= 0;
}
config.data.datasets.push(chart_object);
//find common min and max values
//min
if(min_val>parseFloat(values_obj[key].min)) {
min_val = parseFloat(values_obj[key].min);
}
//max
if(max_val < parseFloat(values_obj[key].max)) {
max_val = parseFloat(values_obj[key].max);
}
});
}
var config = {
drawTicks:false,
type: 'bar',
data: {
datasets: [ ],
labels: ''
},
options: {
animation: {
duration: 0
},
'legend':false,
responsive:true,
maintainAspectRatio: false,
scales: {
xAxes: [{
stacked: true,
barThickness: ($(window).width()<991.99)?14:24,
ticks: {
fontSize: ($(window).width()<991.99)?10:14,
},
gridLines : {
display : false
}
}],
yAxes: [
{
beforeUpdate: function(scale) {
//1 find max and min through all leftlabels
var left_side_list = config.data.datasets.filter(obj => {return obj.yAxisID == "left-y-axis"});
var left_side_list_data = [].concat(...Object.keys(left_side_list).map(e => left_side_list[e].data));
let max_val = Math.max.apply(Math,left_side_list_data);
let min_val = Math.min.apply(Math,left_side_list_data);
// 8 intervals - 9 lines
let left_iterval = (max_val - min_val) / 8;
//set stepsize
scale.chart.options.scales.yAxes[0].ticks.stepSize = left_iterval;
return;
},
id: 'left-y-axis',
type: 'linear',
position: 'left',
ticks: {
beginAtZero: false,
fontSize: ($(window).width()<991.99)?10:14,
callback: function(value, index, values) {
if(index % 2 == 0 || index==0) {
return ' ';
} else {
return " "+value.toFixed(0)+" ";
}
}
},
gridLines: {
drawBorder: false,
tickMarkLength: ($(window).width()<991.99)?34:84,
}
},
{
beforeUpdate: function(scale) {
//var nMaxRev = Math.max.apply(Math,scale.chart.config.data.datasets[1].data);
//get right object data
var temp_list = config.data.datasets.filter(obj => {return obj.yAxisID == "right-y-axis"});
//var temp_list = scale.chart.config.data.datasets.filter(obj => {return obj.yAxisID == "right-y-axis"});
//console.log(temp_list);
if(temp_list[0].data !== undefined || temp_list[0].data != []) {
var nMaxRev = Math.max.apply(Math, temp_list[0].data);
var nMinRev = Math.min.apply(Math, temp_list[0].data);
var nLeftTickCount = 8;
if(nMinRev<0) {
nLeftTickCount = 7;
}
var nTickInterval = (nMaxRev - nMinRev) / nLeftTickCount;
scale.chart.options.scales.yAxes[1].ticks.stepSize = nTickInterval;
}
return;
},
id: 'right-y-axis',
type: 'linear',
position: 'right',
ticks: {
beginAtZero: false,
fontSize: ($(window).width()<991.99)?10:14,
callback: function(value, index, values) {
if(index % 2 == 0 || index==0) {
return '';
} else {
return " "+value.toFixed(0);
}
}
},
gridLines: {
drawBorder: false,
tickMarkLength: ($(window).width()<991.99)?34:84,
}
}
]
}
}
};
window.onload = function() {
var data_string='{"success":true,"axis":["Пн","Вт","Ср","Чт","Пт","Сб","Вс"],"data":{"TempOutdoor":{"period_start":"2021-05-10 00:00:00","period_end":"2021-05-16 23:59:59","data":{"Пн":-4.9787234042553195,"Вт":-2.9166666666666665,"Ср":-3.3125,"Чт":2.5208333333333335,"Пт":6.84375,"Сб":0,"Вс":0},"min":"-4.98","max":"6.84","avg":"-0.26","sum":"-1.84","name":"Температура на улице","color":"#60AD5E","value_type":"instant"},"MotoHW":{"period_start":"2021-05-10 00:00:00","period_end":"2021-05-16 23:59:59","data":{"Пн":11,"Вт":15,"Ср":13,"Чт":12,"Пт":9,"Сб":0,"Вс":0},"min":"0.00","max":"15.00","avg":"8.57","sum":"60.00","name":"Мотогодини: Гаряча вода","color":"#29819D","value_type":"counter"}},"closestPeriods":{"previous":{"2021-05-03 00:00:00":"03.05 - 09.05"},"current":{"2021-05-10 00:00:00":"10.05 - 16.05"},"next":null}}';
var data = JSON.parse(data_string);
config.data.labels = data.axis;
var values_obj = data.data;
data_generation(values_obj);
//console.log(JSON.stringify(config));
var ctx = document.getElementById('StatisticsChartCanvas').getContext('2d');
window.StatisticsChart = new Chart(ctx,config);
};
<script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/2.9.3/Chart.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<div class="chart-wrapper" style="width:548px; height:265px;">
<canvas id="StatisticsChartCanvas"></canvas>
</div>

After a huge amount of ideas, attempts and errors, I found a solution for the full customization of the scale.
For this I
Disabled the display of gridlines and ticks using property
display:false,
Wrote a plugin that draw gridlines and ticks in accordance with the design.
That's what happened
var AikGridLinePlugin = {
beforeDraw: function(chartInstance) {
var yScaleLeft = chartInstance.scales["left-y-axis"];
var yScaleRight = chartInstance.scales["right-y-axis"];
var canvas = chartInstance.chart;
var ctx = canvas.ctx;
//left axis
var left_side_list = chartInstance.data.datasets.filter(obj => {return obj.yAxisID == "left-y-axis"});
var left_side_list_data = [].concat(...Object.keys(left_side_list).map(e => left_side_list[e].data));
let left_side_list_max = Math.max.apply(Math,left_side_list_data);
let left_side_list_min = Math.min.apply(Math,left_side_list_data);
let left_iterval = (left_side_list_max - left_side_list_min) / 8;
// right axis
var right_side_list = chartInstance.data.datasets.filter(obj => {return obj.yAxisID == "right-y-axis"});
var right_side_list_data = [].concat(...Object.keys(right_side_list).map(e => right_side_list[e].data));
let right_side_list_max = Math.max.apply(Math,right_side_list_data);
let right_side_list_min = Math.min.apply(Math,right_side_list_data);
let right_iterval = (right_side_list_max - right_side_list_min) / 8;
var current_value_left = left_side_list_min,
current_value_right = right_side_list_min,
current_value_right_text=0;
for(var i=1;i<10;i++) {
ctx.lineWidth = 1;
ctx.font = "13px Roboto";
ctx.fillStyle = "#666666";
ctx.beginPath();
if(i%2==0) {
ctx.moveTo(47, yScaleLeft.getPixelForValue(current_value_left));
ctx.lineTo((canvas.width-47), yScaleLeft.getPixelForValue(current_value_left));
ctx.fillText((current_value_left>1)?current_value_left.toFixed(0):current_value_left.toFixed(1), 5, yScaleLeft.getPixelForValue(current_value_left)+5);
current_value_right_text=(current_value_right>1 || current_value_right<-1)?current_value_right.toFixed(0):current_value_right.toFixed(1);
ctx.fillText(current_value_right_text, (canvas.width-5-ctx.measureText(current_value_right_text).width), yScaleLeft.getPixelForValue(current_value_left)+5);
} else {
ctx.moveTo(15, yScaleLeft.getPixelForValue(current_value_left));
ctx.lineTo((canvas.width-15), yScaleLeft.getPixelForValue(current_value_left));
}
ctx.strokeStyle = "#91979F";
ctx.stroke();
current_value_left = current_value_left+left_iterval;
current_value_right = current_value_right+right_iterval;
}
return;
}
};
Chart.pluginService.register(AikGridLinePlugin);
function data_generation(values_obj) {
let max_val=-900;
let min_val=0;
Object.keys(values_obj).forEach(function(key) {
chart_object={};
chart_object.label= values_obj[key].name;
chart_object.data= Object.values(values_obj[key].data);
chart_object.backgroundColor= values_obj[key].color;
if(key == 'TempOutdoor') {
chart_object.yAxisID = 'right-y-axis';
chart_object.backgroundColor= "transparent";
chart_object.pointRadius= 4;
chart_object.lineTension= 0;
chart_object.pointBackgroundColor="#FFF";
chart_object.pointBorderColor= "#60AD5E";
chart_object.borderColor= "#60AD5E";
chart_object.pointBorderWidth= 2;
chart_object.type= 'line';
} else {
chart_object.yAxisID = 'left-y-axis';
chart_object.lineTension= 0;
}
config.data.datasets.push(chart_object);
//find common min and max values
//min
if(min_val>parseFloat(values_obj[key].min)) {
min_val = parseFloat(values_obj[key].min);
}
//max
if(max_val < parseFloat(values_obj[key].max)) {
max_val = parseFloat(values_obj[key].max);
}
});
}
var config = {
drawTicks:false,
type: 'bar',
data: {
datasets: [ ],
labels: ''
},
options: {
animation: {
duration: 0
},
'legend':false,
responsive:true,
maintainAspectRatio: false,
scales: {
xAxes: [{
id: 'x-axis',
stacked: true,
barThickness: ($(window).width()<991.99)?14:24,
ticks: {
fontSize: ($(window).width()<991.99)?10:14,
},
gridLines : {
display : false
}
}],
yAxes: [
{
id: 'left-y-axis',
type: 'linear',
position: 'left',
ticks: {
display:false,
},
gridLines: {
display : false,
drawBorder: false,
tickMarkLength: ($(window).width()<991.99)?34:84,
}
},
{
id: 'right-y-axis',
type: 'linear',
position: 'right',
ticks: {
display : false,
beginAtZero: false,
fontSize: ($(window).width()<991.99)?10:14,
},
gridLines: {
display : false,
drawBorder: false,
tickMarkLength: ($(window).width()<991.99)?34:84,
}
}
]
}
}
};
window.onload = function() {
var data_string='{"success":true,"axis":["Пн","Вт","Ср","Чт","Пт","Сб","Вс"],"data":{"TempOutdoor":{"period_start":"2021-05-10 00:00:00","period_end":"2021-05-16 23:59:59","data":{"Пн":-4.9787234042553195,"Вт":-2.9166666666666665,"Ср":-3.3125,"Чт":2.5208333333333335,"Пт":6.84375,"Сб":0,"Вс":0},"min":"-4.98","max":"6.84","avg":"-0.26","sum":"-1.84","name":"Температура на улице","color":"#60AD5E","value_type":"instant"},"MotoHW":{"period_start":"2021-05-10 00:00:00","period_end":"2021-05-16 23:59:59","data":{"Пн":11,"Вт":15,"Ср":13,"Чт":12,"Пт":9,"Сб":0,"Вс":0},"min":"0.00","max":"15.00","avg":"8.57","sum":"60.00","name":"Мотогодини: Гаряча вода","color":"#29819D","value_type":"counter"}},"closestPeriods":{"previous":{"2021-05-03 00:00:00":"03.05 - 09.05"},"current":{"2021-05-10 00:00:00":"10.05 - 16.05"},"next":null}}';
var data = JSON.parse(data_string);
config.data.labels = data.axis;
var values_obj = data.data;
data_generation(values_obj);
//console.log(JSON.stringify(config));
var ctx = document.getElementById('StatisticsChartCanvas').getContext('2d');
window.StatisticsChart = new Chart(ctx,config);
};
<script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/2.9.3/Chart.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<div class="chart-wrapper" style="width:548px; height:265px;">
<canvas id="StatisticsChartCanvas"></canvas>
</div>

Related

How to hide Chart.js data labels for small screens

I am trying to hide data labels generated by the data labels plugin for small screens.
I thought that I could use the onResize property of chartjs and set display to false when the width got small. This is much like the hide labels solution found here.
Unfortunately, I've not been able to get this to work. I have the following CodePen that doesn't work.
var moneyFormat = wNumb({
decimals: 0,
thousand: ',',
prefix: '$',
negativeBefore: '-'
});
var percentFormat = wNumb({
decimals: 0,
suffix: '%',
negativeBefore: '-'
});
/*
* Unregister chartjs-plugins-datalabels - not really necessary for this use case
*/
Chart.plugins.unregister(ChartDataLabels);
var doughnutdata = {
labels: ['Housing',
'Food',
'Transportation',
'Clothing',
'Healthcare',
'Childcare',
'Misc'],
datasets: [
{
backgroundColor: [
'#9B2A00',
'#5B5C90',
'#6B8294',
'#1A6300',
'#BE0000',
'#B8A853',
'#64A856'
],
borderColor: [
'#FFFFFF',
'#FFFFFF',
'#FFFFFF',
'#FFFFFF',
'#FFFFFF',
'#FFFFFF',
'#FFFFFF'
],
data: [88480, 57680, 40050, 18430, 23860, 25840, 17490]
}
]
};
var chartOptions = {
responsive: true,
maintainAspectRatio: true,
legend: {
labels: {
boxWidth: 20
}
},
tooltips: {
callbacks: {
label: function (tooltipItem, data) {
var index = tooltipItem.index;
return data.labels[index] + ': ' + moneyFormat.to(data.datasets[0].data[index]) + '';
}
}
},
plugins: {
datalabels: {
anchor: 'end',
backgroundColor: function (context) {
return context.dataset.backgroundColor;
},
borderColor: 'white',
borderRadius: 25,
borderWidth: 1,
color: 'white',
font: {
size: 10
},
formatter: function (value, pieID) {
var sum = 0;
var dataArr = pieID.chart.data.datasets[0].data;
dataArr.map(function (data) {
sum += data;
});
var percentage = percentFormat.to((value * 100 / sum));
return percentage;
}
}
}
};
var doughnutID = document.getElementById('doughnutchart').getContext('2d');
var pieChart = new Chart(doughnutID, {
plugins: [ChartDataLabels],
type: 'doughnut',
data: doughnutdata,
options: chartOptions,
onResize: function(chart, size) {
var showLabels = (size.width < 500) ? false : true;
chart.options = {
plugins: {
datalabels: {
display: showLabels
}
}
};
}
});
Any ideas concerning what I'm doing wrong (and fixes) would be greatly appreciated.
Responsiveness can be implemented using scriptable options and in your case, you would use a function for the display option that returns false if the chart is smaller than a specific size. (Example):
options: {
plugins: {
datalabels: {
display: function(context) {
return context.chart.width > 500;
}
}
}
}
As usual, as soon as I post a question I come up with an answer. One solution using inline plugin definitions is given at the following CodePen. If you put a browser into developer mode and shrink the window to less than 540 px, the data labels will vanish.
The code is shown below:
"use strict";
/* global Chart */
/* global wNumb */
/* global ChartDataLabels */
/*
* Unregister chartjs-plugins-datalabels - not really necessary for this use case
*/
Chart.plugins.unregister(ChartDataLabels);
var moneyFormat = wNumb({
decimals: 0,
thousand: ",",
prefix: "$",
negativeBefore: "-"
});
var percentFormat = wNumb({
decimals: 0,
suffix: "%",
negativeBefore: "-"
});
var doughnutdata = {
labels: [
"Housing",
"Food",
"Transportation",
"Clothing",
"Healthcare",
"Childcare",
"Misc"
],
datasets: [
{
backgroundColor: [
"#9B2A00",
"#5B5C90",
"#6B8294",
"#1A6300",
"#BE0000",
"#B8A853",
"#64A856"
],
borderColor: [
"#FFFFFF",
"#FFFFFF",
"#FFFFFF",
"#FFFFFF",
"#FFFFFF",
"#FFFFFF",
"#FFFFFF"
],
data: [88480, 57680, 40050, 18430, 23860, 25840, 17490]
}
]
};
var chartOptions = {
responsive: true,
maintainAspectRatio: true,
legend: {
labels: {
boxWidth: 20
}
},
tooltips: {
callbacks: {
label: function(tooltipItem, data) {
var index = tooltipItem.index;
return (
data.labels[index] +
": " +
moneyFormat.to(data.datasets[0].data[index]) +
""
);
}
}
},
plugins: {
datalabels: {
anchor: "end",
backgroundColor: function(context) {
return context.dataset.backgroundColor;
},
borderColor: "white",
borderRadius: 25,
borderWidth: 1,
color: "white",
font: {
size: 10
},
formatter: function(value, pieID) {
var sum = 0;
var dataArr = pieID.chart.data.datasets[0].data;
dataArr.map(function(data) {
sum += data;
});
var percentage = percentFormat.to(value * 100 / sum);
return percentage;
}
}
}
};
var doughnutID = document.getElementById("doughnutchart").getContext("2d");
var pieChart = new Chart(doughnutID, {
plugins: [
ChartDataLabels,
{
beforeLayout: function(chart) {
var showLabels = (chart.width) > 500 ? true : false;
chart.options.plugins.datalabels.display = showLabels;
}
},
{
onresize: function(chart) {
var showLabels = (chart.width) > 500 ? true : false;
chart.options.plugins.datalabels.display = showLabels;
}
}
],
type: "doughnut",
data: doughnutdata,
options: chartOptions
});
I hope that this is useful.

Change style of hover and tooltip in chartjs or ng2-charts

is there any way to change style of hover as well as the tooltip with chartjs or ng2-charts? I want to hide the hover points and only display them whenever I hover on them along with the indicator as the line. Here is the exact chart model I want to build:
https://interactive-bitcoin-price-chart-foxkmkynmg.now.sh/
Thank you in advance for your tips.
EDIT: I followed instructions of GRUNT to apply this chart option into my Angular app, the full chart is shown with tooltip whenever I hover, but the line-trace indicator is not. Here are my codes:
plugin-hoverline.ts:
export class PluginHoverline {
posX: null;
isMouseOut:boolean = false;
drawLine(chart, posX) {
const ctx = chart.ctx,
x_axis = chart.scales['x-axis-0'],
y_axis = chart.scales['y-axis-0'],
x = posX,
topY = y_axis.top,
bottomY = y_axis.bottom;
if (posX < x_axis.left || posX > x_axis.right) return;
// draw line
ctx.save();
ctx.beginPath();
ctx.moveTo(x, topY);
ctx.lineTo(x, bottomY);
ctx.lineWidth = chart.options.lineOnHover.lineWidth;
ctx.strokeStyle = chart.options.lineOnHover.lineColor;
ctx.stroke();
ctx.restore();
};
beforeInit(chart) {
chart.options.events.push('mouseover');
};
afterEvent(chart, event) {
if (!chart.options.lineOnHover || !chart.options.lineOnHover.enabled) return;
if (event.type !== 'mousemove' && event.type !== 'mouseover') {
if (event.type === 'mouseout') this.isMouseOut = true;
chart.clear();
chart.draw();
return;
}
this.posX = event.x;
this.isMouseOut = false;
chart.clear();
chart.draw();
this.drawLine(chart, this.posX);
var metaData = chart.getDatasetMeta(0).data,
radius = chart.data.datasets[0].pointHoverRadius,
posX = metaData.map(e => e._model.x);
posX.forEach(function(pos, posIndex) {
if (this.posX < pos + radius && this.posX > pos - radius) {
chart.updateHoverStyle([metaData[posIndex]], null, true);
chart.tooltip._active = [metaData[posIndex]];
} else chart.updateHoverStyle([metaData[posIndex]], null, false);
}.bind(this));
chart.tooltip.update();
};
afterDatasetsDraw(chart, ease) {
if (!this.posX) return;
if (!this.isMouseOut) this.drawLine(chart, this.posX);
};
}
banner.component.ts: (chart component)
import { Component, OnInit } from '#angular/core';
import { HistoricalBpiService } from '../../services/historical-bpi.service';
import { PluginHoverline } from './plugin-hoverline';
#Component({
selector: 'app-banner',
templateUrl: './banner.component.html',
styleUrls: ['./banner.component.scss']
})
export class BannerComponent implements OnInit {
private dataUrl: string = 'historical/close.json';
constructor(
private historicalBpiService:HistoricalBpiService
) {}
// lineChart
public lineChartData:any = [
{ data:[], label: 'Bitcoin price' }
];
public lineChartLabels:Array<any> = [];
public lineChartOptions:any = {
responsive: true,
maintainAspectRatio: false,
layout: {
padding: 0
},
lineOnHover: {
enabled: true,
lineColor: '#bbb',
lineWidth: 1
},
scales: {
yAxes: [{
display: true,
scaleLabel: {
display: false,
labelString: 'USD'
},
ticks: {
display: false
},
gridLines: {
display: true,
tickMarkLength: 0
}
}],
xAxes: [{
ticks: {
display: false
},
gridLines: {
display: false,
tickMarkLength: 0
}
}]
},
elements: {
point: {
radius: 3
},
line: {
tension: 0.4, // 0 disables bezier curves
}
},
hover: {
mode: 'nearest',
intersect: false
},
tooltips: {
mode: 'nearest',
intersect: false,
backgroundColor: 'rgb(95,22,21)',
callbacks: {
label: function (tooltipItems, data) {
return data.datasets[tooltipItems.datasetIndex].label + ' : ' + '$' + tooltipItems.yLabel.toLocaleString();
},
labelColor: function(tooltipItem, chart) {
var dataset = chart.config.data.datasets[tooltipItem.datasetIndex];
return {
backgroundColor : dataset.backgroundColor
}
}
}
}
};
public lineChartColors:Array<any> = [
{
backgroundColor: 'rgba(199,32,48,0.8',
borderColor: 'rgb(95,22,21);',
pointBackgroundColor: 'rgba(218,208,163,0.8)',
pointHoverBackgroundColor: 'rgba(218,208,163,0.8)',
pointHoverBorderColor: 'rgb(218,208,163)',
pointHoverRadius: 6,
steppedLine: false
}
];
public lineChartLegend:boolean = false;
public lineChartType:string = 'line';
// events
public chartClicked(e:any):void {
console.log(e);
}
public chartHovered(e:any):void {
console.log(e);
}
ngOnInit(){
this.historicalBpiService.getBpiData(this.dataUrl)
.subscribe(
res => {
//this.lineChartData = Object.keys(res.bpi).map(function (key) { return res.bpi[key];});
this.lineChartData[0].data = Object.values(res.bpi);
this.lineChartLabels = Object.keys(res.bpi);
//console.log(this.lineChartData,this.lineChartLabels);
}
)
}
}
template:
<div class="chart">
<canvas baseChart height="360px"
[datasets]="lineChartData"
[labels]="lineChartLabels"
[options]="lineChartOptions"
[colors]="lineChartColors"
[legend]="lineChartLegend"
[chartType]="lineChartType"
(chartHover)="chartHovered($event)"
(chartClick)="chartClicked($event)"></canvas>
</div>
Unfortunately there is no built-in functionality for this yet. However you can use this chart plugin (once created for my own purpose) to achieve your goal.
To utilize the plugin, set the following option in your chart­'s options config :
lineOnHover: {
enabled: true,
lineColor: '#bbb',
lineWidth: 1
}
also, make sure to set the following properties for your dataset :
pointRadius: 0,
pointHoverRadius: 5,
pointHoverBackgroundColor: 'white'
see - live demo
This answer is to build on top of GRUNT's answer above to be able to display if its a multiline graph:
Just change the following (in the code snippet he provide here):
posX.forEach(function(pos, posIndex) {
if (this.posX < pos + radius && this.posX > pos - radius) {
chart.updateHoverStyle([metaData[posIndex]], null, true);
chart.tooltip._active = [metaData[posIndex]];
} else chart.updateHoverStyle([metaData[posIndex]], null, false);
}.bind(this));
to:
posX.forEach(function(pos, posIndex) {
var metaDatum = []
if (this.posX < pos + radius && this.posX > pos - radius) {
chart.data.datasets.forEach((dataset, ind) => {
metaDatum.push(chart.getDatasetMeta(ind).data[posIndex]);
})
chart.updateHoverStyle(metaDatum, null, true);
chart.tooltip._active = metaDatum;
} else {
metaDatum.push(metaData[posIndex]);
chart.updateHoverStyle(metaDatum, null, false);
}
}.bind(this));

Show highest value untop of a bars when dataset is stacked (chartjs)

I have this code:
animation: {
duration: 500,
onComplete: function() {
var ctx = this.chart.ctx;
var chart = this;
ctx.textAlign = "center";
ctx.textBaseline = "middle";
var datasets = this.config.data.datasets;
ctx.font = "15px QuickSand";
datasets.forEach(function (dataset, i) {
switch ( chart.getDatasetMeta(i).type ) {
case "bar":
ctx.fillStyle = "#303030";
chart.getDatasetMeta(i).data.forEach(function (p, j)
{
ctx.fillText(datasets[i].data[j], p._model.x, p._model.y - 10);
});
break;
}
});
}
}
And these datasets:
datasets: [
{
backgroundColor: '#f87979',
data: [6500, 5500]},
{
backgroundColor: '#f8f8ee',
data: [4800, 5600]
}
]
The dataset is set to be stacked using.
scales: {
xAxes: [{
barThickness: 25,
stacked: true,
ticks: {
beginAtZero: true,
padding: 0,
fontSize: 13
}
}],
yAxes: [{
stacked: true,
display: false
}]
},
What the above code does is placing the values over the bars. My problem is that i want to show the highest value from each dataset at above each bar.
And not all the values from each point.
Can you guys help me with this? I have been trying to do this for like a 1 day now.
To clearify instead of this:
Image with all values
I want this:
Image with wanted values
check out this jsfiddle: https://jsfiddle.net/umsbywLg/2/
essentially I calculated the max value and then drew that on top on the stacked bars:
onComplete: function() {
var ctx = this.chart.ctx;
var chart = this;
ctx.textAlign = "center";
ctx.textBaseline = "middle";
var datasets = this.config.data.datasets;
ctx.font = "15px QuickSand";
ctx.fillStyle = "#303030";
datasets.forEach(function (dataset, i) {
var maxValue = 0;
chart.getDatasetMeta(i).data.forEach(function (p, j) {
if(maxValue < datasets[j].data[i]) {
maxValue = datasets[j].data[i];
}
});
ctx.fillText(maxValue, datasets[i]._meta[0].data[i]._view.x, 20);
});
}

chart.js value is two but only one display tooltip

I have web monitoring data and I want to graph with chart js library in javascript.
Multiple data are at the same moment. However, 'tooltip' displays only one piece of data. Why is this?
Please help me :(
I (A) Login-swlee.zabbix.dev selected and time is 06/25 02:23:21
I (B) First page-swlee.zabbix.dev selected and time is 06/25 02:23:21
I (A) and (B) both selected but tooltip is only one.
```
var colors = ["#FEB500", "#5F8CFC", "#ADC803", "#F0605D"]
var list = data.list;
var list2 = data.list2;
var list3 = data.list3;
var datasets = [];
var labels = [];
for (i = 0; i < list3.length; i++) {
var date = new Date(list3[i].clock * 1000).getTime();
if (labels.indexOf(date) < 0) {
labels.push(date);
}
}
labels.sort();
var tbody = document.getElementById('webTable').children[1];
for (var i = 0; i < list.length; i++) {
var item = list[i];
var td_name = [];
for (var j = 0; j < list2.length; j++) {
var item2 = list2[j];
if (item.hosts[0].hostid == item2.hostid && item2.key_.indexOf('web.test.rspcode') > -1) {
var label = item2.name.replace('$1', item2.key_.substring(17, item2.key_.length - 1).split(',')[0]).replace('$2', item2.key_.substring(17, item2.key_.length - 1).split(',')[1]);
//line chart
var dataset = {
data: [],
label: label,
fill: false,
spanGaps: true,
borderColor: colors[i]
};
for (var k = 0; k < list3.length; k++) {
var item3 = list3[k];
if (item2.itemid == item3.itemid) {
var date = new Date(list3[k].clock * 1000).getTime();
dataset.data.push(null);
if (labels.indexOf(date) > -1) {
dataset.data[labels.indexOf(date)] = item3.value;
}
}
}
datasets.push(dataset);
}
}
}
var ctx = document.getElementById("webChart").getContext("2d");
var gData = {
labels: labels,
datasets: datasets
}
var lineChart = new Chart(ctx, {
type: 'line',
data: gData,
options: {
responsive: true,
scales: {
yAxes: [{
ticks: {
stepSize: 200,
suggestedMin: 0,
suggestedMax: 600
}
}],
xAxes: [{
type: 'time',
time: {
unit: 'minute',
round: 'minute',
displayFormats: {
minute: 'h:mm'
}
},
gridLines: {
display: false
}
}]
},
elements: {
line: {
tension: 0,
}
},
animation: false,
hover: {
animationDuration: 0,
},
responsiveAnimationDuration: 0,
legend: {
display: true,
labels: {
fontColor: '#fff'
}
},
tooltips: {
callbacks: {
title: function(tooltipItems, data) {
return new Date(tooltipItems[0].xLabel).format('MM/dd hh:mm:ss');
}
}
}
}
});
You need to set tooltip­'s mode to index
­­­­­­­­
...
tooltips: {
mode: 'index', //<-- set this
callbacks: {
title: function(tooltipItems, data) {
return new Date(tooltipItems[0].xLabel).format('MM/dd hh:mm:ss');
}
}
}
...

Chartjs v2 - format tooltip for multiple data sets (bar and line)

Reading up a lot on how to format the tooltip in ChartJS v2.x utilizing the tooltip callback. I've had success thus far, but find that I'm unable to define two separate formats for the two data sets I have.
As a bit more context, I have a line chart overlayed on top of a bar chart:
My bar data is numerical (in the millions, and needs to be rounded and truncated).
Example: 22345343 needs to be shown as 22M in the tooltip
My line data is a currency
Example: 146.36534 needs to shown as $146.37 in the tooptip
Here's my short WIP code thus far. This formats the tooltip to round and include the $ sign. How can I expand this so that I can separately format my bar data correctly in the tooltip?
tooltips: {
mode: 'index',
intersect: false,
callbacks: {
label: function(tooltipItem, data) {
return "$" + Number(tooltipItem.yLabel).toFixed(2).replace(/./g, function(c, i, a) {
return i > 0 && c !== "." && (a.length - i) % 3 === 0 ? "," + c : c;
});
}
}
}
You could achieve that using the following tooltips callback function ...
callbacks: {
label: function (t, d) {
if (t.datasetIndex === 0) {
return '$' + t.yLabel.toFixed(2)
} else if (t.datasetIndex === 1) {
return Math.round(+t.yLabel.toString().replace(/(\d{2})(.*)/, '$1.$2')) + 'M';
}
}
}
ᴅᴇᴍᴏ
var ctx = document.getElementById("canvas").getContext("2d");
var myChart = new Chart(ctx, {
type: 'bar',
data: {
labels: ["January", "February", "March", "April", "May"],
datasets: [{
type: 'line',
label: "Sales",
data: [144.36534, 146.42534, 145.23534, 147.19534, 145],
fill: false,
borderColor: '#EC932F',
backgroundColor: '#EC932F',
tension: 0,
yAxisID: 'y-axis-2'
}, {
type: 'bar',
label: "Visitor",
data: [22345343, 23345343, 24345343, 25345343, 230245343],
backgroundColor: '#71B37C',
yAxisID: 'y-axis-1'
}]
},
options: {
responsive: false,
tooltips: {
mode: 'index',
intersect: false,
callbacks: {
label: function (t, d) {
if (t.datasetIndex === 0) {
return '$' + t.yLabel.toFixed(2);
} else if (t.datasetIndex === 1) {
if (t.yLabel.toString().length === 9) {
return Math.round(+t.yLabel.toString().replace(/(\d{3})(.*)/, '$1.$2')) + 'M';
} else return Math.round(+t.yLabel.toString().replace(/(\d{2})(.*)/, '$1.$2')) + 'M';
}
}
}
},
scales: {
yAxes: [{
id: "y-axis-1",
position: "left"
}, {
id: "y-axis-2",
position: "right"
}]
}
}
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/2.5.0/Chart.min.js"></script>
<canvas id="canvas" width="400" height="190"></canvas>
Try using below code!
let DoughnutForSavingCount = {
labels: [
intl.formatMessage({ id: 'Guarantee' }),
intl.formatMessage({ id: 'ILAS' }),
intl.formatMessage({ id: 'No Idea' })
],
datasets: [
/* Outer doughnut data starts*/
{
label: 'Graph1',
data: [
_.get(getClientSavingILASPolicyData[0], 'countwithGuaranttee') >
0 &&
_.get(getClientSavingILASPolicyData[0], 'totalWithGuarantee') ===
0
? 0.1
: _.get(getClientSavingILASPolicyData[0], 'totalWithGuarantee'),
_.get(getClientSavingILASPolicyData[0], 'countwithILAS', 0) > 0 &&
_.get(getClientSavingILASPolicyData[0], 'totalWithILAS') === 0
? 0.1
: _.get(getClientSavingILASPolicyData[0], 'totalWithILAS'),
_.get(getClientSavingILASPolicyData[0], 'countNoIdea', 0) > 0 &&
_.get(getClientSavingILASPolicyData[0], 'totalWithNoIdea') === 0
? 0.1
: _.get(getClientSavingILASPolicyData[0], 'totalWithNoIdea')
],
backgroundColor: ['#8c1aff', '#BF80FF', '#E9e9e9'],
hoverBackgroundColor: ['#8c1aff', '#BF80FF', '#E9e9e9']
},
/* Outer doughnut data ends*/
/* Inner doughnut data starts*/
{
label: 'Graph2',
data: [
_.get(getClientSavingILASPolicyData[0], 'countwithGuaranttee'),
_.get(getClientSavingILASPolicyData[0], 'countwithILAS'),
_.get(getClientSavingILASPolicyData[0], 'countNoIdea')
],
backgroundColor: ['#8c1aff', '#BF80FF', '#E9e9e9'],
hoverBackgroundColor: ['#8c1aff', '#BF80FF', '#E9e9e9']
}
/* Inner doughnut data ends*/
],
borderWidth: [1]
};
let DoughnutForSavingCountConfig = {
cutoutPercentage: 70,
legend: {
display: true,
position: 'bottom',
labels: {
fontColor: '#34A0DC',
fontSize: 10,
fontFamily: 'Helvetica',
boxWidth: 10,
usePointStyle: true
}
},
responsive: true,
plugins: {
datalabels: {
display: false
}
},
tooltips: {
enabled: true,
//*****add for reference********** */
callbacks: {
label: function(tooltipItems, data) {
if (tooltipItems.datasetIndex) {
var label = data.labels[tooltipItems.index] || '';
var currentValue =
data.datasets[tooltipItems.datasetIndex].data[
tooltipItems.index
];
if (label) {
label += ': ';
}
label += currentValue == '0.1' ? '0' : currentValue;
return label;
} else {
var label = data.labels[tooltipItems.index] || '';
var currentValue =
data.datasets[tooltipItems.datasetIndex].data[
tooltipItems.index
];
if (label) {
label += ': ';
}
label += intl.formatMessage({ id: 'HKD' }) + ' ';
label +=
currentValue == '0.1'
? '0'
: currentValue
.toString()
.replace(/\B(?=(\d{3})+(?!\d))/g, ',');
return label;
}
}
}
}
};
Thanks, GRUNT!
But since my datasets could me mixed it's better to use the yAxisID to check for the correct dataset.
tooltips: {
callbacks: {
label: function (tooltipItem, details) {
if (details.datasets[tooltipItem.datasetIndex].yAxisID == "$") {
let dataset = details.datasets[tooltipItem.datasetIndex];
let currentValue = dataset.data[tooltipItem.index];
return dataset.label + ": " + currentValue.toFixed(2) + " $";
} else {
let dataset = details.datasets[tooltipItem.datasetIndex];
let currentValue = dataset.data[tooltipItem.index];
return dataset.label + ": " + currentValue +" pieces";
}
}
}
}