Charts.js 图表上的工具提示重叠文本
Charts.js tooltip overlapping text on chart
由于Charts.js还不支持标注,我在绘制图表后添加了数据点的标注。使用 ctx.fillText 如下所示。
animation: {
animateScale: true,
animateRotate: true,
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.fillStyle = this.chart.config.options.defaultFontColor;
ctx.textBaseline = 'bottom';
this.data.datasets.forEach(function (dataset, i) {
var meta = chartInstance.controller.getDatasetMeta(i);
meta.data.forEach(function (bar, index) {
data = dataset.data[index];
ctx.fillText(data, bar._model.x, bar._model.y - 5);
});
});
}
}
除了现在工具提示显示在新添加的文本下方之外,效果很好。这不是很明显,但是有时它会在不好的地方重叠,这意味着您看不到后面的工具提示。
有没有办法设置 ctx.fillText 或工具提示的 z-index,以便我可以正确地对它们进行分层?
对于我设法解决这个问题的任何感兴趣的人,我最终查看了 charts.js 中的工具提示绘图函数并将其修改版本用作自定义工具提示,从而在注释之后绘制工具提示已添加。
首先将此添加到您的选项中
config = {
options: {
tooltips: {
enabled: false,
custom: customTooltips
}
然后调用下面的自定义工具提示函数。
var currentX = null;
var currentY = null;
var customTooltips = function (tooltip) {
var helpers = Chart.helpers;
var ctx = this._chart.ctx;
var vm = this._view;
if (vm == null || ctx == null || helpers == null || vm.opacity === 0) {
return;
}
var tooltipSize = this.getTooltipSize(vm);
var pt = {
x: vm.x,
y: vm.y
};
if (currentX == vm.x && currentY == vm.y) {
return;
}
currentX = vm.x;
currentY = vm.y;
// IE11/Edge does not like very small opacities, so snap to 0
var opacity = Math.abs(vm.opacity < 1e-3) ? 0 : vm.opacity;
// Draw Background
var bgColor = helpers.color(vm.backgroundColor);
ctx.fillStyle = bgColor.alpha(opacity * bgColor.alpha()).rgbString();
helpers.drawRoundedRectangle(ctx, pt.x, pt.y, tooltipSize.width, tooltipSize.height, vm.cornerRadius);
ctx.fill();
// Draw Caret
this.drawCaret(pt, tooltipSize, opacity);
// Draw Title, Body, and Footer
pt.x += vm.xPadding;
pt.y += vm.yPadding;
// Titles
this.drawTitle(pt, vm, ctx, opacity);
// Body
this.drawBody(pt, vm, ctx, opacity);
// Footer
this.drawFooter(pt, vm, ctx, opacity);
};
@user3284707 实际上,您需要做的是在工具提示之前的条形顶部绘制数字,您正在绘制它们 onComplete
,将它们置于所有内容之上。
我使用以下方法绘制这些数字:
Chart.plugins.register({
beforeDraw: function(chartInstance) {
if (chartInstance.config.options.showDatapoints) {
var helpers = Chart.helpers;
var ctx = chartInstance.chart.ctx;
var fontColor = helpers.getValueOrDefault(chartInstance.config.options.showDatapoints.fontColor, chartInstance.config.options.defaultFontColor);
// render the value of the chart above the bar
ctx.font = Chart.helpers.fontString(Chart.defaults.global.defaultFontSize, 'normal', Chart.defaults.global.defaultFontFamily);
ctx.textAlign = 'center';
ctx.textBaseline = 'bottom';
ctx.fillStyle = fontColor;
chartInstance.data.datasets.forEach(function (dataset) {
for (var i = 0; i < dataset.data.length; i++) {
var model = dataset._meta[Object.keys(dataset._meta)[0]].data[i]._model;
var scaleMax = dataset._meta[Object.keys(dataset._meta)[0]].data[i]._yScale.maxHeight;
var yPos = (scaleMax - model.y) / scaleMax >= 0.93 ? model.y + 20 : model.y - 5;
var label = dataset.data[i] || '';
ctx.fillText(label.toLocaleString(), model.x, yPos);
}
});
}
}
});
注意那里的 beforeDraw
。
希望这对 3 年后有所帮助,我花了最后 30 分钟试图解决这个问题
如果有人想为这个问题寻找解决方案,那么有一种更简单的方法可以实现 OP 所需的功能。
不是在 onComplete
回调中绘制,而是在 afterDatasetsDraw
回调中绘制。它在绘制工具提示之前被调用。
如果您正在寻找问题中所写代码的近似解决方案,那么这里是代码:
let ctx2 = document.getElementById("barChart").getContext("2d");
let chart = new Chart(ctx2, {
type: 'bar',
data: {
labels: ['January', 'February', 'March', 'April', 'May', 'June', 'July'],
datasets: [{
label: 'Months',
data: ['20','30','10','15','50','35','25'],
backgroundColor: 'rgba(26,179,148,0.5)',
borderColor: 'rgba(75, 192, 192, 1)',
borderWidth: 1
}]
},
options: {
legend: {
display: false
},
responsive: true,
maintainAspectRatio: true,
legendCallback: function(chart) {
var text = [];
for (var i=0; i<chart.data.datasets.length; i++) {
text.push(chart.data.labels[i]);
}
return text.join("");
},
tooltips: {
mode: 'index',
callbacks: {
// Use the footer callback to display the sum of the items showing in the tooltip
title: function(tooltipItem, data) {
let title_str = data['labels'][tooltipItem[0]['index']];
let lastIndex = title_str.lastIndexOf(" ");
return title_str.substring(0, lastIndex);
},
label: function(tooltipItem, data) {
return 'val: '+data['datasets'][0]['data'][tooltipItem['index']];
},
},
},
scales: {
xAxes: [{
stacked: false,
beginAtZero: true,
// scaleLabel: {
// labelString: 'Month'
// },
ticks: {
min: 0,
autoSkip: false,
maxRotation: 60,
callback: function(label, index, labels) {
return label;
}
}
}]
}
},
plugins:[{
afterDatasetsDraw: function(chart,options) {
// var chartInstance = chart,
let ctx = chart.ctx;
ctx.font = Chart.defaults.global.defaultFontStyle;
ctx.fillStyle = Chart.defaults.global.textColor;
ctx.textAlign = "center";
ctx.textBaseline = "bottom";
chart.data.datasets.forEach(function (dataset, i) {
var meta = chart.controller.getDatasetMeta(i);
meta.data.forEach(function (bar, index) {
ctx.fillText(Math.round(dataset.data[index]), bar._model.x, bar._model.y - 5);
});
})
}
}]
});
document.getElementById('barChart').innerHTML = chart.generateLegend();
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/css/bootstrap.min.css" integrity="sha384-JcKb8q3iqJ61gNV9KGb8thSsNjpSL0n8PARn9HuZOnIxN0hoP+VmmDGMN5t9UJ0Z" crossorigin="anonymous">
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<div>
<canvas id="barChart" height="140"></canvas>
</div>
这里,我使用了插件afterDatasetDraw
。 https://www.chartjs.org/docs/latest/developers/plugins.html?h=afterdatasetsdraw
由于Charts.js还不支持标注,我在绘制图表后添加了数据点的标注。使用 ctx.fillText 如下所示。
animation: {
animateScale: true,
animateRotate: true,
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.fillStyle = this.chart.config.options.defaultFontColor;
ctx.textBaseline = 'bottom';
this.data.datasets.forEach(function (dataset, i) {
var meta = chartInstance.controller.getDatasetMeta(i);
meta.data.forEach(function (bar, index) {
data = dataset.data[index];
ctx.fillText(data, bar._model.x, bar._model.y - 5);
});
});
}
}
除了现在工具提示显示在新添加的文本下方之外,效果很好。这不是很明显,但是有时它会在不好的地方重叠,这意味着您看不到后面的工具提示。
有没有办法设置 ctx.fillText 或工具提示的 z-index,以便我可以正确地对它们进行分层?
对于我设法解决这个问题的任何感兴趣的人,我最终查看了 charts.js 中的工具提示绘图函数并将其修改版本用作自定义工具提示,从而在注释之后绘制工具提示已添加。
首先将此添加到您的选项中
config = {
options: {
tooltips: {
enabled: false,
custom: customTooltips
}
然后调用下面的自定义工具提示函数。
var currentX = null;
var currentY = null;
var customTooltips = function (tooltip) {
var helpers = Chart.helpers;
var ctx = this._chart.ctx;
var vm = this._view;
if (vm == null || ctx == null || helpers == null || vm.opacity === 0) {
return;
}
var tooltipSize = this.getTooltipSize(vm);
var pt = {
x: vm.x,
y: vm.y
};
if (currentX == vm.x && currentY == vm.y) {
return;
}
currentX = vm.x;
currentY = vm.y;
// IE11/Edge does not like very small opacities, so snap to 0
var opacity = Math.abs(vm.opacity < 1e-3) ? 0 : vm.opacity;
// Draw Background
var bgColor = helpers.color(vm.backgroundColor);
ctx.fillStyle = bgColor.alpha(opacity * bgColor.alpha()).rgbString();
helpers.drawRoundedRectangle(ctx, pt.x, pt.y, tooltipSize.width, tooltipSize.height, vm.cornerRadius);
ctx.fill();
// Draw Caret
this.drawCaret(pt, tooltipSize, opacity);
// Draw Title, Body, and Footer
pt.x += vm.xPadding;
pt.y += vm.yPadding;
// Titles
this.drawTitle(pt, vm, ctx, opacity);
// Body
this.drawBody(pt, vm, ctx, opacity);
// Footer
this.drawFooter(pt, vm, ctx, opacity);
};
@user3284707 实际上,您需要做的是在工具提示之前的条形顶部绘制数字,您正在绘制它们 onComplete
,将它们置于所有内容之上。
我使用以下方法绘制这些数字:
Chart.plugins.register({
beforeDraw: function(chartInstance) {
if (chartInstance.config.options.showDatapoints) {
var helpers = Chart.helpers;
var ctx = chartInstance.chart.ctx;
var fontColor = helpers.getValueOrDefault(chartInstance.config.options.showDatapoints.fontColor, chartInstance.config.options.defaultFontColor);
// render the value of the chart above the bar
ctx.font = Chart.helpers.fontString(Chart.defaults.global.defaultFontSize, 'normal', Chart.defaults.global.defaultFontFamily);
ctx.textAlign = 'center';
ctx.textBaseline = 'bottom';
ctx.fillStyle = fontColor;
chartInstance.data.datasets.forEach(function (dataset) {
for (var i = 0; i < dataset.data.length; i++) {
var model = dataset._meta[Object.keys(dataset._meta)[0]].data[i]._model;
var scaleMax = dataset._meta[Object.keys(dataset._meta)[0]].data[i]._yScale.maxHeight;
var yPos = (scaleMax - model.y) / scaleMax >= 0.93 ? model.y + 20 : model.y - 5;
var label = dataset.data[i] || '';
ctx.fillText(label.toLocaleString(), model.x, yPos);
}
});
}
}
});
注意那里的 beforeDraw
。
希望这对 3 年后有所帮助,我花了最后 30 分钟试图解决这个问题
如果有人想为这个问题寻找解决方案,那么有一种更简单的方法可以实现 OP 所需的功能。
不是在 onComplete
回调中绘制,而是在 afterDatasetsDraw
回调中绘制。它在绘制工具提示之前被调用。
如果您正在寻找问题中所写代码的近似解决方案,那么这里是代码:
let ctx2 = document.getElementById("barChart").getContext("2d");
let chart = new Chart(ctx2, {
type: 'bar',
data: {
labels: ['January', 'February', 'March', 'April', 'May', 'June', 'July'],
datasets: [{
label: 'Months',
data: ['20','30','10','15','50','35','25'],
backgroundColor: 'rgba(26,179,148,0.5)',
borderColor: 'rgba(75, 192, 192, 1)',
borderWidth: 1
}]
},
options: {
legend: {
display: false
},
responsive: true,
maintainAspectRatio: true,
legendCallback: function(chart) {
var text = [];
for (var i=0; i<chart.data.datasets.length; i++) {
text.push(chart.data.labels[i]);
}
return text.join("");
},
tooltips: {
mode: 'index',
callbacks: {
// Use the footer callback to display the sum of the items showing in the tooltip
title: function(tooltipItem, data) {
let title_str = data['labels'][tooltipItem[0]['index']];
let lastIndex = title_str.lastIndexOf(" ");
return title_str.substring(0, lastIndex);
},
label: function(tooltipItem, data) {
return 'val: '+data['datasets'][0]['data'][tooltipItem['index']];
},
},
},
scales: {
xAxes: [{
stacked: false,
beginAtZero: true,
// scaleLabel: {
// labelString: 'Month'
// },
ticks: {
min: 0,
autoSkip: false,
maxRotation: 60,
callback: function(label, index, labels) {
return label;
}
}
}]
}
},
plugins:[{
afterDatasetsDraw: function(chart,options) {
// var chartInstance = chart,
let ctx = chart.ctx;
ctx.font = Chart.defaults.global.defaultFontStyle;
ctx.fillStyle = Chart.defaults.global.textColor;
ctx.textAlign = "center";
ctx.textBaseline = "bottom";
chart.data.datasets.forEach(function (dataset, i) {
var meta = chart.controller.getDatasetMeta(i);
meta.data.forEach(function (bar, index) {
ctx.fillText(Math.round(dataset.data[index]), bar._model.x, bar._model.y - 5);
});
})
}
}]
});
document.getElementById('barChart').innerHTML = chart.generateLegend();
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/css/bootstrap.min.css" integrity="sha384-JcKb8q3iqJ61gNV9KGb8thSsNjpSL0n8PARn9HuZOnIxN0hoP+VmmDGMN5t9UJ0Z" crossorigin="anonymous">
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<div>
<canvas id="barChart" height="140"></canvas>
</div>
这里,我使用了插件afterDatasetDraw
。 https://www.chartjs.org/docs/latest/developers/plugins.html?h=afterdatasetsdraw