Search code examples
htmlcsssvgfrontendstroke-dasharray

How can I draw a pie chart only using stroke-dasharray, not stroke-dashoffset


I am trying to draw a pie chart only using stroke-dasharray and other things like rotate and translate, I am not allowed to use stroke-dashoffset since it is not supported by wkhtmltopdf 0.12.5. I have tried to do something similar to the code below

<svg height="20%" width="20%" viewBox="0 0 20 20" style="border:1px solid gray; ">
  <circle r="10" cx="10" cy="10" fill="white" />
  <circle r="5" cx="10" cy="10" fill="bisque"
          stroke="tomato"
          stroke-width="10"
          stroke-dasharray="10.99 31.4"
          transform="rotate(-90) translate(-20)"/>
</svg>

Where 31.4 is the circumference of the circle and 10.99 is 35% of the circumference. This is drawing a slice representing 35% of the pie. How can I draw more slices (for example one respresenting 40% and another for 13%) after this one without using stroke-dashoffset, I could not figure this out. Thanks a lot for the help guys.


Solution

  • Creating pie charts that way is not really recommended. By "that way", I am referring to making circles where the stroke width matches the radius of the circle. To be precise, the stroke width is double the circle radius.

    Some browsers (or browser versions) and rendering libraries have had bugs rendering circles of that form. The recommended way would be to create a path for each pie chart segment.

    However, assuming you want to continue with this method, then here is what you need to know.

    • Stroke patterns on <circle> elements are rendered starting at 3 o'clock, and proceed clockwise around the circle.

      That's why you have the rotate(-90) in your example above. The -90 rotation is rotating the circle -90deg so that the stroke starts at the top (12 o'clock).

    • The two numbers in the dash pattern are <length of dash> <length of gap>. The pattern then repeats.

    Okay. Let's update your SVG to add the extra segments you requested.

    Firstly, I would suggest a couple of changes:

    1. Move the rotate(-90) to a parent group so that you don't have to worry about it when calculating the rotations for your new slices.
    2. You'll find it a lot easier if you use the version of rotate() that takes a centre of rotaion: rotate(angle, centreX, centreY).
    3. We need to add the fill colour (fill="bisque") to a separate circle. Otherwise the fill of each new segment you add will overlap the previous segments.

    So our new starting point is this:

    <svg height="20%" width="20%" viewBox="0 0 20 20" style="border:1px solid gray; ">
      <circle r="5" cx="10" cy="10" fill="bisque" />
      <g transform="rotate(-90, 10,10)" fill="none" stroke-width="10">
        <circle r="5" cx="10" cy="10"
                stroke="tomato"
                stroke-dasharray="10.99 31.4"/>
      </g>
    </svg>

    Add a 40% segment

    The stroke length you need will be 40% of 31.4 = 12.56.

    To rotate it so that it starts at the end of the first segment, you'll need to rotate it by an angle equal to (10.99 / 31.4) * 360deg = 126deg.

    <svg height="20%" width="20%" viewBox="0 0 20 20" style="border:1px solid gray; ">
      <circle r="5" cx="10" cy="10" fill="bisque" />
      <g transform="rotate(-90, 10,10)" fill="none" stroke-width="10">
        <circle r="5" cx="10" cy="10"
                stroke="tomato"
                stroke-dasharray="10.99 31.4"/>
        <circle r="5" cx="10" cy="10"
                stroke="goldenrod"
                stroke-dasharray="12.56 31.4"
                transform="rotate(126, 10,10)"/>
      </g>
    </svg>

    Add a 13% segment

    The stroke length you need will be 13% of 31.4 = 4.082.

    To rotate it so that it starts at the end of the previous segment, you'll need to sum the lengths of the first two segments, and convert that to an angle.

    ((10.99 + 12.56) / 31.4) * 360deg = 0.75 * 360 = 270deg`.
    

    <svg height="20%" width="20%" viewBox="0 0 20 20" style="border:1px solid gray; ">
      <circle r="5" cx="10" cy="10" fill="bisque" />
      <g transform="rotate(-90, 10,10)" fill="none" stroke-width="10">
        <circle r="5" cx="10" cy="10"
                stroke="tomato"
                stroke-dasharray="10.99 31.4"/>
        <circle r="5" cx="10" cy="10"
                stroke="goldenrod"
                stroke-dasharray="12.56 31.4"
                transform="rotate(126, 10,10)"/>
        <circle r="5" cx="10" cy="10"
                stroke="cornflowerblue"
                stroke-dasharray="4.082 31.4"
                transform="rotate(270, 10,10)"/>
      </g>
    </svg>