如何并排对齐两个图表,类似于 Stack Overflow 帖子Chartjs - 使用 Chart.js v3 保持 2 个图表并排对齐中所描述的内容? 这是我的代码。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<script src="https://code.jquery.com/jquery-3.7.1.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/3.4.1/chart.js"></script>
<style>
#chart_a, #chart_b {
width: 320px;
height: 350px;
}
.label {
text-align: center;
width: 600px;
font-size: 20px;
font-weight: bold;
margin: 20px;
}
.chart_container {
float: left;
}
</style>
</head>
<body>
<div id="aligned">
<div class="label">Aligned</div>
<div class="chart_container">
<canvas id="chart_a"></canvas>
</div>
<div class="chart_container">
<canvas id="chart_b"></canvas>
</div>
</div>
<script>
var data, ctx;
data = {
labels: ["Dmitri.Ivanovich.Mendeleev", "Yuri.Alekseyevich.Gagarin", "Alexey.Arkhipovich.Leonov"],
datasets: [{
label: "My First dataset",
fillColor: "rgba(220,220,220,0.5)",
strokeColor: "rgba(220,220,220,0.8)",
highlightFill: "rgba(220,220,220,0.75)",
highlightStroke: "rgba(220,220,220,1)",
data: [50, 88, 15]
}]
};
console.log(document.getElementById("chart_b"))
ctx = document.getElementById("chart_b").getContext("2d");
var mychart2 = new Chart(ctx, {
type: 'bar',
data: data,
options: {
animation: false
}
});
/*
// This code is valid only for v1
Chart.types.Bar.extend({
name: "BarAlt",
draw: function () {
this.scale.endPoint = this.options.endPoint;
Chart.types.Bar.prototype.draw.apply(this, arguments);
}
});
*/
data = {
labels: ["Iantojones", "Jackharkness", "Owenharper"],
datasets: [{
label: "My First dataset",
fillColor: "rgba(220,220,220,0.5)",
strokeColor: "rgba(220,220,220,0.8)",
highlightFill: "rgba(220,220,220,0.75)",
highlightStroke: "rgba(220,220,220,1)",
data: [50, 88, 15]
}]
};
ctx = document.getElementById("chart_a").getContext("2d");
var mychart1 = new Chart(ctx, {
type: 'bar', // type: 'barAlt'
data: data,
options: {
animation: false,
// endPoint: mychart2.scale.endPoint,
},
});
</script>
</body>
</html>
虽然我意识到使用
chart.helpers.extend
实现与 v2 中相同的功能是可能的,但我不确定 v3 中的替代方法,因为助手已被删除。
在ES2015类中使用它似乎很好,如https://www.chartjs.org/docs/3.4.1/developers/charts.html所示,但我不知道如何导入BarController。
更新说明:虽然此解决方案是针对 Chart.js 版本 3.4.1 实现的。正如所要求的,它在当前版本 4.4.1 中无需更改即可工作。
除了一些技术上的复杂性(比如现在的 Chart.js 的布局) 相当复杂,不能通过设置来改变 y 轴的高度 一个变量),存在标签旋转的问题:默认方式标签 计算旋转允许大标签延伸到图表区域的左侧 将其压缩到右侧(问题中描述为不需要)。 我通过一个使用三角函数的插件解决了这个问题 计算标签的新旋转,以便图表区域可以有其 全部可能的宽度。该插件使用
afterLayout
钩子,因为这是计算轴大小的最早阶段。
即使画布大小没有调整,设置
maintainAspectRatio: false
也是必须的
动态地,因为它允许图表区域扩展到最大可用范围
空间,无论画布大小如何。
问题的主要部分,也在
afterLayout
钩子中实现
很简单:resize
第一个图表,使得 bottom
的 chartArea
值与
第二张图表的 bottom
的 chartArea
。
const data1 = {
labels: ["Iantojones", "Jackharkness", "Owenharper"],
datasets: [{
label: "My First dataset",
fillColor: "rgba(220,220,220,0.5)",
strokeColor: "rgba(220,220,220,0.8)",
highlightFill: "rgba(220,220,220,0.75)",
highlightStroke: "rgba(220,220,220,1)",
data: [50, 88, 15]
}]
};
const data2 = {
labels: ["Dmitri.Ivanovich.Mendeleev", "Yuri.Alekseyevich.Gagarin", "Alexey.Arkhipovich.Leonov"],
datasets: [{
label: "My First dataset",
fillColor: "rgba(220,220,220,0.5)",
strokeColor: "rgba(220,220,220,0.8)",
highlightFill: "rgba(220,220,220,0.75)",
highlightStroke: "rgba(220,220,220,1)",
data: [50, 88, 15]
}]
};
const mychart2 = new Chart("chart_b", {
type: 'bar',
data: data2,
options: {
animation: false,
maintainAspectRatio: false,
layout:{
autoPadding: false,
//padding: {left: 20, bottom: 30} // test with padding
}
},
plugins: [
{
afterLayout(chart){
//label rotation
if(!chart.__rotationFixed){ // run only once, avoid infinite recursion
chart.__rotationFixed = true;
const xScale = chart.scales.x,
yScale = chart.scales.y;
const extraSpaceToLeft = yScale.left - yScale.paddingLeft;
if(extraSpaceToLeft > 5){
const currentLeft = chart.chartArea.left,
minTheoreticalLeft = yScale.width,
currentRotation = xScale.labelRotation * Math.PI / 180,
label0XG = xScale.getPixelForValue(xScale.ticks[0].value),
label0Length = label0XG / Math.cos(currentRotation),
cosMin = (label0XG - currentLeft + minTheoreticalLeft) / label0Length,
minRotation = cosMin < 0 ? 90 : Math.acos(cosMin) * 180 / Math.PI;
const gainToLeft = label0XG - label0Length * cosMin;
if(cosMin > 0 && Math.abs(gainToLeft - extraSpaceToLeft) > 10){
console.warn(`Assumption failed; rotation possibly miscalculated! `+
`extraSpaceToLeft = ${extraSpaceToLeft} gainToLeft = ${gainToLeft}`)
}
xScale.options.ticks.minRotation = minRotation;
xScale.options.ticks.maxRotation = minRotation;
chart.options.scales.x.ticks.minRotation = minRotation;
chart.options.scales.x.ticks.maxRotation = minRotation;
chart._updateLayout(chart._minPadding); // hacky, but cleaner
//chart.update('none'); // documented alternative to the above
}
}
}
}
]
});
/*const mychart1 = */new Chart("chart_a", {
type: 'bar',
data: data1,
options: {
maintainAspectRatio: false,
animation: false,
},
plugins:[
{
afterLayout(chart){
// resize the first chart
if(!chart.__resized){ // run only once, avoid infinite recursion
chart.__resized = true;
const deltaY = mychart2.chartArea.bottom - chart.chartArea.bottom;
chart.resize(chart.canvas.offsetWidth, chart.canvas.offsetHeight + deltaY);
}
}
}
]
});
#chart_a, #chart_b{
width: 320px;
height: 350px;
background-color: rgba(192, 192, 192, 0.7); /* to see what's happening */
}
.label {
text-align: center;
width: 600px;
font-size: 20px;
font-weight: bold;
margin: 20px;
}
#aligned{
min-width: 650px
}
.chart_container {
float: left;
}
<div id="aligned">
<div class="label">Aligned</div>
<div class="chart_container" id="container_a">
<canvas id="chart_a"></canvas>
</div>
<div class="chart_container" id="container_b">
<canvas id="chart_b"></canvas>
</div>
</div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/3.4.1/chart.js"></script>
<!--script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/4.4.1/chart.umd.js"></script-->
此解决方案具有优势(与旧版本的链接方案相比) Chart.js)画布中没有空白空间,因此它们可以垂直移动 在狭窄的介质中。
此外,它可以立即调整以调整(放大)第二个图表的大小,以便 很适合第一个。
const data1 = {
labels: ["Iantojones", "Jackharkness", "Owenharper"],
datasets: [{
label: "My First dataset",
fillColor: "rgba(220,220,220,0.5)",
strokeColor: "rgba(220,220,220,0.8)",
highlightFill: "rgba(220,220,220,0.75)",
highlightStroke: "rgba(220,220,220,1)",
data: [50, 88, 15]
}]
};
const data2 = {
labels: ["Dmitri.Ivanovich.Mendeleev", "Yuri.Alekseyevich.Gagarin", "Alexey.Arkhipovich.Leonov"],
datasets: [{
label: "My First dataset",
fillColor: "rgba(220,220,220,0.5)",
strokeColor: "rgba(220,220,220,0.8)",
highlightFill: "rgba(220,220,220,0.75)",
highlightStroke: "rgba(220,220,220,1)",
data: [50, 88, 15]
}]
};
const mychart1 = new Chart("chart_a", {
type: 'bar',
data: data1,
options: {
maintainAspectRatio: false,
animation: false,
}
});
/*const mychart2 = */new Chart("chart_b", {
type: 'bar',
data: data2,
options: {
animation: false,
maintainAspectRatio: false,
layout:{
autoPadding: false,
//padding: {left: 20, bottom: 30} // test with padding
}
},
plugins: [
{
afterLayout(chart){
//label rotation
if(!chart.__rotationFixed){ // run only once, avoid infinite recursion
chart.__rotationFixed = true;
const xScale = chart.scales.x,
yScale = chart.scales.y;
const extraSpaceToLeft = yScale.left - yScale.paddingLeft;
if(extraSpaceToLeft > 5){
const currentLeft = chart.chartArea.left,
minTheoreticalLeft = yScale.width,
currentRotation = xScale.labelRotation * Math.PI / 180,
label0XG = xScale.getPixelForValue(xScale.ticks[0].value),
label0Length = label0XG / Math.cos(currentRotation),
cosMin = (label0XG - currentLeft + minTheoreticalLeft) / label0Length,
minRotation = cosMin < 0 ? 90 : Math.acos(cosMin) * 180 / Math.PI;
const gainToLeft = label0XG - label0Length * cosMin;
if(cosMin > 0 && Math.abs(gainToLeft - extraSpaceToLeft) > 10){
console.warn(`Assumption failed; rotation possibly miscalculated! `+
`extraSpaceToLeft = ${extraSpaceToLeft} gainToLeft = ${gainToLeft}`)
}
xScale.options.ticks.minRotation = minRotation;
xScale.options.ticks.maxRotation = minRotation;
chart.options.scales.x.ticks.minRotation = minRotation;
chart.options.scales.x.ticks.maxRotation = minRotation;
chart._updateLayout(chart._minPadding); // hacky, but cleaner
//chart.update('none'); // documented alternative to the above
}
}
// resize the second chart
if(!chart.__resized && chart.__rotationFixed){ // run only once, avoid infinite recursion
chart.__resized = true;
const deltaY = mychart1.chartArea.bottom - chart.chartArea.bottom;
chart.resize(chart.canvas.offsetWidth, chart.canvas.offsetHeight + deltaY);
}
}
}
]
});
#chart_a, #chart_b{
width: 320px;
height: 350px;
background-color: rgba(192, 192, 192, 0.7); /* to see what's happening */
}
.label {
text-align: center;
width: 600px;
font-size: 20px;
font-weight: bold;
margin: 20px;
}
#aligned{
min-width: 650px
}
.chart_container {
float: left;
}
<div id="aligned">
<div class="label">Aligned</div>
<div class="chart_container" id="container_a">
<canvas id="chart_a"></canvas>
</div>
<div class="chart_container" id="container_b">
<canvas id="chart_b"></canvas>
</div>
</div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/3.4.1/chart.js"></script>