如何在 Chart.JS 中显示饼图的数据子集,同时在悬停时仍显示超集?
How can I show a subset of data on pie pieces in Chart.JS while still displaying the superset when hovering?
我有一个饼图,当鼠标悬停在一个饼图上时,它看起来像这样:
除了想要图例 ,我对此很满意,但我只希望百分比值在饼图块中显示 "all the time" - 并且仍然有 <name> (<%val>): <data>
悬停时显示。
换句话说,我希望馅饼看起来像这样:
我怎样才能把那一份数据(百分比)分解出来,然后把它画到每一块饼上?
这是我目前使用的代码:
var formatter = new Intl.NumberFormat("en-US");
var data = {
labels: [
"Bananas (18%)",
"Lettuce, Romaine (14%)",
"Melons, Watermelon (10%)",
"Pineapple (10%)",
"Berries (10%)",
"Lettuce, Spring Mix (9%)",
"Broccoli (8%)",
"Melons, Honeydew (7%)",
"Grapes (7%)",
"Melons, Cantaloupe (7%)"
],
datasets: [
{
data: [2755, 2256, 1637, 1608, 1603, 1433, 1207, 1076, 1056, 1048],
backgroundColor: [
"#FFE135",
"#3B5323",
"#fc6c85",
"#ffec89",
"#021c3d",
"#3B5323",
"#046b00",
"#cef45a",
"#421C52",
"#FEA620"
]
}
]
};
var optionsPie = {
responsive: true,
scaleBeginAtZero: true,
tooltips: {
callbacks: {
label: function (tooltipItem, data) {
return data.labels[tooltipItem.index] + ": " +
formatter.format(data.datasets[tooltipItem.datasetIndex].data[tooltipItem.index]);
}
}
}
};
var ctx = $("#top10ItemsChart").get(0).getContext("2d");
var top10PieChart = new Chart(ctx,
{
type: 'pie',
data: data,
options: optionsPie
});
$("#top10Legend").html(top10PieChart.generateLegend());
我是否需要添加 afterDraw() 事件?如果需要,它需要如何实现?
更新
我尝试向图表构造函数添加 onAnimationComplete() 回调:
var top10PieChart = new Chart(ctx,
{
type: 'pie',
data: data,
options: optionsPie,
onAnimationComplete: function () {
var ctx = this.chart.ctx;
ctx.font = this.scale.font;
ctx.fillStyle = this.scale.textColor;
ctx.textAlign = "center";
ctx.textBaseline = "center";
this.datasets.forEach(function(dataset) {
dataset.points.forEach(function(points) {
ctx.fillText(points.value, points.x, points.y - 10);
});
});
}
});
...但它什么也没做。
更新 2
我还尝试将以下内容附加到选项对象:
,
tooltipTemplate: "<%= value %>",
onAnimationComplete: function () {
this.showTooltip(this.datasets[0].bars, true);
}
...结果相同(无变化)。
更新 3
好的,我希望答案 能奏效,并根据那里的答案为我的饼图编写了这个新代码:
添加到选项饼图:
showTooltips: false,
onAnimationProgress: drawSegmentValues
检索元素引用后添加:
var midX = canvas.width / 2;
var midY = canvas.height / 2
构造饼图实例后添加:
var radius = top10PieChart.outerRadius;
添加函数 drawSegmentValues()
在上下文中(没有双关语意):
// Top 10 Pie Chart
var formatter = new Intl.NumberFormat("en-US");
var data = {
labels: [
"Bananas (18%)",
"Lettuce, Romaine (14%)",
"Melons, Watermelon (10%)",
"Pineapple (10%)",
"Berries (10%)",
"Lettuce, Spring Mix (9%)",
"Broccoli (8%)",
"Melons, Honeydew (7%)",
"Grapes (7%)",
"Melons, Cantaloupe (7%)"
],
datasets: [
{
data: [2755, 2256, 1637, 1608, 1603, 1433, 1207, 1076
1056, 1048],
backgroundColor: [
"#FFE135",
"#3B5323",
"#fc6c85",
"#ffec89",
"#021c3d",
"#3B5323",
"#046b00",
"#cef45a",
"#421C52",
"#FEA620"
]
}
]
};
var optionsPie = {
responsive: true,
scaleBeginAtZero: true,
legend: {
display: false
},
tooltips: {
callbacks: {
label: function (tooltipItem, data) {
return data.labels[tooltipItem.index] + ": " +
formatter.format
data.datasets[tooltipItem.datasetIndex].data[
tooltipItem.index]);
}
}
},
showTooltips: false,
onAnimationProgress: drawSegmentValues
};
var ctx = $("#top10ItemsChart").get(0).getContext("2d");
var midX = canvas.width / 2;
var midY = canvas.height / 2
var top10PieChart = new Chart(ctx,
{
type: 'pie',
data: data,
options: optionsPie//,
});
var radius = top10PieChart.outerRadius;
$("#top10Legend").html(top10PieChart.generateLegend());
// </ Top 10 Pie Chart
// Price Compliance Bar Chart
. . . this horizontal bar chart code elided for brevity, bu
unchanged from their working state
// Forecast/Impact Analysis Bar chart
. . . this horizontal bar chart code also elided for brevity, bu
unchanged from their working state
function drawSegmentValues() {
for (var i = 0; i < top10PieChart.segments.length; i++) {
ctx.fillStyle = "white";
var textSize = canvas.width / 10;
ctx.font = textSize + "px Verdana";
// Get needed variables
var value = top10PieChart.segments[i].value;
var startAngle = top10PieChart.segments[i].startAngle;
var endAngle = top10PieChart.segments[i].endAngle;
var middleAngle = startAngle + ((endAngle - startAngle)
2);
// Compute text location
var posX = (radius / 2) * Math.cos(middleAngle) + midX;
var posY = (radius / 2) * Math.sin(middleAngle) + midY;
// Text offside by middle
var w_offset = ctx.measureText(value).width / 2;
var h_offset = textSize / 4;
ctx.fillText(value, posX - w_offset, posY + h_offset);
}
}
...但它完全淹没了所有三个图表,什么也没留下 visible/drawing。
更新 4
顺便说一句,我尝试了给定的答案,但没有 result/change - 它仍然按照设计在悬停时显示数据,但除此之外别无其他。
另外,相关的fiddle同时显示数值和百分比;由于房地产限制,我希望百分比仅为 "always on" - "whole enchilada" 仅在悬停时可见。
好的,看来您更新后的代码中存在一些简单的语法错误,这些错误可能是在复制过程中发生的。忽略那些,您可以通过在动画选项中创建一个函数来管理它。这里是 fiddle. Thanks to Hung Tran's work .
animation: {
duration: 0,
easing: "easeOutQuart",
onComplete: function () {
var ctx = this.chart.ctx;
ctx.font = Chart.helpers.fontString(Chart.defaults.global.defaultFontFamily, 'normal', Chart.defaults.global.defaultFontFamily);
ctx.textAlign = 'center';
ctx.textBaseline = 'bottom';
this.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,
total = dataset._meta[Object.keys(dataset._meta)[0]].total,
mid_radius = model.innerRadius + (model.outerRadius - model.innerRadius)/2,
start_angle = model.startAngle,
end_angle = model.endAngle,
mid_angle = start_angle + (end_angle - start_angle)/2;
var x = mid_radius * Math.cos(mid_angle);
var y = mid_radius * Math.sin(mid_angle);
ctx.fillStyle = '#fff';
if (i == 3){ // Darker text color for lighter background
ctx.fillStyle = '#444';
}
var percent = String(Math.round(dataset.data[i]/total*100)) + "%";
// this prints the data number
ctx.fillText(dataset.data[i], model.x + x, model.y + y);
// this prints the percentage
ctx.fillText(percent, model.x + x, model.y + y + 15);
}
});
}
}
以下解决方案使用与 but using Chart.js plugins 相同的计算,这带来了 额外的好处 :
不再有由 animation.onComplete
引起的闪烁效果,因为它会立即发生而无需等待任何事情。
工具提示在文字上方,更自然
遵循执行此操作的插件:
Chart.pluginService.register({
afterDatasetsDraw: function(chartInstance) {
// We get the canvas context
var ctx = chartInstance.chart.ctx;
// And set the properties we need
ctx.font = Chart.helpers.fontString(14, 'bold', Chart.defaults.global.defaultFontFamily);
ctx.textAlign = 'center';
ctx.textBaseline = 'bottom';
ctx.fillStyle = '#666';
// For ervery dataset ...
chartInstance.config.data.datasets.forEach(function(dataset) {
// For every data in the dataset ...
for (var i = 0; i < dataset.data.length; i++) {
// We get all the properties & values we need
var model = dataset._meta[Object.keys(dataset._meta)[0]].data[i]._model,
total = dataset._meta[Object.keys(dataset._meta)[0]].total,
mid_radius = model.innerRadius + (model.outerRadius - model.innerRadius) / 2,
start_angle = model.startAngle,
end_angle = model.endAngle,
mid_angle = start_angle + (end_angle - start_angle) / 2;
// We calculate the right positions for our text
var x = mid_radius * 1.5 * Math.cos(mid_angle);
var y = mid_radius * 1.5 * Math.sin(mid_angle);
// We calculate the percentage
var percent = String(Math.round(dataset.data[i] / total * 100)) + "%";
// And display it
ctx.fillText(percent, model.x + x, model.y + y);
}
});
}
});
您可以检查此插件是否正常工作 on this jsFiddle,结果如下:
我有一个饼图,当鼠标悬停在一个饼图上时,它看起来像这样:
除了想要图例 <name> (<%val>): <data>
悬停时显示。
换句话说,我希望馅饼看起来像这样:
我怎样才能把那一份数据(百分比)分解出来,然后把它画到每一块饼上?
这是我目前使用的代码:
var formatter = new Intl.NumberFormat("en-US");
var data = {
labels: [
"Bananas (18%)",
"Lettuce, Romaine (14%)",
"Melons, Watermelon (10%)",
"Pineapple (10%)",
"Berries (10%)",
"Lettuce, Spring Mix (9%)",
"Broccoli (8%)",
"Melons, Honeydew (7%)",
"Grapes (7%)",
"Melons, Cantaloupe (7%)"
],
datasets: [
{
data: [2755, 2256, 1637, 1608, 1603, 1433, 1207, 1076, 1056, 1048],
backgroundColor: [
"#FFE135",
"#3B5323",
"#fc6c85",
"#ffec89",
"#021c3d",
"#3B5323",
"#046b00",
"#cef45a",
"#421C52",
"#FEA620"
]
}
]
};
var optionsPie = {
responsive: true,
scaleBeginAtZero: true,
tooltips: {
callbacks: {
label: function (tooltipItem, data) {
return data.labels[tooltipItem.index] + ": " +
formatter.format(data.datasets[tooltipItem.datasetIndex].data[tooltipItem.index]);
}
}
}
};
var ctx = $("#top10ItemsChart").get(0).getContext("2d");
var top10PieChart = new Chart(ctx,
{
type: 'pie',
data: data,
options: optionsPie
});
$("#top10Legend").html(top10PieChart.generateLegend());
我是否需要添加 afterDraw() 事件?如果需要,它需要如何实现?
更新
我尝试向图表构造函数添加 onAnimationComplete() 回调:
var top10PieChart = new Chart(ctx,
{
type: 'pie',
data: data,
options: optionsPie,
onAnimationComplete: function () {
var ctx = this.chart.ctx;
ctx.font = this.scale.font;
ctx.fillStyle = this.scale.textColor;
ctx.textAlign = "center";
ctx.textBaseline = "center";
this.datasets.forEach(function(dataset) {
dataset.points.forEach(function(points) {
ctx.fillText(points.value, points.x, points.y - 10);
});
});
}
});
...但它什么也没做。
更新 2
我还尝试将以下内容附加到选项对象:
,
tooltipTemplate: "<%= value %>",
onAnimationComplete: function () {
this.showTooltip(this.datasets[0].bars, true);
}
...结果相同(无变化)。
更新 3
好的,我希望答案
添加到选项饼图:
showTooltips: false,
onAnimationProgress: drawSegmentValues
检索元素引用后添加:
var midX = canvas.width / 2;
var midY = canvas.height / 2
构造饼图实例后添加:
var radius = top10PieChart.outerRadius;
添加函数 drawSegmentValues()
在上下文中(没有双关语意):
// Top 10 Pie Chart
var formatter = new Intl.NumberFormat("en-US");
var data = {
labels: [
"Bananas (18%)",
"Lettuce, Romaine (14%)",
"Melons, Watermelon (10%)",
"Pineapple (10%)",
"Berries (10%)",
"Lettuce, Spring Mix (9%)",
"Broccoli (8%)",
"Melons, Honeydew (7%)",
"Grapes (7%)",
"Melons, Cantaloupe (7%)"
],
datasets: [
{
data: [2755, 2256, 1637, 1608, 1603, 1433, 1207, 1076
1056, 1048],
backgroundColor: [
"#FFE135",
"#3B5323",
"#fc6c85",
"#ffec89",
"#021c3d",
"#3B5323",
"#046b00",
"#cef45a",
"#421C52",
"#FEA620"
]
}
]
};
var optionsPie = {
responsive: true,
scaleBeginAtZero: true,
legend: {
display: false
},
tooltips: {
callbacks: {
label: function (tooltipItem, data) {
return data.labels[tooltipItem.index] + ": " +
formatter.format
data.datasets[tooltipItem.datasetIndex].data[
tooltipItem.index]);
}
}
},
showTooltips: false,
onAnimationProgress: drawSegmentValues
};
var ctx = $("#top10ItemsChart").get(0).getContext("2d");
var midX = canvas.width / 2;
var midY = canvas.height / 2
var top10PieChart = new Chart(ctx,
{
type: 'pie',
data: data,
options: optionsPie//,
});
var radius = top10PieChart.outerRadius;
$("#top10Legend").html(top10PieChart.generateLegend());
// </ Top 10 Pie Chart
// Price Compliance Bar Chart
. . . this horizontal bar chart code elided for brevity, bu
unchanged from their working state
// Forecast/Impact Analysis Bar chart
. . . this horizontal bar chart code also elided for brevity, bu
unchanged from their working state
function drawSegmentValues() {
for (var i = 0; i < top10PieChart.segments.length; i++) {
ctx.fillStyle = "white";
var textSize = canvas.width / 10;
ctx.font = textSize + "px Verdana";
// Get needed variables
var value = top10PieChart.segments[i].value;
var startAngle = top10PieChart.segments[i].startAngle;
var endAngle = top10PieChart.segments[i].endAngle;
var middleAngle = startAngle + ((endAngle - startAngle)
2);
// Compute text location
var posX = (radius / 2) * Math.cos(middleAngle) + midX;
var posY = (radius / 2) * Math.sin(middleAngle) + midY;
// Text offside by middle
var w_offset = ctx.measureText(value).width / 2;
var h_offset = textSize / 4;
ctx.fillText(value, posX - w_offset, posY + h_offset);
}
}
...但它完全淹没了所有三个图表,什么也没留下 visible/drawing。
更新 4
顺便说一句,我尝试了给定的答案,但没有 result/change - 它仍然按照设计在悬停时显示数据,但除此之外别无其他。
另外,相关的fiddle同时显示数值和百分比;由于房地产限制,我希望百分比仅为 "always on" - "whole enchilada" 仅在悬停时可见。
好的,看来您更新后的代码中存在一些简单的语法错误,这些错误可能是在复制过程中发生的。忽略那些,您可以通过在动画选项中创建一个函数来管理它。这里是 fiddle. Thanks to Hung Tran's work
animation: {
duration: 0,
easing: "easeOutQuart",
onComplete: function () {
var ctx = this.chart.ctx;
ctx.font = Chart.helpers.fontString(Chart.defaults.global.defaultFontFamily, 'normal', Chart.defaults.global.defaultFontFamily);
ctx.textAlign = 'center';
ctx.textBaseline = 'bottom';
this.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,
total = dataset._meta[Object.keys(dataset._meta)[0]].total,
mid_radius = model.innerRadius + (model.outerRadius - model.innerRadius)/2,
start_angle = model.startAngle,
end_angle = model.endAngle,
mid_angle = start_angle + (end_angle - start_angle)/2;
var x = mid_radius * Math.cos(mid_angle);
var y = mid_radius * Math.sin(mid_angle);
ctx.fillStyle = '#fff';
if (i == 3){ // Darker text color for lighter background
ctx.fillStyle = '#444';
}
var percent = String(Math.round(dataset.data[i]/total*100)) + "%";
// this prints the data number
ctx.fillText(dataset.data[i], model.x + x, model.y + y);
// this prints the percentage
ctx.fillText(percent, model.x + x, model.y + y + 15);
}
});
}
}
以下解决方案使用与
不再有由
animation.onComplete
引起的闪烁效果,因为它会立即发生而无需等待任何事情。工具提示在文字上方,更自然
遵循执行此操作的插件:
Chart.pluginService.register({
afterDatasetsDraw: function(chartInstance) {
// We get the canvas context
var ctx = chartInstance.chart.ctx;
// And set the properties we need
ctx.font = Chart.helpers.fontString(14, 'bold', Chart.defaults.global.defaultFontFamily);
ctx.textAlign = 'center';
ctx.textBaseline = 'bottom';
ctx.fillStyle = '#666';
// For ervery dataset ...
chartInstance.config.data.datasets.forEach(function(dataset) {
// For every data in the dataset ...
for (var i = 0; i < dataset.data.length; i++) {
// We get all the properties & values we need
var model = dataset._meta[Object.keys(dataset._meta)[0]].data[i]._model,
total = dataset._meta[Object.keys(dataset._meta)[0]].total,
mid_radius = model.innerRadius + (model.outerRadius - model.innerRadius) / 2,
start_angle = model.startAngle,
end_angle = model.endAngle,
mid_angle = start_angle + (end_angle - start_angle) / 2;
// We calculate the right positions for our text
var x = mid_radius * 1.5 * Math.cos(mid_angle);
var y = mid_radius * 1.5 * Math.sin(mid_angle);
// We calculate the percentage
var percent = String(Math.round(dataset.data[i] / total * 100)) + "%";
// And display it
ctx.fillText(percent, model.x + x, model.y + y);
}
});
}
});
您可以检查此插件是否正常工作 on this jsFiddle,结果如下: