skip to Main Content

https://jsfiddle.net/p4d57e8x/3/

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`;
};

This is the code for the path itself, and all of the paths they create a circle.

I am not sure how to get the rotation angle for each path in the circle (say there is red, green, blue, purple), I’ve tried many things but this seems more of a math problem more than anything else.

I tried a bunch of random code I found, this for example

const getRotationAngle = (start, end) => {
    const startAngle = start * 360;
    const endAngle = end * 360; 
    return (startAngle + endAngle) / 2;
};
``` but it doesn't work of course because it's just gibberish for me and I have no idea what I even need to calculate to get the angle.

2

Answers


  1. Your JSFiddle references a 44 KB React solution; there is a 999 Bytes Web Component for it:

    I wrote a Dev.to post for it

    <script src="https://pie-meister.github.io/PieMeister.min.js"></script>
    
    <pie-chart stroke-width="120" pull="-150" stroke="blue,purple,red,green">
        <style>
          text {
            font-size: 4em;
            text-anchor: middle;
            fill:beige
          }
        </style>
        <slice size="100"></slice>
        <slice size="200"></slice>
        <slice size="300"></slice>
        <slice size="150"></slice>
      </pie-chart>
    Login or Signup to reply.
  2. You current script works actually pretty fine. I guess the main confusion comes from the fact you start drawing pie chart segments at "12 o’clock" – which would actually be -90 degree when using CSS transforms.

    Here’s an example where I place labels at the middle of each segment.
    The "rotationAngle" variable gets an offset like so.

    const rotationAngle = (startAngleDegrees + endAngleDegrees) / 2 - 90;
    
    const innerRadius = 10;
    const outerRadius = 50;
    const values = [0.125, 0.25, 0.5];
    renderSegments(values)
    
    function 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 largeArc = end - start >= 0.5;
      const outerFlags = largeArc ? "1 1 1" : "0 0 1";
      const innerFlags = largeArc ? "1 1 0" : "1 0 0";
      //L ${x2},${y2}
      const path = `M ${x2},${y2}
            A ${outerRadius} ${outerRadius} ${outerFlags} ${x3} ${y3} 
            L ${x4},${y4} 
            A ${innerRadius} ${innerRadius} ${innerFlags} ${x1} ${y1} 
            Z`;
      const startAngleDegrees = start * 360;
      const endAngleDegrees = end * 360;
    
      // Midpoint angle of the segment
      const rotationAngle = (startAngleDegrees + endAngleDegrees) / 2 - 90;
      return { path, rotationAngle };
    }
    
    function renderSegments(values) {
      const ns = "http://www.w3.org/2000/svg";
      let start = 0;
      values.forEach((val) => {
        let end = start + val;
        let { path, rotationAngle } = getArcPath(
          start,
          end,
          innerRadius,
          outerRadius
        );
        //create segment
        const segPath = document.createElementNS(ns, "path");
        segPath.setAttribute("d", path);
        segments.append(segPath);
    
        // calculate mid point
        let ptM = getPointOnCircle(
          (outerRadius + innerRadius) / 2,
          0,
          0,
          rotationAngle
        );
    
        //add text label
        let label = document.createElementNS(ns, "text");
        label.setAttribute("text-anchor", "middle");
        label.setAttribute("dominant-baseline", "middle");
        label.setAttribute("x", ptM.x);
        label.setAttribute("y", ptM.y);
        label.textContent = val;
        labels.append(label);
    
        // set new start
        start += val;
      });
    }
    
    
    
    function getPointOnCircle(r, cx, cy, deg) {
      let { cos, sin, PI } = Math;
      let rad = (deg * PI) / 180;
    
      return {
        x: cx + r * cos(rad),
        y: cy + r * sin(rad)
      };
    }
    svg{
      overflow:visible;
      margin:5em;
      border: 1px solid #ccc
    }
    
    path{
      stroke:#fff;
      fill:#ccc;
    }
    
    text{
      font-size:8px;
      font-family:sans-serif
    }
    <svg id="donut" viewBox="0 0 100 100">
      <g id="segments" transform="translate(50, 50)"></g>
      <g id="labels" transform="translate(50, 50)"></g>
    </svg>

    BTW. we can get rid of the first L segment

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