Chart.js 混合图表制作:组合折线柱状图与多 Y 轴配置

作者:闭包旅人

时间:2026-11-03

浏览:433

深入讲解如何使用 Chart.js 制作折线与柱状组合混合图表,详解数据集类型重写、多 Y 轴定义与映射、网格线解耦及图层渲染层级控制,提供完整可复用的代码示例与避坑指南。

文章目录

在前端业务仪表盘中,我们经常需要在单张图表内同时对比绝对数值与相对比率,例如将“每月销售额”(绝对金额)与“转化率”(百分比)放在一起展示。如果直接将两者画在同一个单一 Y 轴坐标系中,数十万的销售额数值会将处于 0 到 1 之间的转化率百分比极度压平,导致折线贴在 X 轴底部失去所有波动细节。解决这一问题的标准方案是构建折线与柱状组合的混合图表(Mixed Chart),并分别绑定独立的左右双 Y 轴(Dual Axes)。本文将通过结构解析、完整配置步骤与避坑细节,完整演示 Chart.js 混合多轴图表的落地实现。

Chart.js 混合图表制作:组合折线柱状图与多 Y 轴配置
图 0:Chart.js 组合折线柱状图与左右双 Y 轴效果概览

一、混合图表的核心构建逻辑:类型重写与图层顺序

Chart.js 的图表实例化通常在最顶层声明一个 type(如 type: 'bar')。但在混合图表中,Chart.js 提供了局部覆盖机制:只要顶层图表类型属于支持混合的基类(最常用的基类是 bar),就可以在 data.datasets 数组内部为任意特定数据集显式指定子类型 type: 'line'。这种声明会覆盖全局默认设定,使该数据集按照折线图的逻辑绘制。

除了类型声明,混合图表还需要重点关注渲染层叠顺序(Layer Order)。在默认的 Canvas 绘制流中,数据集按照数组索引从前往后依次绘制。如果柱状图排在折线图之后绘制,较宽的柱体就会遮挡住折线的节点或线段。Chart.js 提供了 order 属性用于精确控制层级:order 数值越小,渲染优先级越高,图层越靠近顶层。因此,通常将折线数据集设置为 order: 1,柱状数据集设置为 order: 2,确保折线平滑悬浮于柱状图上方。

Chart.js 混合图表制作:组合折线柱状图与多 Y 轴配置
图 1:混合图表内部对象映射与层叠机制示意图

二、制作混合图表的四个核心操作步骤

构建一个健壮的折线柱状混合图表,需要按照清晰的流程进行初始化与配置。以下是经过实践验证的四个标准阶段:

  1. 准备宿主容器并声明顶层图表:在页面中建立 <canvas> 元素,通过 new Chart(ctx, config) 初始化实例,并将顶层 type 设为 'bar'。
  2. 在 datasets 数组中重写子图表形态:在柱状数据集定义柱体样式(如背景色、圆角 borderRadius),在折线数据集内明确写入 type: 'line',并配置 tension(平滑曲线曲率)与 borderColor。
  3. 声明独立 Y 轴并分配 yAxisID:在 options.scales 下定义主轴 y 与副轴 y1,分别指定 position: 'left' 与 position: 'right',并在各数据集中通过 yAxisID 完成精准绑定。
  4. 解耦副轴网格线与微调渲染层级:将副轴的 grid.drawOnChartArea 设为 false 避免网格交织,同时使用 order 参数确保折线置于柱体上层。
Chart.js 混合图表制作:组合折线柱状图与多 Y 轴配置
图 2:混合图表构建四步操作流程图

三、多 Y 轴(Dual Axes)配置与坐标映射

解决量纲与数量级冲突的关键在于 options.scales。在 Chart.js 现代版本中,坐标轴是一组可扩展的命名对象。我们无需受限于预设的单一 Y 轴,而是可以自定义任意数量的坐标轴标识符。

1. 轴定义与位置分配

常规做法是将数量级较大的绝对值(如销售额)分配给左侧的 y 轴(默认即为左侧,也可显式声明 position: 'left'),将比率或指数(如转化率)分配给右侧的 y1 轴,并显式指定 position: 'right'。在数据集对象内,通过 yAxisID: 'y' 和 yAxisID: 'y1' 将数据点映射到对应的几何空间。

2. 解耦网格线(drawOnChartArea: false)

很多初学者在配置双 Y 轴后,会发现画布背景中的水平网格线变得异常密集而混乱。这是因为 Chart.js 默认让每一个 Y 轴都在图表绘图区完整绘制网格线。由于左轴刻度(如 0, 20万, 40万)与右轴刻度(如 0%, 5%, 10%)的计算步长不同,两套网格横线交织在一起,给读者带来极大的视觉干扰。

解决方法非常明确:让主 Y 轴单独承担背景网格的绘制,而在副 Y 轴中关闭网格向图表绘图区的延伸。只需要在 scales.y1.grid 中设置 drawOnChartArea: false,副轴就只会在右侧边框保留刻度小标记(tick mark),而不会向左延伸横线,从而让整个图表保持清爽统一步调。

Chart.js 混合图表制作:组合折线柱状图与多 Y 轴配置
图 3:多 Y 轴刻度映射与网格隔离细节对比指示图

3. 刻度文本格式化(ticks.callback)

双轴展示时,必须让读者在第一眼就能识别左右两侧数字的具体含义。使用 scales[axis].ticks.callback 函数,可以为刻度数字追加货币符号、单位后缀(如“万”)或百分号(%)。例如:

// 左轴刻度追加货币前缀与单位
scales: {
  y: {
    type: 'linear',
    position: 'left',
    beginAtZero: true,
    ticks: {
      callback: function(value) {
        return '¥' + (value / 10000) + '万';
      }
    }
  },
  y1: {
    type: 'linear',
    position: 'right',
    beginAtZero: true,
    grid: {
      drawOnChartArea: false // 关键:关闭右轴背景横线
    },
    ticks: {
      callback: function(value) {
        return value + '%';
      }
    }
  }
}

四、关键调优技巧与常见问题排查

在实际项目落地中,混合图表往往还会遇到坐标轴对齐、Tooltip 信息区分以及图例交互等细节问题。对照以下总结方案能够迅速定位并排除异常:

Chart.js 混合图表制作:组合折线柱状图与多 Y 轴配置
图 4:Chart.js 混合图表核心配置速查与排错决策总结

1. 零刻度基准线对齐(Zero Baseline Alignment)

当柱状图包含负利润,或者转化率有负增长时,两个 Y 轴各自生成的零点高度可能不一致。这会导致左侧的 0 元横线在下方,而右侧的 0% 横线在中间,让读者对正负收益产生误判。在简单的非负数据场景中,务必对双轴同时启用 beginAtZero: true;如果存在正负混合数据,需要计算左右两组数据的最大值与最小值比例,通过配置固定 min 和 max,强制让两条轴的零基准线锁定在同一水平像素线上。

2. Tooltip 提示框的量纲隔离

当鼠标悬浮在柱体或折线节点上时,默认的 Tooltip 只会显示裸露的数字。为了提升数据可读性,可以通过 plugins.tooltip.callbacks.label 针对不同数据集格式化内容:通过判断 context.dataset.yAxisID 或 context.datasetIndex,为主轴数据追加“元”单位,为副轴数据追加“%”单位。

3. 避免柱状堆叠属性干扰折线

如果业务需求中柱状图需要堆叠显示(例如包含“线上销售额”与“线下销售额”两个堆叠柱状),在 scales.x 与 scales.y 上启用 stacked: true 时,折线数据集不应参与柱状堆叠。由于折线已绑定至 y1 轴,只要不将 y1 设为 stacked: true,折线就不会受到柱体累加计算的干扰。

五、完整综合示例代码(开箱即用)

以下是整合了折线柱状混合类型、左右双 Y 轴映射、副轴网格解耦、层级排序及单位格式化的完整 HTML 与 JavaScript 代码,可直接保存并在现代浏览器中运行验证:

<!DOCTYPE html>
<html lang="zh-CN">
<head>
  <meta charset="UTF-8">
  <title>Chart.js 混合图表示例</title>
  <script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
  <style>
    .chart-box {
      width: 760px;
      margin: 40px auto;
      padding: 20px;
      background: #ffffff;
      border: 1px solid #e2e8f0;
      border-radius: 8px;
    }
  </style>
</head>
<body>
  <div class="chart-box">
    <canvas id="mixedChart"></canvas>
  </div>

  <script>
    const ctx = document.getElementById('mixedChart').getContext('2d');

    const mixedChart = new Chart(ctx, {
      type: 'bar', // 顶层默认基础类型
      data: {
        labels: ['1月', '2月', '3月', '4月', '5月', '6月'],
        datasets: [
          {
            type: 'bar',
            label: '月度销售额 (元)',
            data: [420000, 580000, 650000, 490000, 720000, 840000],
            backgroundColor: '#477DAD',
            borderRadius: 4,
            yAxisID: 'y', // 绑定左侧主 Y 轴
            order: 2      // 渲染层叠置于底层
          },
          {
            type: 'line', // 局部重写为折线类型
            label: '商机转化率 (%)',
            data: [8.5, 12.2, 11.0, 15.6, 17.8, 19.4],
            borderColor: '#4F6272',
            backgroundColor: '#4F6272',
            borderWidth: 3,
            tension: 0.3, // 平滑贝塞尔曲线
            pointRadius: 4,
            pointHoverRadius: 6,
            yAxisID: 'y1', // 绑定右侧副 Y 轴
            order: 1       // 渲染层叠置于顶层,防止被柱体遮挡
          }
        ]
      },
      options: {
        responsive: true,
        interaction: {
          mode: 'index',
          intersect: false
        },
        plugins: {
          tooltip: {
            callbacks: {
              label: function(context) {
                let label = context.dataset.label || '';
                if (label) {
                  label += ': ';
                }
                if (context.dataset.yAxisID === 'y1') {
                  label += context.parsed.y + '%';
                } else {
                  label += '¥' + context.parsed.y.toLocaleString();
                }
                return label;
              }
            }
          }
        },
        scales: {
          x: {
            grid: {
              display: false // 关闭竖向网格,保持整体界面简洁
            }
          },
          y: {
            type: 'linear',
            position: 'left',
            beginAtZero: true,
            title: {
              display: true,
              text: '销售总额 (元)',
              color: '#477DAD'
            },
            ticks: {
              callback: function(val) {
                return '¥' + (val / 10000) + '万';
              }
            }
          },
          y1: {
            type: 'linear',
            position: 'right',
            beginAtZero: true,
            title: {
              display: true,
              text: '转化率 (%)',
              color: '#4F6272'
            },
            grid: {
              drawOnChartArea: false // 核心:关闭副轴横向网格线,彻底避免网格重叠冲突
            },
            ticks: {
              callback: function(val) {
                return val + '%';
              }
            }
          }
        }
      }
    });
  </script>
</body>
</html>
核心实现法则回顾

制作 Chart.js 混合图表与多 Y 轴的核心要领可以精简为三句话:顶层声明基础类型,dataset 局部指定 type: 'line';双轴分配 position 并通过 yAxisID 显式绑定;副轴务必声明 grid.drawOnChartArea: false 隔绝网格交织。掌握这三个关键配置点,即可在任何业务场景中快速输出专业、清晰的复合数据可视化图表。

实用工具推荐

电子包浆

绿化、压缩、噪点和虚拟水印,自由调节做旧程度。

使用 ›
图片压缩

选择画质和输出格式,预览压缩前后的大小。

使用 ›
相关文章