在具有额外维度的ChartJS上的多行上对y轴标签进行分组 [英] Grouping y-axis labels on multiple lines on a ChartJS with an extra dimension
问题描述
我已经在SO(Using ChartJS to create a multiple grouped bar chart - see picture below)上成功地成功地使y轴上的第二行标签正确且动态地工作。
我想知道有没有办法让他们走上多条线?查看下图中的标签:
由于它是动态构建的图表,并且行为非常灵敏,所以我的数据有时会超过允许的空间。
因此,低于2.50有时可能太长,以至于溢出到下一个(总计)框中。例如,如果低于2.50的值类似于敏捷的棕色狐狸多次跳过懒狗,并与总数列相遇,则它们将重叠。
我需要找到解决此问题的方法。
推荐答案
TL;DR
看这个答案的最下面(有两种方法,选择第二种):最简单的方法是在数据端使用字符串数组(string [])
用于单个类别,并在打印端使用单独的ctx.fillText()
调用打印每个数组项。
代码部分1:
数据-lang="js"数据-隐藏="假"数据-控制台="真"数据-巴贝尔="假">CanvasRenderingContext2D.prototype.wrapText = function(text, x, y, maxWidth, lineHeight) {
var lines = text.split("
");
var linesCount = 0;
for (var i = 0; i < lines.length; i++) {
var words = lines[i].split(' ');
var line = '';
for (var n = 0; n < words.length; n++) {
var testLine = line + words[n] + ' ';
var metrics = this.measureText(testLine);
var testWidth = metrics.width;
if (testWidth > maxWidth && n > 0) {
this.fillText(line, x, y);
linesCount += 1;
line = words[n] + ' ';
y += lineHeight;
} else {
line = testLine;
}
}
this.fillText(line, x, y);
linesCount += 1;
y += lineHeight;
}
return linesCount;
}
new Chart('myChart', {
type: 'bar',
plugins: [{
afterDraw: chart => {
let ctx = chart.chart.ctx;
ctx.save();
let xAxis = chart.scales['x-axis-0'];
let xCenter = (xAxis.left + xAxis.right) / 2;
let yBottom = chart.scales['y-axis-0'].bottom;
ctx.textAlign = 'center';
ctx.font = '12px Arial';
var size1 = ctx.wrapText(chart.data.categories[0], (xAxis.left + xCenter) / 2, yBottom + 40, 160, 16);
var size2 = ctx.wrapText(chart.data.categories[1], (xCenter + xAxis.right) / 2, yBottom + 40, 160, 16);
var size = size1 > size2 ? size1 : size2;
chart.options.legend.labels.padding = size * 16 + 5;
ctx.strokeStyle = 'lightgray';
[xAxis.left, xCenter, xAxis.right].forEach(x => {
ctx.beginPath();
ctx.moveTo(x, yBottom);
ctx.lineTo(x, yBottom + 40);
ctx.stroke();
});
ctx.restore();
}
}],
data: {
labels: ['2004', '2008', '2012', '2016', '2004', '2008', '2012', '2016'],
categories: ['The quick brown fox jumps over the lazy dog', 'Lower than 2.50'],
datasets: [{
label: 'Male',
data: [42.4, 43.0, 43.0, 50.3, 49.4, 48.4, 51.2, 51.8],
backgroundColor: 'rgba(124, 181, 236, 0.9)',
borderColor: 'rgb(124, 181, 236)',
borderWidth: 1
},
{
label: 'Female',
data: [57.6, 57.0, 57.0, 49.7, 50.6, 51.6, 53.7, 54.6],
backgroundColor: 'rgba(67, 67, 72, 0.9)',
borderColor: 'rgb(67, 67, 72)',
borderWidth: 1
}
]
},
options: {
legend: {
position: 'bottom',
labels: {
padding: 30,
usePointStyle: true
}
},
scales: {
yAxes: [{
ticks: {
min: 0,
max: 80,
stepSize: 20
},
scaleLabel: {
display: true,
labelString: 'Percent (%)'
}
}],
xAxes: [{
gridLines: {
drawOnChartArea: false
}
}]
}
}
});
canvas {
max-width: 400px;
}
<!DOCTYPE html>
<html>
<body>
<script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/2.9.4/Chart.min.js"></script>
<canvas id="myChart" height="200"></canvas>
</body>
</html>
解释#1:
我解决的第一件事是解决为什么图表标题在较低的x轴上没有写成两行。在链接的溶液中,使用fillText
。它是一种将canvas
元素包围起来的方法,但它有一个限制,即您不能在没有工作的情况下将其放在几行上。然而,它可以被删改以适应空间[1],但很快文本就看起来很糟糕。所以,StackOveflow,找到一个解决方法[2]。有几种解决方案,都不容易,但我发现wrapText
方法最方便。它可以让文本尽可能长,而且线条都很整齐。
第二个问题是,我必须向下调整图例,以避免同一空间中的多个内容重叠。因此,我向wrapText
函数添加了行的计算,并使用它生成了正确的空间量。
ctx.font = '8px Arial';
var size1 = ctx.wrapText(chart.data.categories[0], (xAxis.left + xCenter) / 2, yBottom + 40, 160, 8);
var size2 = ctx.wrapText(chart.data.categories[1], (xCenter + xAxis.right) / 2, yBottom + 40, 160, 8);
var size = size1 > size2 ? size1 : size2;
chart.options.legend.labels.padding = size * 8 + 8;
我刚刚将字体大小播放到8像素。它显示所有数据(也显示您的文本),但看起来不是很漂亮。
第1部分的最后说明:
此解决方案是一种变通办法(插件解决方案)。文本最长的最终解决方案看起来如此尴尬,以至于我相信有一个更好的解决方案,但由于我可以做到这一点,我决定分享我的试验,以防没有其他解决方案,所以你至少有这个解决方案。
编辑:更多‘Chart.js’方式,更方便:
代码部分,#2
数据-lang="js"数据-隐藏="假"数据-控制台="真"数据-巴贝尔="假">new Chart('myChart', {
type: 'bar',
plugins: [{
beforeDraw: chart => {
let ctx = chart.chart.ctx;
ctx.save();
let xAxis = chart.scales['x-axis-0'];
let xCenter = (xAxis.left + xAxis.right) / 2;
let yBottom = chart.scales['y-axis-0'].bottom;
ctx.textAlign = 'center';
ctx.font = '12px Arial';
ctx.fillText(chart.data.categories[0][0], (xAxis.left + xCenter) / 2, yBottom + 30);
ctx.fillText(chart.data.categories[0][1], (xAxis.left + xCenter) / 2, yBottom + 40);
ctx.fillText(chart.data.categories[0][2], (xAxis.left + xCenter) / 2, yBottom + 50);
ctx.fillText(chart.data.categories[1], (xCenter + xAxis.right) / 2, yBottom + 40);
ctx.strokeStyle = 'lightgray';
[xAxis.left, xCenter, xAxis.right].forEach(x => {
ctx.beginPath();
ctx.moveTo(x, yBottom);
ctx.lineTo(x, yBottom + 40);
ctx.stroke();
});
ctx.restore();
}
}],
data: {
labels: ['2004', '2008', '2012', '2016', '2004', '2008', '2012', '2016'],
categories: [['The quick brown fox jumps over', 'the lazy dog a bunch of times','and ran into the Total column'], ['Lower than 2.50']],
datasets: [{
label: 'Male',
data: [42.4, 43.0, 43.0, 50.3, 49.4, 48.4, 51.2, 51.8],
backgroundColor: 'rgba(124, 181, 236, 0.9)',
borderColor: 'rgb(124, 181, 236)',
borderWidth: 1
},
{
label: 'Female',
data: [57.6, 57.0, 57.0, 49.7, 50.6, 51.6, 53.7, 54.6],
backgroundColor: 'rgba(67, 67, 72, 0.9)',
borderColor: 'rgb(67, 67, 72)',
borderWidth: 1
}
]
},
options: {
legend: {
position: 'bottom',
labels: {
padding: 30,
align: 'end',
usePointStyle: true
}
},
scales: {
yAxes: [{
ticks: {
min: 0,
max: 80,
stepSize: 20
},
scaleLabel: {
display: true,
labelString: 'Percent (%)'
}
}],
xAxes: [{
gridLines: {
drawOnChartArea: false
}
}]
}
}
});
canvas {
max-width: 400px;
}
<!DOCTYPE html>
<html>
<head>
<script src="/scripts/snippet-javascript-console.min.js?v=1"></script>
</head>
<body>
<script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/2.9.4/Chart.min.js"></script>
<canvas id="myChart" height="200"></canvas>
</body>
说明,第2部分
因此,所有标签、标题等都有两个选项(例如,在[3]上):
字符串
字符串[]
在您的示例中,这次是自己打印的,所以只需引用字符串数组并定义引用内容的写入位置!
我采用了一种方法,而不是使用较小的字体,将页面上的内容略微放大一点,示例文本正好适合!!
来源:
[1]https://www.w3schools.com/tags/playcanvas.asp?filename=playcanvas_filltextmaxwidth
[2]HTML5 canvas ctx.fillText won't do line breaks?
[3]https://www.chartjs.org/docs/latest/axes/labelling.html
这篇关于在具有额外维度的ChartJS上的多行上对y轴标签进行分组的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!