How to render Chart.js 2.x tooltips on top of everything - chart.js

I'm using Chart.js 2.6.0 and I have been working on an example to render custom graphics. You can see a live demo at: https://jsfiddle.net/arminzia/bm4ezdur/.
Basically I'm rendering circles on top of each bar to denote the value. here's the code:
var ctx = document.getElementById("myChart");
var myChart = new Chart(ctx, {
type: 'bar',
responsive: true,
maintainAspectRatio: false,
data: {
labels: ["2 Jan", "9 Jan", "16 Jan", "23 Jan", "30 Jan", "6 Feb", "13 Feb"],
datasets: [{
data: [16, 87, 56, 35, 88, 60, 12],
backgroundColor: "#4082c4"
}]
},
options: {
"hover": {
"animationDuration": 0
},
"animation": {
"duration": 1,
"onComplete": function () {
var chartInstance = this.chart,
ctx = chartInstance.ctx;
ctx.font = Chart.helpers.fontString(Chart.defaults.global.defaultFontSize, Chart.defaults.global.defaultFontStyle, Chart.defaults.global.defaultFontFamily);
ctx.textAlign = 'center';
ctx.textBaseline = 'bottom';
this.data.datasets.forEach(function (dataset, i) {
var meta = chartInstance.controller.getDatasetMeta(i);
meta.data.forEach(function (bar, index) {
var data = dataset.data[index] + '$';
var centerX = bar._model.x;
var centerY = bar._model.y - 10;
var radius = 22;
ctx.beginPath();
ctx.arc(centerX, centerY, radius, 0, 2 * Math.PI, false);
ctx.fillStyle = 'white';
ctx.fill();
ctx.lineWidth = 1;
ctx.strokeStyle = '#4082c4';
ctx.stroke();
ctx.fillStyle = '#4082c4';
ctx.font = "bold 14px Calibri";
ctx.fillText(data, bar._model.x, bar._model.y);
});
});
}
},
legend: {
"display": false
},
tooltips: {
"enabled": true
},
scales: {
yAxes: [{
display: true,
gridLines: {
display : true
},
ticks: {
display: true,
beginAtZero:true,
max: 140
}
}],
xAxes: [{
barThickness: 40,
gridLines: {
display : false
},
ticks: {
beginAtZero:true
}
}]
}
}
});
Now the problem is, tooltips are being hidden under these circles. I've been struggling with this for too long and haven't found any solution or anything useful from the docs.
How can I force tooltips to be rendered on top of these circles? On top of everything actually, something like z-index: 1000.

Forgot to update this post. The answer is to use External (Custom) Tooltips. That's basically rendering HTML elements to create tooltips. Read more on the docs here.

Related

Chart.js - Tooltips overlapped by bar values

