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

如何消除D3.js分组柱状图中缺失数据导致的柱间空白

解决D3.js分组柱状图缺失数据导致的空白问题

问题核心是当前代码为所有年龄组(即使某州无对应数据)都预留了位置,导致空白。要消除空白,需要让每个州只渲染存在数据的柱子,并动态分配组内的位置和宽度。

修改步骤

1. 过滤无效数据项

在绑定柱子数据时,筛掉每个州中值为undefined的年龄组,避免生成空柱子:

.data(function(d) { 
  return keys.map(function(key) { return {key: key, value: d[key]}; })
    .filter(item => item.value !== undefined);
})

2. 为每个州创建独立子比例尺

不再使用全局的x1比例尺,而是为每个州单独生成子比例尺,基于该州的有效年龄组计算位置和带宽:

g.append("g")
  .selectAll("g")
  .data(data)
  .enter().append("g")
    .attr("transform", function(d) { return "translate(" + x0(d.State) + ",0)"; })
    .each(function(d) {
      // 获取当前州的有效年龄组
      const validKeys = keys.filter(key => d[key] !== undefined);
      // 创建专属子比例尺
      const x1 = d3.scaleBand()
        .padding(0.05)
        .domain(validKeys)
        .rangeRound([0, x0.bandwidth()]);
      // 将比例尺绑定到当前组元素
      d3.select(this).datum(Object.assign({}, d, {x1: x1}));
    })

3. 动态计算柱子位置与宽度

绘制柱子时,从当前组元素获取绑定的子比例尺,计算每个柱子的x坐标和宽度:

.selectAll("rect")
.data(function(d) { 
  return keys.map(function(key) { return {key: key, value: d[key]}; })
    .filter(item => item.value !== undefined);
})
.enter().append("rect")
  .attr("x", function(d) { 
    const x1 = d3.select(this.parentNode).datum().x1;
    return x1(d.key); 
  })
  .attr("width", function() { 
    const x1 = d3.select(this.parentNode).datum().x1;
    return x1.bandwidth(); 
  })

修改后的完整代码

<!DOCTYPE html>
<style>
.axis .domain {
 display: none;
}
</style>
<svg width="960" height="500"></svg>
<script src="https://d3js.org/d3.v4.min.js"></script>
<script>
var svg = d3.select("svg"),
   margin = {top: 20, right: 20, bottom: 30, left: 40},
   width = +svg.attr("width") - margin.left - margin.right,
   height = +svg.attr("height") - margin.top - margin.bottom,
   g = svg.append("g").attr("transform", "translate(" + margin.left + "," + margin.top + ")");

var x0 = d3.scaleBand()
   .rangeRound([0, width])
   .paddingInner(0.1);

var y = d3.scaleLinear()
   .rangeRound([height, 0]);

var z = d3.scaleOrdinal()
   .range(["#98abc5", "#8a89a6", "#7b6888", "#6b486b", "#a05d56", "#d0743c", "#ff8c00"]);

var data = [
 {
   "State": "CA",
   "Under 5 Years": 2704659,
   "5 to 13 Years": 4499890,
   "14 to 17 Years": 2159981,
   "18 to 24 Years": 3853788,
   "25 to 44 Years": 10604510,
   "45 to 64 Years": 8819342,
   "65 Years and Over": 4114496
 },
 {
   "State": "TX",
   "Under 5 Years": 2027307,
   "5 to 13 Years": 3277946,   
   "45 to 64 Years": 5656528,
   "65 Years and Over": 2472223
 },
 {
   "State": "NY",
   "Under 5 Years": 1208495,
   "5 to 13 Years": 2141490,    
   "25 to 44 Years": 5355235,
   "45 to 64 Years": 5120254,
   "65 Years and Over": 2607672
 },
 {
   "State": "FL",
   "Under 5 Years": 1140516,
   "5 to 13 Years": 1938695,
   "14 to 17 Years": 925060,
   "18 to 24 Years": 1607297,
   "25 to 44 Years": 4782119,
   "45 to 64 Years": 4746856,
   "65 Years and Over": 3187797
 },
 {
   "State": "IL",
   "Under 5 Years": 894368,
   "5 to 13 Years": 1558919,
   "14 to 17 Years": 725973,
   "18 to 24 Years": 1311479,
   "25 to 44 Years": 3596343,
   "45 to 64 Years": 3239173,
   "65 Years and Over": 1575308
 },
 {
   "State": "PA",
   "Under 5 Years": 737462,
   "5 to 13 Years": 1345341,
   "14 to 17 Years": 679201,
   "18 to 24 Years": 1203944,
   "25 to 44 Years": 3157759,
   "45 to 64 Years": 3414001,
   "65 Years and Over": 1910571
 }
];

var keys  = Object.keys(data[0]);
keys = keys.slice(1);

x0.domain(data.map(function(d) { return d.State; }));
y.domain([0, d3.max(data, function(d) { return d3.max(keys, function(key) { return d[key]; }); })]).nice();

g.append("g")
  .selectAll("g")
  .data(data)
  .enter().append("g")
    .attr("transform", function(d) { return "translate(" + x0(d.State) + ",0)"; })
    .each(function(d) {
      const validKeys = keys.filter(key => d[key] !== undefined);
      const x1 = d3.scaleBand()
        .padding(0.05)
        .domain(validKeys)
        .rangeRound([0, x0.bandwidth()]);
      d3.select(this).datum(Object.assign({}, d, {x1: x1}));
    })
  .selectAll("rect")
  .data(function(d) { 
    return keys.map(function(key) { return {key: key, value: d[key]}; })
      .filter(item => item.value !== undefined);
  })
  .enter().append("rect")
    .attr("x", function(d) { 
      const x1 = d3.select(this.parentNode).datum().x1;
      return x1(d.key); 
    })
    .attr("y", function(d) { return y(d.value); })
    .attr("width", function() { 
      const x1 = d3.select(this.parentNode).datum().x1;
      return x1.bandwidth(); 
    })
    .attr("height", function(d) { return height - y(d.value); })
    .attr("fill", function(d) { return z(d.key); });

g.append("g")
    .attr("class", "axis")
    .attr("transform", "translate(0," + height + ")")
    .call(d3.axisBottom(x0));

g.append("g")
    .attr("class", "axis")
    .call(d3.axisLeft(y).ticks(null, "s"))
  .append("text")
    .attr("x", 2)
    .attr("y", y(y.ticks().pop()) + 0.5)
    .attr("dy", "0.32em")
    .attr("fill", "#000")
    .attr("font-weight", "bold")
    .attr("text-anchor", "start")
    .text("Population");

var legend = g.append("g")
    .attr("font-family", "sans-serif")
    .attr("font-size", 10)
    .attr("text-anchor", "end")
  .selectAll("g")
  .data(keys.slice().reverse())
  .enter().append("g")
    .attr("transform", function(d, i) { return "translate(0," + i * 20 + ")"; });

legend.append("rect")
    .attr("x", width - 19)
    .attr("width", 19)
    .attr("height", 19)
    .attr("fill", z);

legend.append("text")
    .attr("x", width - 24)
    .attr("y", 9.5)
    .attr("dy", "0.32em")
    .text(function(d) { return d; });
</script>
</html>

关键说明

  • 每个州的柱子组会根据自身有效数据动态计算布局,不存在数据的年龄组不会预留位置。
  • 子比例尺确保每个州内的柱子均匀填充组内宽度,消除空白间隙。

内容的提问来源于stack exchange,提问作者Shijo C J

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.29 15:09:52