如何用D3.js实现指定的自定义环形分段形状?
如何用D3.js实现指定的自定义环形分段形状?
嘿,我懂你现在的困扰——手动凑数值调形状真的太折腾了,而且那个gapAngle确实没啥用,咱们来把代码捋顺,精准实现你要的图形。
首先聊聊原代码里的几个核心问题:
- 手动硬编码角度偏移(比如
1.517这种数值)完全不灵活,得靠精确计算来对齐内外段端点 gapAngle属于冗余变量,直接删掉就行,咱们让分段精准衔接- 连接线的倾斜逻辑应该和分段位置联动,而不是用固定值
下面是优化后的完整代码,我已经帮你移除了无用的gapAngle,用计算式精准对齐所有端点,连接线也能完美衔接内外分段:
document.addEventListener("DOMContentLoaded", () => { const width = 1000; // 画布宽度 const height = 1000; // 画布高度 const innerRingOuterRadius = 280; // 内部分段的外半径 const outerRingInnerRadius = 295; // 外侧分段的内半径 const segmentThickness = 3; // 统一所有分段的厚度 const segmentColor = "#265152"; // 分段颜色 const lineColor = "#FF0000"; // 连接线颜色 const inclinationAngle = 0.06; // 连接线的倾斜角度(弧度) // 各分段的角度(角度制) const innerSegmentDegrees = [133, 133]; // 左右对称的两个内部分段 const outerSegmentDegrees = [40, 40]; // 对应外侧的两个小分段 // 转换为弧度 const innerSegmentRadians = innerSegmentDegrees.map(deg => deg * Math.PI / 180); const outerSegmentRadians = outerSegmentDegrees.map(deg => deg * Math.PI / 180); // 计算内部分段的结束角度,用于定位外侧分段的起始位置 const innerSegmentEnds = [ innerSegmentRadians[0], Math.PI + innerSegmentRadians[1] ]; // 创建SVG画布并居中 const svg = d3 .select("#c4") .append("svg") .attr("width", width) .attr("height", height) .append("g") .attr("transform", `translate(${width / 2}, ${height / 2})`); // 极坐标转笛卡尔坐标的工具函数 const polarToCartesian = (radius, angle) => ({ x: radius * Math.cos(angle), y: radius * Math.sin(angle), }); // 绘制内部分段 innerSegmentRadians.forEach((segmentAngle, index) => { const startAngle = index === 0 ? 0 : Math.PI; // 左右对称放置 const endAngle = startAngle + segmentAngle; const arc = d3 .arc() .innerRadius(innerRingOuterRadius - segmentThickness) .outerRadius(innerRingOuterRadius) .startAngle(startAngle) .endAngle(endAngle); svg.append("path") .attr("d", arc) .attr("fill", segmentColor); }); // 绘制外侧分段(精准衔接内部分段的间隙) outerSegmentRadians.forEach((segmentAngle, index) => { const startAngle = innerSegmentEnds[index]; // 直接从内部分段结束处开始 const endAngle = startAngle + segmentAngle; const arc = d3 .arc() .innerRadius(outerRingInnerRadius) .outerRadius(outerRingInnerRadius + segmentThickness) .startAngle(startAngle) .endAngle(endAngle); svg.append("path") .attr("d", arc) .attr("fill", segmentColor); }); // 绘制连接线(精准衔接内外分段的端点) const connectionPoints = [ // 左侧内段结束点 -> 左侧外段起始点 { innerAngle: innerSegmentEnds[0], outerAngle: innerSegmentEnds[0], direction: -1 }, // 右侧内段结束点 -> 右侧外段起始点 { innerAngle: innerSegmentEnds[1], outerAngle: innerSegmentEnds[1], direction: 1 }, // 左侧外段结束点 -> 左侧内段对应点 { innerAngle: innerSegmentEnds[0] - innerSegmentRadians[0] + outerSegmentRadians[0], outerAngle: innerSegmentEnds[0] + outerSegmentRadians[0], direction: -1 }, // 右侧外段结束点 -> 右侧内段对应点 { innerAngle: innerSegmentEnds[1] - innerSegmentRadians[1] + outerSegmentRadians[1], outerAngle: innerSegmentEnds[1] + outerSegmentRadians[1], direction: 1 } ]; connectionPoints.forEach(({ innerAngle, outerAngle, direction }) => { const innerPoint = polarToCartesian(innerRingOuterRadius, innerAngle); // 应用倾斜角度,左右方向相反 const inclinedOuterAngle = outerAngle + direction * inclinationAngle; const outerPoint = polarToCartesian(outerRingInnerRadius, inclinedOuterAngle); svg.append("line") .attr("x1", innerPoint.x) .attr("y1", innerPoint.y) .attr("x2", outerPoint.x) .attr("y2", outerPoint.y) .attr("stroke", lineColor) .attr("stroke-width", 2); }); });
几个关键优化说明:
- 移除冗余变量:删掉了无用的
gapAngle,直接通过内部分段的结束角度定位外侧分段的起始位置,确保分段间没有间隙 - 替换硬编码偏移:所有端点位置都用计算式生成,不管你调整分段角度、半径还是厚度,图形都会自动对齐,不用再手动凑数字
- 统一可配置项:新增
segmentThickness变量,方便后续统一调整所有分段的厚度 - 联动连接线逻辑:连接线的倾斜方向和分段位置绑定,确保左右两侧的线条对称且精准衔接
之后你要是想调整图形参数,直接修改顶部的变量值就行,不用再折腾内部的角度计算啦。
备注:内容来源于stack exchange,提问作者Global Ant
相关产品推荐
相关产品推荐

