You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.28 02:22:34