You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在Chart.js中创建带有空心最大值与实心当前值的双组条形图?

如何在Chart.js中创建带有空心最大值与实心当前值的双组条形图?

我完全理解你的困扰——你现在的代码把每个颜色的「当前值」和「最大值」都做成了独立的数据集,Chart.js会默认把它们当成四个不同的条形来排列,所以才会出现每组四个条的问题。我们的核心目标是让同一颜色的实心当前条和空心最大值条完全重叠,这样每组就只会显示两个条(绿、红各一个),每个条自带对应的最大值空心边框。

下面我给你两种经过验证的可行方案,你可以根据自己的需求选择:


方案一:调整数据集配置,实现同颜色条重叠

这种方案基于你现有的代码修改,核心是让同颜色的当前值和最大值数据集绑定到同一X轴,实现条形重叠:

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Chart.js Grouped Bar Chart</title>
  <script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
  <style>
    canvas {
        max-width: 50%;
        height: 400px;
    }
  </style>
</head>
<body>
  <main>
    <canvas id="barChart"></canvas>
  </main>

  <script>
    const data = {
      labels: ['2020', '2021', '2022', '2023', '2024'],
      datasets: [
        // 绿色当前值(实心)
        {
          label: '绿色当前值',
          data: [50, 150, 20, 40, 150],
          backgroundColor: 'rgba(0, 255, 0, 0.5)',
          borderColor: 'rgba(0, 255, 0, 1)',
          borderWidth: 0,
          barThickness: 20,
          xAxisID: 'x',
          barPercentage: 1,
          categoryPercentage: 0.5,
        },
        // 绿色最大值(空心,与当前值重叠)
        {
          label: '绿色最大值',
          data: [100, 200, 30, 50, 200],
          backgroundColor: 'transparent',
          borderColor: 'rgba(0, 255, 0, 1)',
          borderWidth: 2,
          barThickness: 20,
          xAxisID: 'x',
          barPercentage: 1,
          categoryPercentage: 0.5,
          hidden: true // 隐藏图例中的最大值项,避免混乱
        },
        // 红色当前值(实心)
        {
          label: '红色当前值',
          data: [20, 100, 160, 25, 85],
          backgroundColor: 'rgba(255, 0, 0, 0.5)',
          borderColor: 'rgba(255, 0, 0, 1)',
          borderWidth: 0,
          barThickness: 20,
          xAxisID: 'x',
          barPercentage: 1,
          categoryPercentage: 0.5,
        },
        // 红色最大值(空心,与当前值重叠)
        {
          label: '红色最大值',
          data: [50, 150, 180, 30, 100],
          backgroundColor: 'transparent',
          borderColor: 'rgba(255, 0, 0, 1)',
          borderWidth: 2,
          barThickness: 20,
          xAxisID: 'x',
          barPercentage: 1,
          categoryPercentage: 0.5,
          hidden: true
        },
      ],
    };

    const options = {
      responsive: true,
      maintainAspectRatio: false,
      plugins: {
        legend: {
          display: true,
        },
      },
      scales: {
        x: {
          stacked: false,
          grid: {
            display: false,
          },
        },
        y: {
          beginAtZero: true,
          grid: {
            drawBorder: false,
          },
        },
      },
    };

    const ctx = document.getElementById('barChart').getContext('2d');
    new Chart(ctx, {
      type: 'bar',
      data: data,
      options: options,
    });
  </script>
</body>
</html>

关键修改点:

  1. 给同颜色的当前/最大值数据集设置相同的xAxisID,并调整barPercentage和categoryPercentage参数,确保条形完全重叠;
  2. 隐藏最大值数据集的图例项,避免界面混乱;
  3. 最大值数据集使用透明背景色,仅保留边框实现空心效果。

方案二:自定义插件绘制最大值边框(更简洁易维护)

如果不想维护过多数据集,可以把最大值数据存在当前值数据集的自定义属性中,通过Chart.js插件自动绘制空心边框:

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Chart.js Grouped Bar Chart</title>
  <script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
  <style>
    canvas {
        max-width: 50%;
        height: 400px;
    }
  </style>
</head>
<body>
  <main>
    <canvas id="barChart"></canvas>
  </main>

  <script>
    // 自定义插件:自动绘制最大值空心边框
    const maxValueOverlayPlugin = {
      id: 'maxValueOverlay',
      afterDatasetsDraw(chart) {
        const { ctx, data, scales } = chart;
        const xScale = scales.x;
        const yScale = scales.y;

        // 遍历每个数据集
        data.datasets.forEach((dataset) => {
          if (!dataset.maxData) return; // 无最大值数据则跳过

          // 绘制每个数据点的最大值边框
          dataset.data.forEach((_, index) => {
            const maxValue = dataset.maxData[index];
            if (!maxValue) return;

            // 计算边框的位置与尺寸
            const barWidth = (xScale.getPixelForValue(index) - xScale.getPixelForValue(index - 1)) * dataset.barPercentage;
            const x = xScale.getPixelForValue(index) - barWidth / 2;
            const y = yScale.getPixelForValue(maxValue);
            const height = yScale.getPixelForValue(0) - y;

            // 绘制空心矩形
            ctx.save();
            ctx.strokeStyle = dataset.borderColor;
            ctx.lineWidth = 2;
            ctx.strokeRect(x, y, barWidth, height);
            ctx.restore();
          });
        });
      }
    };

    const data = {
      labels: ['2020', '2021', '2022', '2023', '2024'],
      datasets: [
        {
          label: '绿色',
          data: [50, 150, 20, 40, 150], // 当前值
          maxData: [100, 200, 30, 50, 200], // 自定义属性:最大值
          backgroundColor: 'rgba(0, 255, 0, 0.5)',
          borderColor: 'rgba(0, 255, 0, 1)',
          borderWidth: 0,
          barThickness: 20,
        },
        {
          label: '红色',
          data: [20, 100, 160, 25, 85], // 当前值
          maxData: [50, 150, 180, 30, 100], // 自定义属性:最大值
          backgroundColor: 'rgba(255, 0, 0, 0.5)',
          borderColor: 'rgba(255, 0, 0, 1)',
          borderWidth: 0,
          barThickness: 20,
        },
      ],
    };

    const options = {
      responsive: true,
      maintainAspectRatio: false,
      plugins: {
        legend: {
          display: true,
        },
      },
      scales: {
        x: {
          stacked: false,
          grid: {
            display: false,
          },
        },
        y: {
          beginAtZero: true,
          grid: {
            drawBorder: false,
          },
        },
      },
    };

    const ctx = document.getElementById('barChart').getContext('2d');
    new Chart(ctx, {
      type: 'bar',
      data: data,
      options: options,
      plugins: [maxValueOverlayPlugin] // 注册自定义插件
    });
  </script>
</body>
</html>

这个方案的优势:

  1. 仅需维护2个数据集,最大值数据存在自定义属性maxData中,结构更清晰;
  2. 插件自动处理最大值边框的绘制,后续修改数据更便捷;
  3. 图例仅显示绿、红两个选项,完全符合需求。

两种方案都能完美实现你想要的效果,你可以根据自己的开发习惯选择使用。

备注:内容来源于stack exchange,提问作者Vamshi Krishna

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.04.14 17:18:08