问题
Can't find a way to position tooltip in center of bar like this (yeah, I know it is not exactly in center on this screenshot, but still):
If I use custom tooltip option, I can get only x
and y
positions of a caret on top of a bar. But can't get height/width of a bar.
Here is a part of an options object that I pass to Chart constructor:
const options = {
tooltips: {
enabled: false,
custom: (tooltip) => {
// Retrieving valuable props from tooltip (caretX, caretY)
// and creating custom tooltip that is positioned
// on top of a bar
}
}
// other options
}
const chart = new Chart(ctx, {
type: 'bar',
data,
options
})
回答1:
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>
回答2:
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
回答3:
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/
来源:https://stackoverflow.com/questions/45415925/position-tooltip-in-center-of-bar