如何计算SVG环形图中各路径的旋转角度?
SVG环形图分段旋转角度计算问题
以下代码用于生成SVG环形图的路径:
const getArcPath = (start, end, innerRadius, outerRadius) => { const startAngle = start * Math.PI * 2; const endAngle = end * Math.PI * 2; const x1 = innerRadius * Math.sin(startAngle); const y1 = innerRadius * -Math.cos(startAngle); const x2 = outerRadius * Math.sin(startAngle); const y2 = outerRadius * -Math.cos(startAngle); const x3 = outerRadius * Math.sin(endAngle); const y3 = outerRadius * -Math.cos(endAngle); const x4 = innerRadius * Math.sin(endAngle); const y4 = innerRadius * -Math.cos(endAngle); const bigArc = end - start >= 0.5; const outerFlags = bigArc ? '1 1 1' : '0 0 1'; const innerFlags = bigArc ? '1 1 0' : '1 0 0'; return `M ${x1},${y1} L ${x2},${y2} A ${outerRadius} ${outerRadius} ${outerFlags} ${x3},${y3} L ${x4},${y4} A ${innerRadius} ${innerRadius} ${innerFlags} ${x1},${y1} Z`; };
我需要获取环形图中各分段(红、绿、蓝、紫)的旋转角度,但尝试了网上找到的代码后无法正常工作,核心是没搞懂正确的计算逻辑:
const getRotationAngle = (start, end) => { const startAngle = start * 360; const endAngle = end * 360; return (startAngle + endAngle) / 2; };
完整React实现代码
JavaScript
const {useState, useEffect, useMemo} = React; const PROGRESS_UNIT = 0.01; const PROGRESS_TIMEOUT = 5; const getArcPath = (start, end, innerRadius, outerRadius) => { const gap = 0.01; // 调整此值控制分段间的间隙 const startAngle = start * Math.PI * 2; const endAngle = (end - gap) * Math.PI * 2; // 调整后的结束角度 const x1 = innerRadius * Math.sin(startAngle); const y1 = innerRadius * -Math.cos(startAngle); const x2 = outerRadius * Math.sin(startAngle); const y2 = outerRadius * -Math.cos(startAngle); const x3 = outerRadius * Math.sin(endAngle); const y3 = outerRadius * -Math.cos(endAngle); const x4 = innerRadius * Math.sin(endAngle); const y4 = innerRadius * -Math.cos(endAngle); const bigArc = end - start - gap >= 0.5; const outerFlags = bigArc ? '1 1 1' : '0 0 1'; const innerFlags = bigArc ? '1 1 0' : '1 0 0'; return { path: `M ${x1},${y1} L ${x2},${y2} A ${outerRadius} ${outerRadius} ${outerFlags} ${x3},${y3} L ${x4},${y4} A ${innerRadius} ${innerRadius} ${innerFlags} ${x1},${y1} Z`, }; }; const DonutChart = ({ width, height, items, innerRadius, outerRadius }) => { const [visiblePart, setVisiblePart] = useState(0); const [rotationAngle, setRotationAngle] = useState(0) useEffect(() => { if (visiblePart < 1) { setTimeout(() => setVisiblePart(visiblePart + PROGRESS_UNIT), PROGRESS_TIMEOUT); } }, [visiblePart]); const segments = useMemo(() => { const sum = items.reduce((sum, item) => sum + item.value, 0); let start = 0; return items.map((item) => { const delta = (item.value / sum) * visiblePart; const { path } = getArcPath(start, start + delta, innerRadius, outerRadius); // 计算当前分段的中心旋转角度 const segmentStartDeg = start * 360; const segmentEndDeg = (start + delta) * 360; const centerRotation = (segmentStartDeg + segmentEndDeg) / 2; start += delta; return { ...item, path, rotationAngle: centerRotation }; }); }, [items, innerRadius, outerRadius, visiblePart]); useEffect(() => { if (segments.length > 0) { setRotationAngle(segments[0].rotationAngle); } }, [segments]) return ( <div className='container'> <div className='info'>{rotationAngle.toFixed(2)}deg / {segments[0]?.color || ''}</div> <div className='pointer'></div> <svg width={width} height={height}> <g transform={`translate(${width/2},${height/2})`}> {segments.map((segment) => ( <path key={segment.color} stroke={segment.color} fill={segment.color} d={segment.path} {/* 若需单个分段旋转,取消下方注释 */} {/* transform={`rotate(${segment.rotationAngle})`} */} /> ))} </g> </svg> </div> ); }; const items = [ {value: 100, color: 'red'}, {value: 200, color: 'green'}, {value: 300, color: 'blue'}, {value: 150, color: 'purple'}, ]; ReactDOM.render(<DonutChart width={500} height={300} items={items} innerRadius={100} outerRadius={140} />, document.querySelector("#app"))
CSS
svg { border: 1px solid #888; } .pointer { width: 20px; height: 20px; border-left: 15px solid transparent; border-right: 15px solid transparent; border-top: 15px solid #2f2f2f; margin: 0 auto; } div { position: relative; } .info { margin: 0 auto; text-align: center; font-weight: bold; } .container { width: 500px; }
HTML
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/18.2.0/umd/react.production.min.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/18.2.0/umd/react-dom.production.min.js"></script> <div id='app' />
问题解决说明
- 正确的旋转角度计算逻辑:
- 每个分段的
start和end是基于总数值占比的0-1区间值,乘以360即可转换为对应的角度。 - 分段的中心旋转角度取起始角度与结束角度的平均值,即
(start*360 + (start+delta)*360)/2,这个值就是该分段的中心旋转角度。
- 每个分段的
- 原代码问题分析:
- 原
getRotationAngle函数逻辑本身正确,但之前未正确传递start和end参数(原getArcPath未返回旋转角度,且计算时未结合visiblePart的动画进度)。
- 原
- 关键修改点:
- 在生成分段的
useMemo逻辑中,直接计算每个分段的中心旋转角度并挂载到分段对象上。 - 移除了原代码中错误的整体旋转逻辑,若需要单个分段独立旋转,可在对应
path标签的transform属性中设置旋转角度。
- 在生成分段的
内容的提问来源于stack exchange,提问作者dinmavr
相关产品推荐
相关产品推荐

