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

D3.js v7水平堆叠条形图Y轴意外偏移问题排查

问题根源与修复方案

你的堆叠条形图出现垂直偏移的核心原因是选择器范围错误:

  • 代码中svg.selectAll('g')会选中SVG中已存在的所有<g>元素(包括你之前创建的y轴、x轴容器g),导致数据绑定逻辑完全混乱。
  • 同时你没有正确使用D3的enter()模式创建新分组元素,而是直接调用.append('g'),这会在已有g元素上继续添加内容,最终引发布局偏移。

具体修复步骤

  1. 限定分组选择器范围
    将选择器改为针对专属类名(比如.bar-group),避免选中坐标轴的g元素,同时正确使用enter()模式创建新元素:
var groups = svg.selectAll('.bar-group') // 仅选择带bar-group类的g
  .data(newdata)
  .enter() // 仅为新增数据创建元素
  .append('g')
  .attr('class', 'bar-group') // 给分组添加类名
  .style('fill', function(d, i) {
    return colors(i);
  });
  1. 使用scaleBand的带宽设置条形高度
    替换固定的height:10,用yScale.bandwidth()获取自动计算的带宽,适配y轴刻度:
.attr('height', yScale.bandwidth())

完整修复后的代码

<script src="https://d3js.org/d3.v7.min.js"></script>

<body>
  <div id="bar_chart">
    <script>
      var data = [{
          dep_time: "5:30",
          risk: 100,
          details: [{
              time: 19,
              source: 'Drive'
            },
            {
              time: 10,
              source: 'Margin'
            },
            {
              time: 42,
              source: 'Full'
            },
            {
              time: 35,
              source: 'Crossing'
            },
            {
              time: 23,
              source: 'Drive'
            }
          ]
        },
        {
          dep_time: "6:20",
          risk: 80,
          details: [{
              time: 25,
              source: 'Drive'
            },
            {
              time: 1,
              source: 'Margin'
            },
            {
              time: 38,
              source: 'Full'
            },
            {
              time: 35,
              source: 'Crossing'
            },
            {
              time: 25,
              source: 'Drive'
            }
          ]
        },
        {
          dep_time: "7:10",
          risk: 5,
          details: [{
              time: 8,
              source: 'Drive'
            },
            {
              time: 28,
              source: 'Margin'
            },
            {
              time: 38,
              source: 'Full'
            },
            {
              time: 35,
              source: 'Crossing'
            },
            {
              time: 18,
              source: 'Drive'
            }
          ]
        }
      ];

      var units = [];
      var xMax = 0;
      data.forEach(function(s) {
        var total = 0;
        s.details.forEach(function(s) {
          s["x0"] = total;
          s["x"] = s.time;
          s["x1"] = total + s.time;
          total = total + s.time;
          if (total > xMax) xMax = total;
        });
        s["y"] = s.dep_time;
        units.push(s.dep_time);
      });

      var newdata = [];
      for (var i = 0; i < data[0].details.length; i++) {
        var row = [];
        data.forEach(function(s) {
          row.push({
            x: s.details[i].x,
            y: s.dep_time,
            x0: s.details[i].x0
          });
        });
        newdata.push(row);
      }

      var margins = {
        left: 50,
        bottom: 50,
        top: 25,
        right: 25
      };

      var sizes = {
        width: 500,
        height: 150
      };

      var width = sizes.width - margins.left - margins.right;
      var height = sizes.height - margins.bottom - margins.top;

      var svg = d3.select("#bar_chart")
        .append('svg')
        .attr('width', width + margins.left + margins.right)
        .attr('height', height + margins.bottom + margins.top)
        .append('g')
        .attr('transform', 'translate(' + margins.left + ', ' + margins.top + ")");

      var yScale = d3.scaleBand()
        .domain(units)
        .rangeRound([0, height])
        .padding(0.1);

      var yAxis = d3.axisLeft(yScale);
      var yAxisG = svg.append("g")
        .attr("id", "yaxis")
        .call(yAxis);

      const xScale = d3.scaleLinear()
        .domain([0, xMax])
        .range([0, width]);
      var xAxis = d3.axisBottom(xScale);
      var xAxisG = svg.append("g")
        .attr("transform", "translate(0, " + height + ")")
        .attr("id", "xaxis")
        .call(xAxis.ticks(8));

      var bar_colors = ['red', 'purple', 'green', 'lightblue', 'yellow'];
      var colors = function(i) {
        return bar_colors[i];
      }

      var groups = svg.selectAll('.bar-group')
        .data(newdata)
        .enter()
        .append('g')
        .attr('class', 'bar-group')
        .style('fill', function(d, i) {
          return colors(i);
        });

      groups.selectAll('rect')
        .data(function(d) {
          return d;
        })
        .enter()
        .append('rect')
        .attr('x', function(d) {
          return xScale(d.x0);
        })
        .attr('y', function(d) {
          return yScale(d.y);
        })
        .attr('height', yScale.bandwidth())
        .attr('width', function(d) {
          return xScale(d.x);
        });
    </script>
  </div>
</body>

额外说明:代码中还修正了SVG高度计算(原代码未包含top边距),并添加了可选的padding参数让条形之间有间距,优化视觉效果。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.15 03:25:31