SVG交互式饼图分段视觉分隔求助:实现圆心到边缘描边
SVG交互式饼图分段:圆心到外边缘描边分隔方案
针对你需要实现仅从圆心延伸至圆形外边缘的描边分隔需求,提供两种直接可行的实现方案:
方案一:利用stroke-dasharray控制现有路径描边
通过计算路径各段长度,配置stroke-dasharray只显示分段的径向描边(从外边缘到内边缘的线段),隐藏圆弧部分的描边,从而模拟圆心到边缘的分隔线效果。
修改后的核心代码(segment函数)
function segment(i, segments, size, radius, width, className, fillColor) { const center = size/2 const degrees = 360 / segments const start = degrees * i const end = (degrees * (i + 1) + 1) const path = segmentPath(center, center, radius, radius-width, start, end); const el = document.createElementNS( 'http://www.w3.org/2000/svg', 'path' ); el.setAttribute('d', path); el.classList.add( 'path-segment', className ); el.style.fill = fillColor; // 计算路径各段长度,配置stroke-dasharray const arcLength = Math.PI * radius * (degrees / 180); // 外圆弧长度 const innerArcLength = Math.PI * (radius - width) * (degrees / 180); // 内圆弧长度 const radialLength = width; // 径向线段长度 const totalPathLength = arcLength + radialLength + innerArcLength + radialLength; // 仅显示从外到内的径向描边,隐藏其他部分 el.setAttribute('stroke-dasharray', `${radialLength}, ${totalPathLength - radialLength}`); // 偏移到径向线段的起始位置 el.setAttribute('stroke-dashoffset', `${arcLength}`); return el; }
方案二:添加独立的圆心到边缘分隔线
直接在每个分段的<g>容器中插入<line>元素,绘制从圆心到外边缘的直线,这种方式更直观,无需复杂的路径计算。
修改后的render函数(添加分隔线部分)
function render() { const circleDiv = document.getElementById( 'interactive-circle-svg' ); if ( null !== circleDiv ) { const svgSize = 580 const segments = 6 const segmentWidth = 60 const outerFill = '#FB8D00' const middleFill = '#E68E36' const innerFill = '#F9B46F' circleDiv.style.height = svgSize + 'px'; circleDiv.style.width = svgSize + 'px'; circleDiv.style.transform = `rotate(${ 360/2 }deg)` circleDiv.style.transformOrigin = '50% 50%' const svgns = 'http://www.w3.org/2000/svg'; const svg = document.createElementNS( svgns, 'svg' ) svg.setAttribute( 'viewbox', `0 0 ${svgSize} ${svgSize}` ); svg.setAttribute( 'overflow', 'visible' ); svg.setAttribute( 'stroke', '#000' ); svg.setAttribute( 'stroke-width', 3 ); for ( let i = 0; i < segments; i++ ) { let n = i + 1, radius1 = svgSize/2, radius2 = svgSize/2-segmentWidth+1, radius3 = svgSize/2-(segmentWidth*2)+2, g1 = document.createElementNS( svgns, 'g' ); g1.classList.add( 'segment', `segment-${n}` ); g1.setAttribute( 'data-segment-number', n ); g1.appendChild( segment( i, segments, svgSize, radius1, segmentWidth, 'outer', outerFill ) ); g1.appendChild( segment( i, segments, svgSize, radius2, segmentWidth, 'middle', middleFill ) ); g1.appendChild( segment( i, segments, svgSize, radius3, segmentWidth, 'inner', innerFill ) ); // 添加圆心到外边缘的分隔线 const center = svgSize/2; const degrees = 360 / segments; // 取分段的结束角度作为分隔线角度 const lineAngle = degrees * (i + 1); const [xEnd, yEnd] = polarToCartesian(center, center, svgSize/2, lineAngle); const separatorLine = document.createElementNS(svgns, 'line'); separatorLine.setAttribute('x1', center); separatorLine.setAttribute('y1', center); separatorLine.setAttribute('x2', xEnd); separatorLine.setAttribute('y2', yEnd); // 可自定义分隔线样式 separatorLine.setAttribute('stroke', '#000'); separatorLine.setAttribute('stroke-width', 3); g1.appendChild(separatorLine); svg.appendChild( g1 ); g1.addEventListener( 'click', function(e){ const segNumber = this.getAttribute( 'data-segment-number' ); const bodies = document.getElementsByClassName( 'segment-body' ) for ( let i = 0; i < bodies.length; i++ ) { if ( ! bodies[i].classList.contains( 'hide' ) ) { bodies[i].classList.add( 'hide' ); } } const body = document.getElementById( 'segment-body-' + segNumber); body.classList.remove( 'hide' ); }); } circleDiv.append( svg ); } }
完整代码
JavaScript
/** * Polar to Cartesian. */ function polarToCartesian(x, y, r, degrees) { const radians = degrees * Math.PI / 180.0; return [x + (r * Math.cos(radians)), y + (r * Math.sin(radians))] } /** * Segment Path. */ function segmentPath(x, y, r0, r1, d0, d1) { const arc = Math.abs(d0 - d1) > 180 ? 1 : 0 const point = (radius, degree) => polarToCartesian(x, y, radius, degree) .map(n => n.toPrecision(5)) .join(',') return [ `M${point(r0, d0)}`, `A${r0},${r0},0,${arc},1,${point(r0, d1)}`, `L${point(r1, d1)}`, `A${r1},${r1},0,${arc},0,${point(r1, d0)}`, 'Z', ].join('') } /** * Segment Function. */ function segment(i, segments, size, radius, width, className, fillColor) { const center = size/2 const degrees = 360 / segments const start = degrees * i const end = (degrees * (i + 1) + 1) const path = segmentPath(center, center, radius, radius-width, start, end); const el = document.createElementNS( 'http://www.w3.org/2000/svg', 'path' ); el.setAttribute('d', path); el.classList.add( 'path-segment', className ); el.style.fill = fillColor; // 方案一:配置stroke-dasharray(若使用方案二可注释此段) const arcLength = Math.PI * radius * (degrees / 180); const innerArcLength = Math.PI * (radius - width) * (degrees / 180); const radialLength = width; const totalPathLength = arcLength + radialLength + innerArcLength + radialLength; el.setAttribute('stroke-dasharray', `${radialLength}, ${totalPathLength - radialLength}`); el.setAttribute('stroke-dashoffset', `${arcLength}`); return el; } /** * Render. */ function render() { const circleDiv = document.getElementById( 'interactive-circle-svg' ); if ( null !== circleDiv ) { const svgSize = 580 const segments = 6 const segmentWidth = 60 const outerFill = '#FB8D00' const middleFill = '#E68E36' const innerFill = '#F9B46F' circleDiv.style.height = svgSize + 'px'; circleDiv.style.width = svgSize + 'px'; circleDiv.style.transform = `rotate(${ 360/2 }deg)` circleDiv.style.transformOrigin = '50% 50%' const svgns = 'http://www.w3.org/2000/svg'; const svg = document.createElementNS( svgns, 'svg' ) svg.setAttribute( 'viewbox', `0 0 ${svgSize} ${svgSize}` ); svg.setAttribute( 'overflow', 'visible' ); svg.setAttribute( 'stroke', '#000' ); svg.setAttribute( 'stroke-width', 3 ); for ( let i = 0; i < segments; i++ ) { let n = i + 1, radius1 = svgSize/2, radius2 = svgSize/2-segmentWidth+1, radius3 = svgSize/2-(segmentWidth*2)+2, g1 = document.createElementNS( svgns, 'g' ); g1.classList.add( 'segment', `segment-${n}` ); g1.setAttribute( 'data-segment-number', n ); g1.appendChild( segment( i, segments, svgSize, radius1, segmentWidth, 'outer', outerFill ) ); g1.appendChild( segment( i, segments, svgSize, radius2, segmentWidth, 'middle', middleFill ) ); g1.appendChild( segment( i, segments, svgSize, radius3, segmentWidth, 'inner', innerFill ) ); // 方案二:添加独立分隔线(若使用方案一可注释此段) // const center = svgSize/2; // const degrees = 360 / segments; // const lineAngle = degrees * (i + 1); // const [xEnd, yEnd] = polarToCartesian(center, center, svgSize/2, lineAngle); // const separatorLine = document.createElementNS(svgns, 'line'); // separatorLine.setAttribute('x1', center); // separatorLine.setAttribute('y1', center); // separatorLine.setAttribute('x2', xEnd); // separatorLine.setAttribute('y2', yEnd); // separatorLine.setAttribute('stroke', '#000'); // separatorLine.setAttribute('stroke-width', 3); // g1.appendChild(separatorLine); svg.appendChild( g1 ); g1.addEventListener( 'click', function(e){ const segNumber = this.getAttribute( 'data-segment-number' ); const bodies = document.getElementsByClassName( 'segment-body' ) for ( let i = 0; i < bodies.length; i++ ) { if ( ! bodies[i].classList.contains( 'hide' ) ) { bodies[i].classList.add( 'hide' ); } } const body = document.getElementById( 'segment-body-' + segNumber); body.classList.remove( 'hide' ); }); } circleDiv.append( svg ); } } window.onload = render;
HTML
<div id="interactive-circle-svg"></div>
方案选择建议
- 若希望减少DOM元素数量,选择方案一,通过描边属性控制显示效果;
- 若追求直观易维护,选择方案二,直接添加独立线条,样式调整更灵活。
内容的提问来源于stack exchange,提问作者Matthew Ediger
相关产品推荐
相关产品推荐

