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

如何遍历D3.js嵌套数据并实现分组Y轴水平条形图

实现递归遍历嵌套数据并构建分组Y轴的水平条形图

我来帮你搞定这个需求!针对你提供的d3.nest生成的嵌套数据,我们可以写一个通用的递归函数来遍历所有层级,收集分组信息,再基于这些信息构建多层Y轴分组的水平条形图。

首先先看你给出的嵌套数据:

let nestedData = [{ key: "A", values: [{ key: "ab", values: [{ key: "xy", values: [{ name: "tom" }] }, { key: "tu", values: [{ name: "corin" }] } ] }, { key: "cd", values: [{ key: "xy", values: [{ name: "nancy" }] }] } ] }, { key: "B", values: [{ key: "ab", values: [{ key: "xy", values: [{ name: "tom" }] }] }, { key: "cd", values: [{ key: "xy", values: [{ name: "nancy" }] }] } ] }]

第一步:编写递归处理函数

这个函数会遍历所有嵌套层级,收集每一级的分组键,同时记录最底层条目的完整路径(方便后续定位Y轴位置):

// 递归处理嵌套数据,收集层级分组信息和条目路径
function processNestedData(data, currentPath = [], level = 0) {
  const result = {
    groups: {}, // 存储每一层的所有分组key
    entries: [] // 存储最底层条目及其完整路径
  };

  // 记录当前层级的分组key(去重)
  result.groups[level] = [...new Set(data.map(item => item.key))];

  data.forEach(item => {
    const newPath = [...currentPath, item.key];
    // 如果还有子层级,递归处理
    if (item.values && item.values.length) {
      const childResult = processNestedData(item.values, newPath, level + 1);
      // 合并子层级的分组信息
      Object.keys(childResult.groups).forEach(lvl => {
        if (!result.groups[lvl]) {
          result.groups[lvl] = childResult.groups[lvl];
        } else {
          result.groups[lvl] = [...new Set([...result.groups[lvl], ...childResult.groups[lvl]])];
        }
      });
      // 合并子层级的条目
      result.entries.push(...childResult.entries);
    } else {
      // 到达最底层,保存条目和路径
      result.entries.push({ ...item, path: newPath });
    }
  });

  return result;
}

调用这个函数处理你的数据:

const processedData = processNestedData(nestedData);

第二步:构建Y轴比例尺(多层分组)

我们需要为每一层分组创建独立的比例尺,同时为最底层的条目创建基础Y轴比例尺:

// 先定义画布尺寸
const width = 600;
const height = processedData.entries.length * 40; // 根据条目数动态计算高度

// 最底层条目的Y轴比例尺(用来定位每个条形的位置)
const entryYScale = d3.scaleBand()
  .domain(d3.range(processedData.entries.length))
  .range([0, height])
  .padding(0.1);

// 为每一层分组创建比例尺,用来计算分组标签的位置和范围
const groupYScales = {};
// 获取所有层级并按顺序排序
const levels = Object.keys(processedData.groups).map(Number).sort((a, b) => a - b);

levels.forEach(level => {
  const groupKeys = processedData.groups[level];
  // 计算每个分组包含的条目数量,用来确定分组在Y轴上的覆盖范围
  const groupEntryCounts = groupKeys.map(key => {
    return processedData.entries.filter(entry => entry.path[level] === key).length;
  });

  groupYScales[level] = d3.scaleBand()
    .domain(groupKeys)
    .range([0, height])
    .padding(0.05);
});

// X轴比例尺(假设我们给每个条目模拟一个数值)
const xScale = d3.scaleLinear()
  .domain([0, 100])
  .range([0, width - 200]); // 留出空间放多层Y轴标签

第三步:绘制分组Y轴标签和水平条形

现在我们可以把分组标签和条形画出来了:

// 创建SVG画布
const svg = d3.select("body")
  .append("svg")
  .attr("width", width)
  .attr("height", height);

// 绘制每一层的分组标签
levels.forEach((level, idx) => {
  const scale = groupYScales[level];
  svg.selectAll(`.group-label-${level}`)
    .data(scale.domain())
    .enter()
    .append("text")
    .attr("class", `group-label-${level}`)
    .attr("x", -10 - idx * 80) // 每一层标签向左偏移,避免重叠
    .attr("y", d => scale(d) + scale.bandwidth() / 2)
    .attr("dy", "0.35em")
    .attr("text-anchor", "end")
    .style("font-size", "14px")
    .text(d => d);
});

// 绘制水平条形(这里给每个条目随机生成一个数值作为示例)
svg.selectAll(".bar")
  .data(processedData.entries)
  .enter()
  .append("rect")
  .attr("class", "bar")
  .attr("x", 0)
  .attr("y", (d, idx) => entryYScale(idx))
  .attr("width", () => xScale(Math.random() * 100))
  .attr("height", entryYScale.bandwidth())
  .attr("fill", "steelblue");

// 绘制X轴
const xAxis = d3.axisBottom(xScale);
svg.append("g")
  .attr("transform", `translate(0, ${height})`)
  .call(xAxis);

关键说明

  • 这个递归函数支持任意深度的嵌套数据,不管你的d3.nest分了多少层,都能自动适配
  • 分组标签的偏移量可以根据实际需求调整,避免不同层级的标签重叠
  • 如果你的实际数据里有数值字段(比如每个条目的value),直接替换掉示例里的随机数即可
  • 可以通过调整padding参数来控制分组和条形之间的间距

内容的提问来源于stack exchange,提问作者R.Kam

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.29 09:08:37