skip to Main Content

I’m creating a svg circle that has different segments, which will work like tabs for for showing/hiding content. It’s basically a pie chart. I’m having trouble visually separating the segments I’m creating. I need it to stroke just from the center to the outside of the circle. I feel I need to figure out the stroke-dasharray attribute? Would love some help figuring out how to configure things.

My other ideas were to use a margin to offset the segmentPath function but the client wasn’t loving the look of things. Finally had an idea to just add a separate line element to each segment – traveling from the edge of the circle to the center. Any help would be great.

/**
 * Polar to Cartesian.
 * 
 * Håken Lid's SVG circle segments.
 * 
 * @see https://observablehq.com/@haakenlid/svg-circle
 * @see https://blog.logrocket.com/interactive-svg-circle-of-fifths/#arcs-circles
 */
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.
 * 
 * Håken Lid's SVG circle segments.
 * 
 * @see https://observablehq.com/@haakenlid/svg-circle
 * @see https://blog.logrocket.com/interactive-svg-circle-of-fifths/#arcs-circles
 */
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.
 * 
 * Modified version of Håken Lid's SVG circle segments.
 * 
 * @see https://observablehq.com/@haakenlid/svg-circle
 * @see https://blog.logrocket.com/interactive-svg-circle-of-fifths/#arcs-circles
 */
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;

    return el;
}

/**
 * Render.
 * 
 * Function that will render the svg circle.
 */
function render() {
    // Circle Div.
    const circleDiv = document.getElementById( 'interactive-circle-svg' );

    if ( null !== circleDiv ) {

        // Setup environment.
        const svgSize = 580
        const segments = 6
        const segmentWidth = 60
        const outerFill = '#FB8D00'
        const middleFill = '#E68E36'
        const innerFill = '#F9B46F'

        // Set cirlce div attributes.
        circleDiv.style.height = svgSize + 'px';
        circleDiv.style.width = svgSize + 'px';
        circleDiv.style.transform = `rotate(${ 360/2 }deg)`
        circleDiv.style.transformOrigin = '50% 50%'

        // SVG NS.
        const svgns = 'http://www.w3.org/2000/svg';

        // Create the <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 );

        // Attach the children nodes.
        for ( let i = 0; i < segments; i++ ) {
            // Create the first <g>.
            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 );
            
            // Attach segments to the <g>.
            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 ) );


            // Attach the <g> to the <svg>
            svg.appendChild( g1 );

            // Create event handlers with the g1.
            g1.addEventListener( 'click', function(e){
                const segNumber = this.getAttribute( 'data-segment-number' );

                // Hide bodies.
                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' );
                    }
                }

                // Show correct body.
                const body = document.getElementById( 'segment-body-' +  segNumber);
                body.classList.remove( 'hide' );
            });
        }

        // Attach to div.
        circleDiv.append( svg );
    }   
}

// Attach render to onload.
window.onload = render;
<div id="interactive-circle-svg"></div>

2

