如何用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
相关产品推荐
相关产品推荐

