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

Chart.js单条堆叠条形图分段间距与底部括号标签实现方法

Chart.js 单条横向堆叠条形图调整方案

堆叠分段间隙实现

Chart.js 原生无堆叠分段间隙的直接配置项,采用透明占位数据集的方式实现最简便:

  • 预先定义间隙占总长度的比例(建议设为0.005~0.01,可按需调整)
  • 在每两个彩色业务分段之间插入1个背景透明的占位数据集,数值为定义好的间隙宽度
  • 调整各段圆角规则:仅最左侧分段保留左侧圆角、最右侧分段保留右侧圆角,中间业务分段去掉两端圆角,透明占位段不设置圆角,避免间隙处露出多余边角

下方括号分类标注实现

通过Chart.js自定义插件能力,在图表渲染完成后用Canvas原生API绘制括号和标签,无需引入额外依赖:

  • 在插件的afterDraw生命周期钩子中,先计算每个分类区间(低区间、目标区间、高区间)对应的Canvas像素起止坐标
  • 调用Canvas路径API绘制对应样式的括号轮廓
  • 在每个括号的居中下方位置绘制分类文字,设置匹配的字号、颜色即可

调整后完整代码

JS部分

// 各段长度定义
var len_one = 0.02;
var len_two = 0.05;
var len_three = 0.72;
var len_four = 0.16;
var len_five = 0.05;
// 分段间隙宽度
const gap = 0.008;

// 自定义括号标注插件
const bracketLabelPlugin = {
  id: 'bracketLabel',
  afterDraw: (chart) => {
    const ctx = chart.ctx;
    const xAxis = chart.scales.x;
    const yPos = chart.chartArea.bottom + 15;
    const bracketHeight = 8;
    ctx.save();
    ctx.strokeStyle = '#666';
    ctx.fillStyle = '#666';
    ctx.font = '14px sans-serif';
    ctx.textAlign = 'center';
    ctx.lineWidth = 2;

    // 分组配置:[起始占比, 结束占比, 标签文本]
    const groups = [
      [0, len_one + gap + len_two, 'Low'],
      [len_one + gap + len_two + gap, len_one + gap + len_two + gap + len_three, 'Target'],
      [len_one + gap + len_two + gap + len_three + gap, 1, 'High']
    ];

    groups.forEach(([start, end, label]) => {
      const xStart = xAxis.getPixelForValue(start);
      const xEnd = xAxis.getPixelForValue(end);
      // 绘制方括号
      ctx.beginPath();
      // 左竖线
      ctx.moveTo(xStart, yPos);
      ctx.lineTo(xStart, yPos + bracketHeight);
      // 中间横线
      ctx.moveTo(xStart, yPos);
      ctx.lineTo(xEnd, yPos);
      // 右竖线
      ctx.moveTo(xEnd, yPos);
      ctx.lineTo(xEnd, yPos + bracketHeight);
      ctx.stroke();
      // 绘制标签
      ctx.fillText(label, (xStart + xEnd)/2, yPos + bracketHeight + 18);
    })
    ctx.restore();
  }
}

const ctx = document.getElementById("myChart").getContext('2d');
const myChart = new Chart(ctx, {
    type: 'bar',
    data: {
        labels: [0],
        datasets: [
          {
            label: 'red-low',
            data: [len_one],
            backgroundColor: '#ab3025',
            barThickness: 50,
            borderSkipped: false,
            borderRadius: {
                bottomLeft: 3,
                topLeft: 3,
                topRight: 0,
                bottomRight: 0
            },
          },
          // 间隙占位
          {
            data: [gap],
            backgroundColor: 'transparent',
            barThickness: 50,
          },
          {
            label: 'rose-low', 
            data: [len_two],
            backgroundColor: '#d59792',
            barThickness: 50,
            borderSkipped: false,
            borderRadius: 0,
          },
          // 间隙占位
          {
            data: [gap],
            backgroundColor: 'transparent',
            barThickness: 50,
          },
          {
            label: 'Target',
            data: [len_three],
            backgroundColor: '#44c973',
            barThickness: 50,
            borderSkipped: false,
            borderRadius: 0,
          },
          // 间隙占位
          {
            data: [gap],
            backgroundColor: 'transparent',
            barThickness: 50,
          },
          {
            label: 'high-yellow',
            data: [len_four],
            backgroundColor: '#f9c63d',
            barThickness: 50, 
            borderSkipped: false,
            borderRadius: 0,
          },
          // 间隙占位
          {
            data: [gap],
            backgroundColor: 'transparent',
            barThickness: 50,
          },
          {
            label: 'high-orange',
            data: [len_five],
            backgroundColor: '#f5a000',
            barThickness: 50,
            borderSkipped: false,
            borderRadius: {
                bottomLeft: 0,
                topLeft: 0,
                topRight: 3,
                bottomRight: 3
            },
          }
        ]
    },
    options: {
        indexAxis: 'y',
        events: [], 
        maintainAspectRatio: false,
        layout: {
            padding: {
              top: 20,
              bottom: 60,
              left:20,
              right:20
            },
        },
        plugins: {
            legend: {
                display: false,
            }
        },
        scales: {
            x: {
                stacked: true,
                display: false,
                max: 1
            },
            y: {
                beginAtZero: true,
                stacked: true,
                display: false
            },
        },
    },
    plugins: [bracketLabelPlugin]
});

HTML部分

<!DOCTYPE html>
<html lang="en">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.2.0-beta1/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-0evHe/X+R7YkIZDRvuzKMRqM+OrBnVFBL6DOitfPri4tjfHxaWutUpFmBp4vmVor" crossorigin="anonymous">
        <style>
          #myChart {
            height: 180px;
          }
        </style>
    </head>

    <body>
        <!-- 时间范围展示图表 -->
        <div class="col-6 offset-3 my-5" id="time_in_range">
            <canvas id="myChart"></canvas>
        </div>
        
        <script src="https://cdn.jsdelivr.net/npm/chart.js@4.4.8/dist/chart.umd.min.js"></script>
        <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.2.0-beta1/dist/js/bootstrap.bundle.min.js" integrity="sha384-pprn3073KE6tl6bjs2QrFaJGz5/SUsLqktiwsUTF55Jfv3qYSDhgCecCxMW52nD2" crossorigin="anonymous"></script>
     
    </body>

</html>

调整说明

  • 可修改gap变量值调整分段间隙宽度,值越大间隙越宽
  • 可修改自定义插件中的bracketHeight、font、颜色值调整括号和标签的样式
  • 如果需要花括号样式,只需要修改插件中绘制路径的逻辑即可

内容的提问来源于stack exchange,提问作者Alex

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.26 23:57:26