In bar charts, label above bars overlaps tooltip. Please see screenshots below.
Bar chart - original
Bar chart - tooltip problem
Please see my code below.
<!doctype html>
<html>
<head>
<title>Single Bar Chart</title>
<script src="Chart.min.js"></script>
<style>
canvas {
border:1px solid #367ee9;
}
#container{
width: 950px;
}
</style>
</head>
<body>
<div id="container">
<canvas id="canvas"></canvas>
</div>
<script>
var barChartData = {
labels: ["Jul 19", "Aug 19", "Sep 19", "Oct 19", "Nov 19", "Dec 19", "Jan 20", "Feb 20", "Mar 20", "Apr 20", "May 20", "Jun 20"],
datasets: [{
//label: 'Jan 20',
backgroundColor: '#367ee9',
borderColor: '#367ee9',
borderWidth: 1,
data: [33111, 27510, 27377, 14947, 4312, 7279, 70988, 2903, 29575, 65, 9861, 3416],
data_action: ["bar1_action", "bar2_action", "bar3_action", "bar4_action", "bar5_action", "bar6_action", "bar7_action", "bar8_action", "bar9_action", "bar10_action", "bar11_action", "bar12_action"],
data_alt: ["Click here to see results of bar1_alt", "bar2_alt", "bar3_alt", "bar4_alt", "bar5_alt", "bar6_alt", "bar7_alt", "bar8_alt", "bar9_alt", "bar10_alt", "bar11_alt", "bar12_alt"],
}]
};
window.onload = function() {
var ctx = document.getElementById('canvas').getContext('2d');
window.myBar = new Chart(ctx, {
type: 'bar',
data: barChartData,
options: {
responsive: true,
onHover: (event, chartElement) => {
event.target.style.cursor = chartElement[0] ? 'pointer' : 'default';
},
hover: {
animationDuration: 0,
},
animation: {
duration: 1000,
onComplete: function () {
var chartInstance = this.chart,
ctx = chartInstance.ctx;
ctx.font = Chart.helpers.fontString(Chart.defaults.global.defaultFontSize, Chart.defaults.global.defaultFontStyle, Chart.defaults.global.defaultFontFamily);
ctx.textAlign = 'center';
ctx.textBaseline = 'bottom';
//ctx.canvas.style.zIndex = 5;
this.data.datasets.forEach(function (dataset, i) {
var meta = chartInstance.controller.getDatasetMeta(i);
meta.data.forEach(function (bar, index) {
var val = dataset.data[index];
var data = "$" + val.toLocaleString();
if(val > -1){
ctx.fillText(data, bar._model.x, bar._model.y - 5);
}
else{
ctx.fillText(data, bar._model.x, bar._model.y + 14);
}
});
});
}
},
onClick:function(evt) {
var firstPoint = this.getElementAtEvent(evt)[0];
if (firstPoint) {
var value = this.data.datasets[firstPoint._datasetIndex].data_action[firstPoint._index];
alert(value);
}
},
legend: {
display: false,
position: 'top',
onHover:function(){
event.target.style.cursor = 'pointer';
},
},
title: {
display: true,
text: 'Total Benefits Exceeded ($231,345) (Claim Date)',
fontColor: '#000',
fontSize: '15',
},
tooltips: {
yAlign:'top',
displayColors: false, // hide color box
yPadding: 10,
xPadding: 10,
backgroundColor: '#fff',
borderColor: '#000',
borderWidth: 1,
bodyFontFamily: 'tahoma,Verdana,Arial, Helvetica, sans-serif',
bodyFontSize: 13,
bodyFontColor:'#000',
callbacks: {
title: function(tooltipItem, data) {
return; // hide title
},
label: function(tooltipItem, data) {
barCount = tooltipItem.index;
barIndex = tooltipItem.datasetIndex;
var label = data.datasets[barIndex].data_alt[barCount];
return label;
},
}
},
scales: {
yAxes: [{
display: false,
scaleLabel: {
display: true,
labelString: 'Denied Charges ($)',
fontColor: '#000',
fontSize: '15',
fontStyle: 'bold',
},
ticks: {
callback: function(value, index, values) {
return '$' + value.toLocaleString();
}
},
}]
}
}
});
};
</script>
</body>
</html>
Is there any way to avoid this?
I tried to find details about setting z-index in above code. But, I haven't got much help.
Other than this, the graphs work great.
Thanks,
Sandeep
At the begin of the animation.onComplete callback function, you should add the following lines:
ctx.save();
ctx.globalCompositeOperation='destination-over';
And at the end of animation.onComplete, you need to invoke ctx.restore().
For further details about ctx.globalCompositeOperation='destination-over', have a look at this answer.
Please take a look at your amended code and see how it works.
var barChartData = {
labels: ["Jul 19", "Aug 19", "Sep 19", "Oct 19", "Nov 19", "Dec 19", "Jan 20", "Feb 20", "Mar 20", "Apr 20", "May 20", "Jun 20"],
datasets: [{
//label: 'Jan 20',
backgroundColor: '#367ee9',
borderColor: '#367ee9',
borderWidth: 1,
data: [33111, 27510, 27377, 14947, 4312, 7279, 70988, 2903, 29575, 65, 9861, 3416],
data_action: ["bar1_action", "bar2_action", "bar3_action", "bar4_action", "bar5_action", "bar6_action", "bar7_action", "bar8_action", "bar9_action", "bar10_action", "bar11_action", "bar12_action"],
data_alt: ["Click here to see results of bar1_alt", "bar2_alt", "bar3_alt", "bar4_alt", "bar5_alt", "bar6_alt", "bar7_alt", "bar8_alt", "bar9_alt", "bar10_alt", "bar11_alt", "bar12_alt"],
}]
};
var ctx = document.getElementById('canvas').getContext('2d');
window.myBar = new Chart(ctx, {
type: 'bar',
data: barChartData,
options: {
responsive: true,
onHover: (event, chartElement) => {
event.target.style.cursor = chartElement[0] ? 'pointer' : 'default';
},
hover: {
animationDuration: 0,
},
animation: {
duration: 1000,
onComplete: function() {
var chartInstance = this.chart,
ctx = chartInstance.ctx;
ctx.save();
ctx.globalCompositeOperation='destination-over';
ctx.font = Chart.helpers.fontString(Chart.defaults.global.defaultFontSize, Chart.defaults.global.defaultFontStyle, Chart.defaults.global.defaultFontFamily);
ctx.textAlign = 'center';
ctx.textBaseline = 'bottom';
//ctx.canvas.style.zIndex = 5;
this.data.datasets.forEach(function(dataset, i) {
var meta = chartInstance.controller.getDatasetMeta(i);
meta.data.forEach(function(bar, index) {
var val = dataset.data[index];
var data = "$" + val.toLocaleString();
if (val > -1) {
ctx.fillText(data, bar._model.x, bar._model.y - 5);
} else {
ctx.fillText(data, bar._model.x, bar._model.y + 14);
}
});
});
ctx.restore();
}
},
onClick: function(evt) {
var firstPoint = this.getElementAtEvent(evt)[0];
if (firstPoint) {
var value = this.data.datasets[firstPoint._datasetIndex].data_action[firstPoint._index];
alert(value);
}
},
legend: {
display: false,
position: 'top',
onHover: function() {
event.target.style.cursor = 'pointer';
},
},
title: {
display: true,
text: 'Total Benefits Exceeded ($231,345) (Claim Date)',
fontColor: '#000',
fontSize: '15',
},
tooltips: {
yAlign: 'top',
displayColors: false, // hide color box
yPadding: 10,
xPadding: 10,
backgroundColor: '#fff',
borderColor: '#000',
borderWidth: 1,
bodyFontFamily: 'tahoma,Verdana,Arial, Helvetica, sans-serif',
bodyFontSize: 13,
bodyFontColor: '#000',
callbacks: {
title: function(tooltipItem, data) {
return; // hide title
},
label: function(tooltipItem, data) {
barCount = tooltipItem.index;
barIndex = tooltipItem.datasetIndex;
var label = data.datasets[barIndex].data_alt[barCount];
return label;
},
}
},
scales: {
yAxes: [{
display: false,
scaleLabel: {
display: true,
labelString: 'Denied Charges ($)',
fontColor: '#000',
fontSize: '15',
fontStyle: 'bold',
},
ticks: {
callback: function(value, index, values) {
return '$' + value.toLocaleString();
}
},
}]
}
}
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/2.9.3/Chart.min.js"></script>
<div id="container">
<canvas id="canvas"></canvas>
</div>

Adding line over stacked line chart with ChartJS

I'm trying to recreate this below chart with a stacked option on my background lines
But my attempts were unsuccessful with this image below as result
$(function() {
var areaChartCanvas = $('#areaChart').get(0).getContext('2d')
var areaChartData = {
labels: ['', '', ''],
datasets: [{
backgroundColor: 'transparent',
borderColor: 'black',
pointRadius: false,
data: [32, 12, 28],
type: 'line'
}, {
backgroundColor: 'red',
pointRadius: false,
data: [20, 20, 20]
}, {
backgroundColor: 'orange',
pointRadius: false,
data: [40, 40, 40]
}, {
backgroundColor: 'cyan',
pointRadius: false,
data: [60, 60, 60]
}]
}
var areaChartOptions = {
maintainAspectRatio: false,
responsive: true,
legend: {
display: false
},
scales: {
xAxes: [{
gridLines: {
display: true,
}
}],
yAxes: [{
gridLines: {
display: true,
},
stacked: true
}]
}
}
var areaChart = new Chart(areaChartCanvas, {
type: 'line',
data: areaChartData,
options: areaChartOptions
})
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/2.8.0/Chart.min.js"></script>
<canvas id="areaChart" style="height:250px"></canvas>
ideally, I want to be able to create 'AREAS' with different colors that will be stacked according to the interval I pass to it.
e.g:
cyan - 20
orange - 20
red - 20
but currently, I'm doing
cyan - 60
orange - 40
red - 20
If I understand you correctly, I thought about different approach and extends the chart with Plugin[1] (Inspired by https://stackoverflow.com/a/49303362/863110)
Chart.pluginService.register({
beforeDraw: function (chart, easing) {
if (chart.config.options.fillColor) {
var ctx = chart.chart.ctx;
var chartArea = chart.chartArea;
ctx.save();
let delta = 0;
const chartHeight = chartArea.bottom - chartArea.top;
const bottomBarHeight = chart.height - chartHeight - chartArea.top;
chart.config.options.fillColor.map(color => {
const colorHeight = chartHeight * (color[0] / 100);
const colorBottom = chartArea.bottom + colorHeight;
ctx.fillStyle = color[1];
const x = chartArea.left,
y = chart.height - bottomBarHeight - colorHeight - delta,
width = chartArea.right - chartArea.left,
height = colorHeight;
delta += height;
ctx.fillRect(x, y, width, height);
ctx.restore();
})
}
}
});
var chartData = {
labels: ['a', 'b', 'c', 'd'],
datasets: [{
label: 'value',
borderColor: 'blue',
data: [30, 50, 25, 10]
}]
};
var ctx = document.getElementById("myChart").getContext("2d");
var myBar = new Chart(ctx, {
type: 'line',
data: chartData,
options: {
scales: {
yAxes: [{ ticks: { max: 60 } }]
},
legend: { display: false },
fillColor: [
[20, 'red'],
[20, 'blue'],
[20, 'green'],
[20, 'pink'],
[20, 'yellow'],
]
}
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/2.7.2/Chart.min.js"></script>
<canvas id="myChart" height="300" width="500"></canvas>
https://jsbin.com/lisuvuq/2/edit?js,output
[1] - With Plugin you can custom the chart's behaviour. When you use Plugin you get an API so you can "listen" to life cycle events of the chart (beforeDraw for example) and call your own code. The API calls your function and gives you data about the chart so you can use it in your code.In this example, we're using the API to (1) Run a code before the chart been drawing (2) Using the data to calculate the areas of the different colors. (3) Draw additional shapes (ctx.fillRect) based on the calculation.

Chart.js - Draw horizontal line on horizontal bar chart

I'm trying to achieve the following chart but i am not able to build it.
I was trying with this code (using https://github.com/chartjs/chartjs-plugin-annotation) but i think the solution is not perfect. The value of line "Días meta" needs to be configurable, so the line must be re-drawn as its value changes and the horizontal bar's dataset also changes.
The code:
<script>
var color = Chart.helpers.color;
var horizontalBarChartData = {
//labels: ['7', '6', '5', '4', '3', '2', '0'],
labels: ['5', '3', '2', '1'],
datasets: [{
label: 'QTA de bultos',
backgroundColor: color('#91B643').alpha(0.5).rgbString(),
borderColor: '#91B643',
borderWidth: 1,
data: [
2,
7,
4,
11
]
}]
};
$(document).ready(function () {
var ctx = document.getElementById('myChart').getContext('2d');
window.myHorizontalBar = new Chart(ctx, {
type: 'horizontalBar',
data: horizontalBarChartData,
options: {
// Elements options apply to all of the options unless overridden in a dataset
// In this case, we are setting the border of each horizontal bar to be 2px wide
elements: {
rectangle: {
borderWidth: 2,
}
},
responsive: true,
legend: {
position: 'top',
},
title: {
display: true,
text: 'Chart.js Horizontal Bar Chart'
},
hover: {
animationDuration: 0
},
animation: {
duration: 1,
onComplete: function () {
var chartInstance = this.chart,
ctx = chartInstance.ctx;
ctx.font = Chart.helpers.fontString(Chart.defaults.global.defaultFontSize, Chart.defaults.global.defaultFontStyle, Chart.defaults.global.defaultFontFamily);
ctx.textAlign = 'left';
ctx.textBaseline = 'center';
this.data.datasets.forEach(function (dataset, i) {
var meta = chartInstance.controller.getDatasetMeta(i);
meta.data.forEach(function (bar, index) {
var data = dataset.data[index];
ctx.fillStyle = "#000";
ctx.fillText(data, bar._model.x + 5, bar._model.y);
});
});
}
},
scales: {
yAxes: [{
scaleLabel: {
display: true,
labelString: 'Días en Almacén'
},
gridLines: {
display: false
}
}
],
xAxes: [{
gridLines: {
display: true
},
ticks: {
beginAtZero: true
}
}
]
},
annotation: {
annotations: [{
type: 'line',
mode: 'horizontal',
scaleID: 'x-axis-0',
value: 1,
borderColor: 'rgb(75, 192, 192)',
borderWidth: 4,
label: {
enabled: true,
content: 'Días meta: 999'
}
}]
}
}
});
});
</script>
Example - With original dataset
Example - With change only one value of dataset
Any help will be appreciated, thanks =)

Remove excess lines on y axis using chartjs

I wonder how to remove the excess lines on the line chart. I tried to set drawborder to false but of course it just remove the all lines to the axis. I just wanted get rid of the unwanted vertical lines that points to the y axis labels like the image below with red mark.
Template:
<d-chartrecord
:chart-data="datacollection"
v-bind:options="options"
:height="200"
></d-chartrecord>
Script:
export default {
data () {
return {
datacollection: {},
options: {
responsive: true,
legend: {
display: false,
},
scales: {
xAxes: [{
gridLines: {
display: true,
color: '#D7D7D7'
},
ticks: {
fontSize: 8,
beginAtZero: true
},
gridLines: {
display: true,
}
}],
yAxes: [{
display: true,
ticks: {
fontSize: 8,
beginAtZero: true,
stepSize: 50,
maxTicksLimit: 3
}
}],
}
},
}
},
mounted () {
this.putData()
},
methods: {
putData () {
this.datacollection = {
labels: ['JAN', 'FEB', 'MAR', 'APR', 'MAY', 'JUN', 'JUL', 'AUG', 'SEP', 'OCT', 'NOV', 'DEC'],
datasets: [{
lineTension: 0,
radius: 4,
borderWidth: 1,
borderColor: '#F2A727',
pointBackgroundColor:[ '#fff', '#fff', '#fff', '#fff', '#fff', '#F2A727'],
backgroundColor: 'transparent',
data: [this.getRandomInt(), this.getRandomInt(), this.getRandomInt(), this.getRandomInt(), this.getRandomInt(), this.getRandomInt()]
}]
}
},
getRandomInt () {
return Math.floor(Math.random() * (95)) + 5
}
}
}
In chart.js, gridLines provides an option tickMarkLength to disable the length beyond the axes, Eg:
yAxes: [{
gridLines: {
tickMarkLength: 0,
},
}]
xAxes: [{
gridLines: {
tickMarkLength: 0,
},
}]
Unfortunately, there isn't any native functionality for this in ChartJS at the moment. You would rather need to create a chart plugin to achieve that.
ᴘʟᴜɢɪɴ (ᴅʀᴀᴡ x-ᴀxɪꜱ ɢʀɪᴅ-ʟɪɴᴇꜱ)
­
Chart.plugins.register({
beforeDraw: function(chart) {
var ctx = chart.chart.ctx,
x_axis = chart.scales['x-axis-0'],
topY = chart.scales['y-axis-0'].top,
bottomY = chart.scales['y-axis-0'].bottom;
x_axis.options.gridLines.display = false;
x_axis.ticks.forEach(function(label, index) {
if (index === 0) return;
var x = x_axis.getPixelForValue(label);
ctx.save();
ctx.beginPath();
ctx.strokeStyle = x_axis.options.gridLines.color;
ctx.moveTo(x, topY);
ctx.lineTo(x, bottomY);
ctx.stroke();
ctx.restore();
});
}
});
* place this at the top of your script
ᴡᴏʀᴋɪɴɢ ᴇxᴀᴍᴘʟᴇ ⧩
Chart.plugins.register({
beforeDraw: function(chart) {
var ctx = chart.chart.ctx,
x_axis = chart.scales['x-axis-0'],
topY = chart.scales['y-axis-0'].top,
bottomY = chart.scales['y-axis-0'].bottom;
x_axis.options.gridLines.display = false; // hide original grid-lines
// loop through x-axis ticks
x_axis.ticks.forEach(function(label, index) {
if (index === 0) return;
var x = x_axis.getPixelForValue(label);
ctx.save();
ctx.beginPath();
ctx.strokeStyle = x_axis.options.gridLines.color;
ctx.moveTo(x, topY);
ctx.lineTo(x, bottomY);
ctx.stroke();
ctx.restore();
});
}
});
var chart = new Chart(ctx, {
type: 'line',
data: {
labels: ['Jan', 'Feb', 'Mar', 'Apr', 'May'],
datasets: [{
label: 'LINE',
data: [3, 1, 4, 2, 5],
backgroundColor: 'rgba(0, 119, 290, 0.2)',
borderColor: 'rgba(0, 119, 290, 0.6)',
fill: false,
tension: 0
}]
},
options: {
scales: {
yAxes: [{
ticks: {
beginAtZero: true,
stepSize: 1
},
gridLines: {
display: false
}
}]
}
}
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/2.6.0/Chart.min.js"></script>
<canvas id="ctx"></canvas>
in Chart.js (I'm using version 2.9), gridLines also provides an option to disable the tick mark : drawTicks.
scales: {
xAxes: [{
gridLines:{
drawTicks: false
}
}]
}

Dashed line for missing data in Chart.JS (spanGaps style)

I have a chart with data points that are null. Chart.js does the correct thing and skips those data points but I would like to have a 'dashed' line fill in the missing parts. For example, in the below code the line should be solid as in the CodePen link, but should be dashed from "Blue" to "Yellow". I know the spanGaps option is available but I would like to apply a different style to it.
Does anyone have any thoughts on how to accomplish this? I am using Chart.js 2.x
var ctx = document.getElementById("myChart");
var myChart = new Chart(ctx, {
type: 'line',
data: {
labels: ["Red", "Blue", "Orange", "Yellow"],
datasets: [{
label: '# of Votes',
data: [12, 19, null, 3]
}]
},
options: {
scales: {
yAxes: [{
ticks: {
beginAtZero:true
}
}]
}
}
});
CodePen
The Plugin Core API offers a range of hooks that may be used for performing custom code. You can use the beforeDraw hook to draw lines of different style between different datapoints using text directly on the canvas using CanvasRenderingContext2D.
Please take a look at the runnable code snippet below:
var ctx = document.getElementById("myChart");
var myChart = new Chart(ctx, {
type: 'line',
plugins: [{
beforeDraw: chart => {
var ctx = chart.chart.ctx;
ctx.save();
let xAxis = chart.scales['x-axis-0'];
let yAxis = chart.scales['y-axis-0'];
let dataset = chart.data.datasets[0];
var valueFrom = null;
var valueFromIndex = 0;
var xFrom = null;
let yFrom = null;
ctx.strokeStyle = dataset.borderColor;
dataset.data.forEach((value, index) => {
if (value != null) {
var x = xAxis.getPixelForTick(index);
var y = yAxis.getPixelForValue(value);
if (valueFrom != null) {
ctx.lineWidth = dataset.borderWidth;
if (index - valueFromIndex > 1) {
ctx.setLineDash([5, 5]);
} else {
ctx.setLineDash([]);
}
ctx.beginPath();
ctx.moveTo(xFrom, yFrom);
ctx.lineTo(x, y);
ctx.stroke();
}
valueFrom = value;
valueFromIndex = index;
xFrom = x;
yFrom = y;
}
});
ctx.restore();
}
}],
data: {
labels: ["A", "B", "C", "D", "E", "F", "G"],
datasets: [{
label: 'My Dataset',
data: [12, 19, null, 3, 6, null, 8],
backgroundColor: 'rgba(255, 99, 132, 0.6)',
borderColor: 'rgb(255, 99, 132)',
borderWidth: 3,
showLine: false,
}]
},
options: {
animation: {
duration: 0
},
scales: {
yAxes: [{
ticks: {
beginAtZero: true
}
}]
}
}
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/2.9.3/Chart.min.js"></script>
<canvas id="myChart" height="90"></canvas>