Chart.js v3 保持 2 个图表并排对齐

问题描述 投票:0回答:1

如何并排对齐两个图表,类似于 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
1个回答
0
投票

更新说明:虽然此解决方案是针对 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>

© www.soinside.com 2019 - 2024. All rights reserved.