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

如何使用d3.nest处理嵌套数据绘制D3.js饼图?

用D3.js嵌套数据创建细分饼图并添加交互展示

没问题,我来帮你一步步实现这个需求——把奖牌数据按项目拆分为129类,用嵌套数据生成饼图,同时添加交互展示细分内容。

第一步:用d3.nest处理数据(扁平化嵌套结构)

首先我们需要把原始的3类奖牌数据,按项目+奖牌类型拆分为细分条目。这里分两种常见的原始数据格式处理:

情况1:原始数据是单个获奖记录

如果你的数据是每条记录对应一个获奖项(比如[{sport: "游泳", medal: "Gold"}, ...]),用d3.nest按项目和奖牌分组并统计数量:

// 假设rawData是你的原始获奖记录数组
const nestedData = d3.nest()
  .key(d => d.sport)          // 第一层键:项目名称
  .key(d => d.medal)          // 第二层键:奖牌类型(Gold/Silver/Bronze)
  .rollup(group => group.length) // 统计每组的获奖数量
  .entries(rawData);

// 将嵌套结构扁平化为d3.pie可直接使用的数组
const pieData = [];
nestedData.forEach(sportGroup => {
  sportGroup.values.forEach(medalGroup => {
    pieData.push({
      label: `${sportGroup.key} - ${medalGroup.key}`, // 项目+奖牌的组合标签
      value: medalGroup.value,
      sport: sportGroup.key,
      medal: medalGroup.key
    });
  });
});

情况2:原始数据是按项目汇总的奖牌数

如果你的数据已经按项目汇总了金、银、铜数量(比如[{sport: "游泳", gold:5, silver:3, bronze:2}, ...]),直接拆分为单个条目即可:

// 假设rawData是按项目汇总的数组
const pieData = [];
rawData.forEach(item => {
  // 拆分金牌条目
  pieData.push({
    label: `${item.sport} - Gold`,
    value: item.gold,
    sport: item.sport,
    medal: "Gold"
  });
  // 拆分银牌条目
  pieData.push({
    label: `${item.sport} - Silver`,
    value: item.silver,
    sport: item.sport,
    medal: "Silver"
  });
  // 拆分铜牌条目
  pieData.push({
    label: `${item.sport} - Bronze`,
    value: item.bronze,
    sport: item.sport,
    medal: "Bronze"
  });
});

第二步:重构饼图代码(适配细分数据)

现在用处理好的pieData替换原来的data,同时调整颜色映射、交互逻辑,适配129个细分项:

// 初始化饼图生成器,指定value字段
var pie = d3.pie().value(d => d.value);

// 颜色映射:按奖牌类型分配颜色,保持同一奖牌颜色一致
var color = d3.scaleOrdinal()
  .domain(["Gold", "Silver", "Bronze"])
  .range(['#e5ce0c', '#e5e4e0', '#a4610a']);

// 创建弧生成器:饼图设innerRadius为0,环形图可设为w/4这类大于0的值
var arc = d3.arc()
  .outerRadius(Math.min(w/2, (h-25)/2) - 10)
  .innerRadius(0);

// 绑定数据并创建弧组
var arcs = d3.select(svg).selectAll("g.arc")
  .data(pie(pieData))
  .enter()
  .append("g")
  .attr("class", "arc")
  .attr("transform", `translate(${w/2}, ${(h-25)/2})`);

// 绘制弧路径
arcs.append("path")
  .attr("fill", d => color(d.data.medal)) // 按奖牌类型取色,保持视觉一致性
  .attr("d", arc)
  .attr("stroke", "white")
  .style("stroke-width", "0.5px")
  // 鼠标hover交互:高亮+显示细分信息
  .on('mouseover', function(event, d) {
    d3.select(this).attr('opacity', .7);
    // 创建自定义tooltip(可根据需求调整样式)
    d3.select("body").append("div")
      .attr("class", "tooltip")
      .style("position", "absolute")
      .style("background", "rgba(0,0,0,0.8)")
      .style("color", "white")
      .style("padding", "8px")
      .style("border-radius", "4px")
      .html(`
        <strong>项目:</strong> ${d.data.sport}<br>
        <strong>奖牌:</strong> ${d.data.medal}<br>
        <strong>数量:</strong> ${d.data.value}
      `)
      .style("left", (event.pageX + 10) + "px")
      .style("top", (event.pageY - 10) + "px");
  })
  .on('mouseleave', function() {
    d3.select(this).attr('opacity', 1);
    // 移除tooltip
    d3.select(".tooltip").remove();
  });

// 添加标题
d3.select(svg).append('text')
  .attr('x', w/2)
  .attr('y', '20')
  .text(`Medal breakdown for ${country} (by Sport)`)
  .attr('font-size', '16px')
  .attr("text-anchor", "middle");

// 可选:添加弧中心文本(仅显示数量大于0的项,避免拥挤)
arcs.append("text")
  .attr("transform", d => `translate(${arc.centroid(d)})`)
  .attr("text-anchor", "middle")
  .style("font-size", "10px")
  .text(d => d.data.value > 0 ? d.data.value : "");

关键注意点

  • 数据扁平化:d3.nest生成的是嵌套层级结构,必须转成扁平数组才能被d3.pie识别,每个元素需要包含value字段。
  • 颜色一致性:按奖牌类型而非索引映射颜色,让用户更容易通过颜色快速识别奖牌等级。
  • 交互优化:因为有129个细分项,hover时的tooltip要清晰展示核心信息;如果饼图过于拥挤,可改为环形图(调整innerRadius)或添加筛选/缩放功能。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.06 17:52:30