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

如何用D3.js在单个SVG中实现多环径向饼图(NHL季后赛)

问题描述

我正在复刻2023年NHL季后赛的径向对阵图,最终参考的是NHL径向对阵图样式。目前已经完成最外层的首轮环,但不知道怎么添加后续轮次的径向环,现有代码如下:

<!DOCTYPE html>
<html lang = "en">
   <head>
      <script src="https://d3js.org/d3.v7.min.js"></script>      
      <style>
        .wedge:hover {
          stroke-width: 15;
        }
      </style>
   </head>
    <body>
        <script>
            // 创建SVG区域
            var svg = d3.select("body")
                        .append("svg")
                        .attr("width", 1200)
                        .attr("height", 800)
                        .style("border", "1px solid black");
            
            // 将SVG容器移到页面中央
            d3.select("svg")
              .append("g")
              .attr("transform", "translate(500,350)");
                
            // 创建首轮扇形布局
            var playoffRoundOne = d3.pie()
                .startAngle(Math.PI / 4)
                .endAngle(Math.PI * 3)
                .padAngle(.03)
                .value((d) => d.wins)
                .sort(function (a, b) {
                    return b.value > a.value;
                });

            // 季后赛队伍数据(包含队名、胜场、logo)
            var data = playoffRoundOne([{...}]);

            // 队伍颜色映射
            var colorScaleFill = d3.scaleOrdinal()
                                    .domain(["avalanche", "kraken", "stars", "wild", "knights", 
                                             "jets", "oilers", "kings", "bruins", "panthers", 
                                             "leafs", "lightning", "canes", "islanders", "devils", 
                                             "rangers",])
                                    .range(["#6F263D", "#99D9D9", "#006847", "#154734", "#B4975A", 
                                            "#041E42", "#FF4C00", "#A2AAAD", "#FFB81C", "#B9975B", 
                                            "#00205B", "#FFFFFF", "#CE1126", "#F47D30", "#CE1126", "#0038A8"]);
            
            // 首轮弧生成器
            var arcGen = d3.arc()
                           .innerRadius(242)
                           .outerRadius(300);

            // 绘制首轮扇形
            d3.select("svg g")
              .selectAll("path")
              .data(data)
              .enter()
              .append("path")
              .attr("d", arcGen)
              .attr("stroke", (d) => colorScaleFill((d)))
              .attr("stroke-linejoin", "round")
              .attr("stroke-width", 3)
              .attr("fill", (d) => colorScaleFill((d)))
              .attr("class","wedge");

            // 绘制首轮logo
            var imgs = svg.selectAll("image").data(data);
            imgs.enter()
                .append("image")
                .attr("xlink:href", (d)=>(d.data.logo))
                .attr("width", "30")
                .attr("height", "40")
                .attr("x", d => d3.pointRadial((d.startAngle + d.endAngle  - 0.02)/2 , (270))[0])
                .attr("y", d => d3.pointRadial((d.startAngle + d.endAngle  - 0.02)/2 , (540)/2)[1])
                .attr("transform","translate(488,330) rotate(1.2)");
  
      </script>
    </body>
</html>
解决方法

1. 按轮次整理数据

NHL季后赛每轮队伍数量减半:首轮16队→次轮8队→分区决赛4队→总决赛2队。给每一轮单独准备数据,要包含晋级队伍、对应的外层获胜队伍(用于关联对阵关系),以及轮次标识。示例:

// 次轮晋级队伍数据
var roundTwoData = playoffRoundTwo([
  {team: "knights", wins: 4, logo: "knights-logo-url", parentTeam: "knights"},
  {team: "oilers", wins: 4, logo: "oilers-logo-url", parentTeam: "oilers"},
  // 剩余6支次轮队伍数据
]);

2. 为每轮创建独立弧生成器

每一轮的环半径向内收缩,调整innerRadius和outerRadius参数,确保轮次之间有间隔:

// 首轮弧生成器(已实现)
var arcGenRound1 = d3.arc().innerRadius(242).outerRadius(300);
// 次轮弧生成器
var arcGenRound2 = d3.arc().innerRadius(180).outerRadius(230);
// 分区决赛弧生成器
var arcGenRound3 = d3.arc().innerRadius(120).outerRadius(170);
// 总决赛弧生成器
var arcGenRound4 = d3.arc().innerRadius(60).outerRadius(110);

3. 复用饼图布局逻辑

保持所有轮次的startAngle和endAngle一致,确保扇形对齐;根据每轮队伍数量调整padAngle,避免拥挤:

var playoffRoundTwo = d3.pie()
  .startAngle(Math.PI / 4)
  .endAngle(Math.PI * 3)
  .padAngle(.05) // 次轮队伍少,可增大间距
  .value(d => d.wins)
  .sort(null); // 按对阵顺序排列,不自动排序

4. 绘制每一轮的扇形

复制首轮的绘制逻辑,替换对应的弧生成器和数据:

// 绘制次轮扇形
d3.select("svg g")
  .selectAll(".round2-wedge")
  .data(roundTwoData)
  .enter()
  .append("path")
  .attr("d", arcGenRound2)
  .attr("stroke", d => colorScaleFill(d.data.team))
  .attr("stroke-width", 3)
  .attr("fill", d => colorScaleFill(d.data.team))
  .attr("class","wedge round2-wedge");

5. 添加晋级连接路径

绘制曲线连接外层获胜队伍和内层晋级队伍,明确对阵关系:

// 生成连接路径的工具函数
function getConnectionPath(outerArc, innerArc) {
  const outerMid = d3.pointRadial((outerArc.startAngle + outerArc.endAngle)/2, 242);
  const innerMid = d3.pointRadial((innerArc.startAngle + innerArc.endAngle)/2, 230);
  return `M ${outerMid[0]},${outerMid[1]} L ${innerMid[0]},${innerMid[1]}`;
}

// 绘制次轮连接路径
d3.select("svg g")
  .selectAll(".connection")
  .data(roundTwoData)
  .enter()
  .append("path")
  .attr("d", d => {
    const outerArc = data.find(item => item.data.team === d.data.parentTeam);
    return getConnectionPath(outerArc, d);
  })
  .attr("stroke", "#333")
  .attr("stroke-width", 2)
  .attr("fill", "none");

6. 调整内层轮次的logo位置

根据内层环的半径,修改d3.pointRadial的半径参数,让logo对应到轮次环的中间位置:

// 绘制次轮logo
svg.selectAll(".round2-logo")
  .data(roundTwoData)
  .enter()
  .append("image")
  .attr("xlink:href", d => d.data.logo)
  .attr("width", "30")
  .attr("height", "40")
  .attr("x", d => d3.pointRadial((d.startAngle + d.endAngle - 0.02)/2, 205)[0])
  .attr("y", d => d3.pointRadial((d.startAngle + d.endAngle - 0.02)/2, 205)[1])
  .attr("transform","translate(488,330) rotate(1.2)")
  .attr("class","round2-logo");

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.23 17:59:58