如何在环形图(Donut Chart)中实现扇区均匀分隔?
环形图扇区无法均匀分隔的修复方案
问题分析
你的代码存在几个核心问题导致扇区无法均匀分隔:
- 路径绘制时未使用扣除间距后的实际角度,仍依赖初始计算的
item.value判断大弧标志,逻辑错位 - 用于创建间距的角度调整仅作用于标签位置,未应用到扇区路径的角度计算
- 累计角度的累加逻辑未完全匹配扇区+间距的分配规则
修复步骤
- 移除冗余的
calculatedSlices和total计算,直接基于实际扇区角度处理逻辑 - 将间距调整整合到扇区的起始/结束角度计算中,确保路径绘制使用正确范围
- 修正大弧标志的判断逻辑,基于实际扇区角度是否超过180度来判定
- 调整累计角度的累加方式,保证扇区与间距的角度分配准确
修改后的完整代码
import { storyblokEditable } from "@storyblok/react"; import { type BlokType } from "../../../types/storyblok"; interface AnimatedPieBlokProps { blok: BlokType; } function AnimatedPieBlok({ blok }: AnimatedPieBlokProps) { const { pieSlices } = blok; const totalSlices = pieSlices.length; const spacing = 1; // 扇区间的间距角度 const sliceAngle = (360 - spacing * totalSlices) / totalSlices; // 每个扇区的实际角度(扣除总间距) let cumulativeAngle = 0; // 累计角度,初始为0 return ( <div {...storyblokEditable(blok)} key={blok._uid} className="flex flex-row justify-center"> <svg width="450" height="450" viewBox="0 0 200 200" className="flex-1"> {pieSlices.map((item: BlokType, index: number) => { const words = item.title.split(' '); const firstWord = words[0]; const secondWord = words[1]; // 计算当前扇区的起始/结束角度(转换为弧度) const startAngle = cumulativeAngle * (Math.PI / 180); // 结束角度 = 起始角度 + 扇区实际角度 const endAngle = (cumulativeAngle + sliceAngle) * (Math.PI / 180); // 计算路径端点坐标 const x1 = 100 + 100 * Math.cos(startAngle); const y1 = 100 + 100 * Math.sin(startAngle); const x2 = 100 + 100 * Math.cos(endAngle); const y2 = 100 + 100 * Math.sin(endAngle); // 计算标签和图片的中点角度(基于实际扇区角度) const midAngle = (startAngle + endAngle) / 2; const labelX = 100 + 65 * Math.cos(midAngle); const labelY = 100 + 65 * Math.sin(midAngle); const imageX = 100 + 40 * Math.cos(midAngle); const imageY = 100 + 40 * Math.sin(midAngle); // 累计角度 += 扇区角度 + 间距角度 cumulativeAngle += sliceAngle + spacing; // 判断是否需要使用大弧标志(角度>180度时为1) const isLargeArc = sliceAngle > 180 ? 1 : 0; return ( <g key={index} className="transition-transform duration-300 ease-in-out hover:scale-105 relative hover:z-10" > <path d={`M 100,100 L ${x1},${y1} A 100,100 0 ${isLargeArc} 1 ${x2},${y2} Z`} fill={item.backgroundColor} /> <image href={item.image.filename} x={imageX - 15} y={imageY - 15} width="30" height="30" /> <text x={labelX} y={labelY} fill="white" fontSize="7px" textAnchor="middle" alignmentBaseline="middle" > <tspan>{firstWord}</tspan> {secondWord ? <tspan x={labelX} dy="1.2em">{secondWord}</tspan> : null} </text> </g> ); })} <circle cx="100" cy="100" r="30" fill="white" /> <circle cx="100" cy="100" r="29" fill="none" stroke="#140965" strokeWidth="3" /> </svg> </div> ); } export default AnimatedPieBlok;
关键说明
- 所有角度计算直接基于
sliceAngle(扣除总间距后的均匀角度),确保每个扇区大小一致 - 路径的大弧标志由实际扇区角度决定,避免逻辑错误
- 累计角度严格按照「扇区角度+间距角度」累加,保证扇区和间距的均匀分布
- 移除了冗余的
calculatedSlices映射,简化代码逻辑
内容的提问来源于stack exchange,提问作者Doolan
相关产品推荐
相关产品推荐

