4π
As you may already know, to position a custom tooltip at the center of a bar, you might need some of itΒβs (bar) properties, such as β width, height, top and left position. But unfortunately, there is no straight-forward way of getting these properties, rather you need to calculate them yourself.
To obtain / calculate those properties, you can use the following function (can be named anything), which will return an object containing all these (width, height, top, left) properties of a particular bar, when hovered over.
function getBAR(chart) {
const dataPoints = tooltipModel.dataPoints,
datasetIndex = chart.data.datasets.length - 1,
datasetMeta = chart.getDatasetMeta(datasetIndex),
scaleBottom = chart.scales['y-axis-0'].bottom,
bar = datasetMeta.data[dataPoints[0].index]._model,
canvasPosition = chart.canvas.getBoundingClientRect(),
paddingLeft = parseFloat(getComputedStyle(chart.canvas).paddingLeft),
paddingTop = parseFloat(getComputedStyle(chart.canvas).paddingTop),
scrollLeft = document.body.scrollLeft,
scrollTop = document.body.scrollTop;
return {
top: bar.y + canvasPosition.top + paddingTop + scrollTop,
left: bar.x - (bar.width / 2) + canvasPosition.left + paddingLeft + scrollLeft,
width: bar.width,
height: scaleBottom - bar.y
}
}
Calculate Center Position
After retrieving the required properties, you can calculate center position of a bar as such :
πππππππ β=β πππ-ππππ + (πππ-π ππππ / πΈ)
Β
πππππππ β=β πππ-πππ + (πππ-ππππππ / πΈ)
then, create your custom tooltip element and position it accordingly.
α΄Κα΄α΄ Ιͺα΄α΄‘
ΚΙͺα΄ α΄ α΄xα΄α΄α΄Κᴠ⧩
const chart = new Chart(ctx, {
type: 'bar',
data: {
labels: ['Jan', 'Feb', 'Mar', 'Apr'],
datasets: [{
label: 'Revenue',
data: [4, 2, 3, 3],
backgroundColor: '#2d4e6d'
}, {
label: 'Expenses',
data: [3, 3.5, 4, 1],
backgroundColor: '#c06526'
}, {
label: 'Profit',
data: [3, 2.5, 4, 2],
backgroundColor: '#e0ecf0'
}]
},
options: {
scales: {
xAxes: [{
stacked: true
}],
yAxes: [{
stacked: true,
ticks: {
beginAtZero: true
}
}]
},
tooltips: {
enabled: false,
custom: function(tooltipModel) {
/*** jQuery IS USED FOR SIMPLICITY ***/
/* TOOLTIP & CARET ELEMENT */
let tooltip = $('#tooltip');
let tooltipCaret = $('#tooltip-caret');
/* CREATE TOOLTIP & CARET ELEMENT AT FIRST RENDER */
if (!tooltip.length && !tooltipCaret.length) {
tooltip = $('<div></div>').attr('id', 'tooltip');
tooltipCaret = $('<div></div>').attr('id', 'tooltip-caret');
$('body').append(tooltip, tooltipCaret);
}
/* HIDE IF NO TOOLTIP */
if (!tooltipModel.opacity) {
tooltip.hide();
tooltipCaret.hide();
return;
}
/* GET BAR PROPS (width, height, top, left) */
const barWidth = getBAR(this._chart).width,
barHeight = getBAR(this._chart).height,
barTop = getBAR(this._chart).top,
barLeft = getBAR(this._chart).left;
/* SET STYLE FOR TOOLTIP
(these can also be set in separate css file) */
tooltip.css({
"display": "inline-block",
"position": "absolute",
"color": "rgba(255, 255, 255, 1)",
"background": "rgba(0, 0, 0, 0.7)",
"padding": "5px",
"font": "12px Arial",
"border-radius": "3px",
"white-space": "nowrap",
"pointerEvents": "none"
});
/* SET STYLE FOR TOOLTIP CARET
(these can also be set in separate css file) */
tooltipCaret.css({
"display": "block",
"position": "absolute",
"width": 0,
"pointerEvents": "none",
"border-style": "solid",
"border-width": "8px 10px 8px 0",
"border-color": "transparent rgba(0, 0, 0, 0.7) transparent transparent"
});
/* ADD CONTENT IN TOOLTIP */
tooltip.text('ChartJS');
tooltip.append('<br><div class="color-box"></div><label style="display: block; margin: -16px 0 0 16px;"> Custom Tooltip<label>');
/* POSITION TOOLTIP & CARET
(position should be set after tooltip & caret is rendered) */
const centerX = barLeft + (barWidth / 2),
centerY = barTop + (barHeight / 2)
tooltip.css({
"top": centerY - (tooltip.outerHeight() / 2) + 'px',
"left": centerX + tooltipCaret.outerWidth() + 'px'
});
tooltipCaret.css({
"top": centerY - (tooltipCaret.outerHeight() / 2) + 'px',
"left": centerX + 'px'
});
/* FUNCTION TO GET BAR PROPS */
function getBAR(chart) {
const dataPoints = tooltipModel.dataPoints,
datasetIndex = chart.data.datasets.length - 1,
datasetMeta = chart.getDatasetMeta(datasetIndex),
scaleBottom = chart.scales['y-axis-0'].bottom,
bar = datasetMeta.data[dataPoints[0].index]._model,
canvasPosition = chart.canvas.getBoundingClientRect(),
paddingLeft = parseFloat(getComputedStyle(chart.canvas).paddingLeft),
paddingTop = parseFloat(getComputedStyle(chart.canvas).paddingTop),
scrollLeft = document.body.scrollLeft,
scrollTop = document.body.scrollTop;
return {
top: bar.y + canvasPosition.top + paddingTop + scrollTop,
left: bar.x - (bar.width / 2) + canvasPosition.left + paddingLeft + scrollLeft,
width: bar.width,
height: scaleBottom - bar.y
}
}
}
}
}
});
.color-box{width:12px;height:12px;background:#c06526;display:inline-block;margin-top:5px}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/2.6.0/Chart.min.js"></script>
<canvas id="ctx"></canvas>
UPDATE
IF you wish to position tooltip at the center of each bar segment then, use the following function :
function getBARSegment(chart) {
const dataPoints = tooltipModel.dataPoints,
bar = chart.active[dataPoints[0].datasetIndex]._model,
canvasPosition = chart.canvas.getBoundingClientRect(),
paddingLeft = parseFloat(getComputedStyle(chart.canvas).paddingLeft),
paddingTop = parseFloat(getComputedStyle(chart.canvas).paddingTop),
scrollLeft = document.body.scrollLeft,
scrollTop = document.body.scrollTop;
return {
top: bar.y + canvasPosition.top + paddingTop + scrollTop,
left: bar.x - (bar.width / 2) + canvasPosition.left + paddingLeft + scrollLeft,
width: bar.width,
height: bar.base - bar.y
}
}
α΄Κα΄α΄ Ιͺα΄α΄‘
ΚΙͺα΄ α΄ α΄xα΄α΄α΄Κᴠ⧩
const chart = new Chart(ctx, {
type: 'bar',
data: {
labels: ['Jan', 'Feb', 'Mar', 'Apr'],
datasets: [{
label: 'Revenue',
data: [4, 2, 3, 3],
backgroundColor: '#2d4e6d'
}, {
label: 'Expenses',
data: [3, 3.5, 4, 1],
backgroundColor: '#c06526'
}, {
label: 'Profit',
data: [3, 2.5, 4, 2],
backgroundColor: '#e0ecf0'
}]
},
options: {
scales: {
xAxes: [{
stacked: true
}],
yAxes: [{
stacked: true,
ticks: {
beginAtZero: true
}
}]
},
tooltips: {
enabled: false,
custom: function(tooltipModel) {
/*** jQuery IS USED FOR SIMPLICITY ***/
/* TOOLTIP & CARET ELEMENT */
let tooltip = $('#tooltip');
let tooltipCaret = $('#tooltip-caret');
/* CREATE TOOLTIP & CARET ELEMENT AT FIRST RENDER */
if (!tooltip.length && !tooltipCaret.length) {
tooltip = $('<div></div>').attr('id', 'tooltip');
tooltipCaret = $('<div></div>').attr('id', 'tooltip-caret');
$('body').append(tooltip, tooltipCaret);
}
/* HIDE IF NO TOOLTIP */
if (!tooltipModel.opacity) {
tooltip.hide();
tooltipCaret.hide();
return;
}
/* GET BAR PROPS (width, height, top, left) */
const barWidth = getBARSegment(this._chart).width,
barHeight = getBARSegment(this._chart).height,
barTop = getBARSegment(this._chart).top,
barLeft = getBARSegment(this._chart).left;
/* SET STYLE FOR TOOLTIP
(these can also be set in separate css file) */
tooltip.css({
"display": "inline-block",
"position": "absolute",
"color": "rgba(255, 255, 255, 1)",
"background": "rgba(0, 0, 0, 0.7)",
"padding": "5px",
"font": "12px Arial",
"border-radius": "3px",
"white-space": "nowrap",
"pointerEvents": "none"
});
/* SET STYLE FOR TOOLTIP CARET
(these can also be set in separate css file) */
tooltipCaret.css({
"display": "block",
"position": "absolute",
"width": 0,
"pointerEvents": "none",
"border-style": "solid",
"border-width": "8px 10px 8px 0",
"border-color": "transparent rgba(0, 0, 0, 0.7) transparent transparent"
});
/* ADD CONTENT IN TOOLTIP */
tooltip.text('ChartJS');
tooltip.append('<br><div class="color-box"></div><label style="display: block; margin: -16px 0 0 16px;"> Custom Tooltip<label>');
/* POSITION TOOLTIP & CARET
(position should be set after tooltip & caret is rendered) */
const centerX = barLeft + (barWidth / 2),
centerY = barTop + (barHeight / 2)
tooltip.css({
"top": centerY - (tooltip.outerHeight() / 2) + 'px',
"left": centerX + tooltipCaret.outerWidth() + 'px'
});
tooltipCaret.css({
"top": centerY - (tooltipCaret.outerHeight() / 2) + 'px',
"left": centerX + 'px'
});
/* FUNCTION TO GET BAR PROPS */
function getBARSegment(chart) {
const dataPoints = tooltipModel.dataPoints,
bar = chart.active[dataPoints[0].datasetIndex]._model,
canvasPosition = chart.canvas.getBoundingClientRect(),
paddingLeft = parseFloat(getComputedStyle(chart.canvas).paddingLeft),
paddingTop = parseFloat(getComputedStyle(chart.canvas).paddingTop),
scrollLeft = document.body.scrollLeft,
scrollTop = document.body.scrollTop;
return {
top: bar.y + canvasPosition.top + paddingTop + scrollTop,
left: bar.x - (bar.width / 2) + canvasPosition.left + paddingLeft + scrollLeft,
width: bar.width,
height: bar.base - bar.y
}
}
}
}
}
});
.color-box{width:12px;height:12px;background:#c06526;display:inline-block;margin-top:5px}
<script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/2.6.0/Chart.min.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<canvas id="ctx"></canvas>
4π
For Chartjs 3+ (tested in 3.5.1)
Chart.Tooltip.positioners.center = function (elements, eventPosition) {
if(elements.length){ // to prevent errors in the console
const { x, y, base } = elements[0].element; // _model doesn't exist anymore
const height = !base ? 0 : base - y;// so it doesn't break in combo graphs like lines + bars
return { x, y: y + (height / 2) };
}
return false; // without this it gets stuck in the last active tooltip
};
Set this custom "center" position in options.plugins.tooltip.position instead of the previous options.tooltips.position
Chartjs 2.8 allows you to add custom position modes for tooltips. With this you can create a center position option:
Chart.Tooltip.positioners.center = function (elements) {
const { x, y, base } = elements[0]._model;
const height = base - y;
return { x, y: y + (height / 2) };
};
See fiddle for working example: https://jsfiddle.net/astroash/wk5y0fqd/36/
1π
You can use values from the datasets
to workout the relative height of the item being hovered over and adjust the CSS accordingly.
The following is close to the centre, but is not the exact centre. My calculations need correcting if you want exactness.
Inside the custom tooltip function include the following:
// find relative proportion
var dataIndex = tooltip.dataPoints[0].index;
var datasetIndex = tooltip.dataPoints[0].datasetIndex;
var totalHeight = 0;
var thisHeight = this._chart.config.data.datasets[datasetIndex].data[dataIndex];
for (var i = 0; i <= datasetIndex; i++)
{
totalHeight += this._chart.config.data.datasets[i].data[dataIndex];
}
var positionY = this._chart.canvas.offsetTop;
var positionX = this._chart.canvas.offsetLeft;
var chartHeight = this._chart.canvas.scrollHeight;
var tooltipHalfHeight = tooltip.height / 2;
// Display, position, and set styles for font
tooltipEl.style.left = positionX + tooltip.caretX + 'px';
tooltipEl.style.top = tooltip.caretY + ((chartHeight - tooltip.caretY - positionY) * (thisHeight / totalHeight / 2)) - tooltipHalfHeight + 'px';
<!DOCTYPE html>
<html>
<head>
<link rel="stylesheet" href="style.css" />
<script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/2.6.0/Chart.bundle.min.js"></script>
<script src="script.js"></script>
<style>
canvas{
-moz-user-select: none;
-webkit-user-select: none;
-ms-user-select: none;
}
#chartjs-tooltip {
opacity: 1;
position: absolute;
background: rgba(0, 0, 0, .7);
color: white;
border-radius: 3px;
-webkit-transition: all .1s ease;
transition: all .1s ease;
pointer-events: none;
/*-webkit-transform: translate(-50%, 0);
transform: translate(-50%, 0);*/
}
.chartjs-tooltip-key {
display: inline-block;
width: 10px;
height: 10px;
margin-right: 10px;
}
</style>
</head>
<body>
<div id="chartjs-tooltip">
<table></table>
</div>
<canvas id="myChart" width="400" height="400"></canvas>
<script>
var customTooltips = function(tooltip) {
// Tooltip Element
var tooltipEl = document.getElementById('chartjs-tooltip');
if (!tooltipEl) {
tooltipEl = document.createElement('div');
tooltipEl.id = 'chartjs-tooltip';
tooltipEl.innerHTML = "<table></table>"
this._chart.canvas.parentNode.appendChild(tooltipEl);
}
// Hide if no tooltip
if (tooltip.opacity === 0) {
tooltipEl.style.opacity = 0;
return;
}
// Set caret Position
tooltipEl.classList.remove('above', 'below', 'no-transform');
if (tooltip.yAlign) {
tooltipEl.classList.add(tooltip.yAlign);
} else {
tooltipEl.classList.add('no-transform');
}
function getBody(bodyItem) {
return bodyItem.lines;
}
// Set Text
if (tooltip.body) {
var titleLines = tooltip.title || [];
var bodyLines = tooltip.body.map(getBody);
var innerHtml = '<thead>';
titleLines.forEach(function(title) {
innerHtml += '<tr><th>' + title + '</th></tr>';
});
innerHtml += '</thead><tbody>';
bodyLines.forEach(function(body, i) {
var colors = tooltip.labelColors[i];
var style = 'background:' + colors.backgroundColor;
style += '; border-color:' + colors.borderColor;
style += '; border-width: 2px';
var span = '<span class="chartjs-tooltip-key" style="' + style + '"></span>';
innerHtml += '<tr><td>' + span + body + '</td></tr>';
});
innerHtml += '</tbody>';
var tableRoot = tooltipEl.querySelector('table');
tableRoot.innerHTML = innerHtml;
}
// find relative proportion
var dataIndex = tooltip.dataPoints[0].index;
var datasetIndex = tooltip.dataPoints[0].datasetIndex;
var totalHeight = 0;
var thisHeight = this._chart.config.data.datasets[datasetIndex].data[dataIndex];
for (var i = 0; i <= datasetIndex; i++)
{
totalHeight += this._chart.config.data.datasets[i].data[dataIndex];
}
var positionY = this._chart.canvas.offsetTop;
var positionX = this._chart.canvas.offsetLeft;
var chartHeight = this._chart.canvas.scrollHeight;
var tooltipHalfHeight = tooltip.height / 2;
// Display, position, and set styles for font
tooltipEl.style.opacity = 1;
tooltipEl.style.left = positionX + tooltip.caretX + 'px';
tooltipEl.style.top = tooltip.caretY + ((chartHeight - tooltip.caretY - positionY) * (thisHeight / totalHeight / 2)) - tooltipHalfHeight + 'px';
tooltipEl.style.fontFamily = tooltip._fontFamily;
tooltipEl.style.fontSize = tooltip.fontSize;
tooltipEl.style.fontStyle = tooltip._fontStyle;
tooltipEl.style.padding = tooltip.yPadding + 'px ' + tooltip.xPadding + 'px';
};
var ctx = document.getElementById("myChart").getContext('2d');
var myChart = new Chart(ctx, {
type: 'bar',
data: {
labels: ["This", "That", "Something else", "Important thing", "Oh really?", "What!!"],
datasets: [{
label: '# of Votes',
data: [12, 19, 3, 5, 2, 3],
backgroundColor: [
'rgba(255, 99, 132, 0.2)',
'rgba(54, 162, 235, 0.2)',
'rgba(255, 206, 86, 0.2)',
'rgba(75, 192, 192, 0.2)',
'rgba(153, 102, 255, 0.2)',
'rgba(255, 159, 64, 0.2)'
],
borderColor: [
'rgba(255,99,132,1)',
'rgba(54, 162, 235, 1)',
'rgba(255, 206, 86, 1)',
'rgba(75, 192, 192, 1)',
'rgba(153, 102, 255, 1)',
'rgba(255, 159, 64, 1)'
],
borderWidth: 1
}, {
data: [2, 5, 13, 5, 3, 4],
backgroundColor: [
'rgba(255, 206, 86, 0.2)',
'rgba(75, 192, 192, 0.2)',
'rgba(153, 102, 255, 0.2)',
'rgba(255, 159, 64, 0.2)',
'rgba(255, 99, 132, 0.2)',
'rgba(54, 162, 235, 0.2)'
],
borderColor: [
'rgba(255, 206, 86, 1)',
'rgba(75, 192, 192, 1)',
'rgba(153, 102, 255, 1)',
'rgba(255, 159, 64, 1)',
'rgba(255,99,132,1)',
'rgba(54, 162, 235, 1)'
],
borderWidth: 1
}]
},
options: {
scales: {
xAxes: [{
stacked: true,
}],
yAxes: [{
stacked: true
}]
},
tooltips: {
enabled: false,
custom: customTooltips,
}
}
});
</script>
</body>
</html>
Plunker: http://plnkr.co/edit/f0EqpYe6zJMyIDxY4Xg9?p=preview