Answers


  1. Chosen as BEST ANSWER

    /**
     * Polar to Cartesian.
     * 
     * Håken Lid's SVG circle segments.
     * 
     * @see https://observablehq.com/@haakenlid/svg-circle
     * @see https://blog.logrocket.com/interactive-svg-circle-of-fifths/#arcs-circles
     */
    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.
     * 
     * Håken Lid's SVG circle segments.
     * 
     * @see https://observablehq.com/@haakenlid/svg-circle
     * @see https://blog.logrocket.com/interactive-svg-circle-of-fifths/#arcs-circles
     */
    function segmentPath(x, y, r0, r1, d0, d1, i) {
        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.
     * 
     * Modified version of Håken Lid's SVG circle segments.
     * 
     * @see https://observablehq.com/@haakenlid/svg-circle
     * @see https://blog.logrocket.com/interactive-svg-circle-of-fifths/#arcs-circles
     */
    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, i);  
        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;
    
        return el;
    }
    
    /**
     * Render.
     * 
     * Function that will render the svg circle.
     */
    function render() {
        // Circle Div.
        const circleDiv = document.getElementById( 'interactive-circle-svg' );
    
        if ( null !== circleDiv ) {
    
            // Setup environment.
            const svgSize = 580
            const segments = 6
            const segmentWidth = 60
            const strokeWidth = 25
            const strokeColor = '#fff'
            const outerFill = '#FB8D00'
            const middleFill = '#E68E36'
            const innerFill = '#F9B46F'
    
            // Set cirlce div attributes.
            circleDiv.style.height = svgSize + 'px';
            circleDiv.style.width = svgSize + 'px';
            circleDiv.style.transform = `rotate(${ 360/2 }deg)`
            circleDiv.style.transformOrigin = '50% 50%'
    
            // SVG NS.
            const svgns = 'http://www.w3.org/2000/svg';
    
            // Create the <svg>.
            const svg = document.createElementNS( svgns, 'svg' )
            svg.setAttribute( 'viewbox', `0 0 ${svgSize} ${svgSize}` );
            svg.setAttribute( 'overflow', 'visible' );
    
            // Start the lines variables.
            const center = svgSize/2
            const degrees = 360/segments
    
            // Attach the children nodes.
            for ( let i = 0; i < segments; i++ ) {
                // Create the first <g>.
                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 );
                
                // Attach segments to the <g>.
                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 ) );
    
                // Start the interior lines.
                if ( i > 0 ) {
                    let start = degrees * i
                    let [lineX1, lineY1] = polarToCartesian(center, center, radius1, start)
                    let [lineX2, lineY2] = polarToCartesian(center, center, radius3 - segmentWidth, start);
    
                    // Attach the segment line.
                    let segLine = document.createElementNS( svgns, 'line' )
                    segLine.setAttribute( 'stroke', strokeColor );
                    segLine.setAttribute( 'stroke-width', strokeWidth );
                    segLine.setAttribute( 'x1', lineX1  )
                    segLine.setAttribute( 'y1', lineY1 )
                    segLine.setAttribute( 'x2', lineX2 )
                    segLine.setAttribute( 'y2', lineY2 )
                    svg.appendChild( segLine );
                }       
                        
                // Attach the <g> to the <svg>
                svg.appendChild( g1 );
    
                // Create event handlers with the g1.
                g1.addEventListener( 'click', function(e){
                    const segNumber = this.getAttribute( 'data-segment-number' );
    
                    // Hide bodies.
                    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' );
                        }
                    }
    
                    // Show correct body.
                    const body = document.getElementById( 'segment-body-' +  segNumber);
                    body.classList.remove( 'hide' );
                });
            }
    
            // Create and attach line zero.
            const line0 = document.createElementNS( svgns, 'line' );
            const [lineX1, lineY1] = polarToCartesian( center, center, svgSize/2-segmentWidth+1 + segmentWidth, 0 )
            const [lineX2, lineY2] = polarToCartesian( center, center, (svgSize/2-(segmentWidth*2)+2 - segmentWidth), 0 );
    
            line0.setAttribute( 'stroke', strokeColor );
            line0.setAttribute( 'stroke-width', strokeWidth/2 )
            line0.setAttribute( 'x1', lineX1 )
            line0.setAttribute( 'y1', lineY1 + ( ( strokeWidth/2 ) / 2 ) )
            line0.setAttribute( 'x2', lineX2 )
            line0.setAttribute( 'y2', lineY2 + ( ( strokeWidth/2 ) / 2 ) )
            svg.appendChild( line0 );
    
            // Attach to div.
            circleDiv.append( svg );
        }   
    }
    
    // Attach render to onload.
    window.onload = render;
    <div id="interactive-circle-svg"></div>

    My answer ended up being using separate line's to show the difference of the sections. I had to add an extra line at the end to be the first, as there is no z-index in svg-land and there were siblings covering it up.


  2. According to provided code, to implement required functionality, to add line element to each segment from center of circle to edge of circle, this code,

    g1.appendChild( segment( i, segments, svgSize, radius3, segmentWidth, 'inner', innerFill ) );
    

    should be replaced with this code,

    g1.appendChild( segment( i, segments, svgSize, radius3, radius3, 'inner', innerFill ) );
    

    It can be checked, if it works.

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search