Search code examples
svgsketchapp

How to draw donut chart in sketch app, which generates correct SVG code


I have few issues with the way sketch app is generating donut chart.

This is how am building the donut chart in Sketch app

Please refer to screenshots to follow screenshots to follow

  1. I created a 200px by 200px circle
  2. Empty fill. Stroke of 25 (center position). This will be the empty state circle so I color it as grey
  3. I have duplicated this base circle to create my first segment of 50% (3 in total, 50%, 25%, 25%)
  4. For my first segment of 50%, the length of the "Dash" be 314. It appears the dash is drawn from the 6o'clock position and moves in anti-clockwise direction (this will be more obvious in the other segments). However, I much prefer if it started from 12' o'clock and moved in clockwise direction.
  5. Duplicate circle to create the second segment of 25%, which equals to a "Dash" of 157 and "Gap" of 471.
  6. Due to how circle stroke is being drawn (see point 4), I applied a transformation of 90deg
  7. For the last segment, similar to the second one, with only exception of transforming by 180deg
  8. The generated SVG doesn't represent what was drawn in Sketch Questions:

    • How to ensure the end SVG looks as designed in Sketch app
    • In sketch, how to ensure the stroke starts from 12o'clock position (I guess with transformation?) and is drawn clockwise.

Generated code by Sketch (https://jsfiddle.net/BrightPixels/932w6j9d/)

<?xml version="1.0" encoding="UTF-8"?>
<svg width="226px" height="226px" viewBox="0 0 226 226" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
    <!-- Generator: Sketch 45.2 (43514) - http://www.bohemiancoding.com/sketch -->
    <title>donut-chart</title>
    <desc>Created with Sketch.</desc>
    <defs></defs>
    <g id="Page-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
        <g id="iPad" transform="translate(-236.000000, -213.000000)" stroke-width="25">
            <g id="donut-chart" transform="translate(249.000000, 226.000000)">
                <circle id="base" stroke="#D8D8D8" cx="100" cy="100" r="100"></circle>
                <circle id="segment1" stroke="#FFD900" stroke-dasharray="314,314" cx="100" cy="100" r="100"></circle>
                <circle id="segment2" stroke="#64B445" stroke-dasharray="157,471" transform="translate(100.000000, 100.000000) rotate(90.000000) translate(-100.000000, -100.000000) " cx="100" cy="100" r="100"></circle>
                <circle id="segment3" stroke="#004FB6" stroke-dasharray="157,471" transform="translate(100.000000, 100.000000) rotate(180.000000) translate(-100.000000, -100.000000) " cx="100" cy="100" r="100"></circle>
            </g>
        </g>
    </g>
</svg>  

Solution

  • There are two ways that I would suggest building this, and neither rely on rotate transforms. Transforms (especially when exported from design tools alongside other attributes like stroke-dasharray or masks) can cause issues.

    Approach 1:

    If this is intended to be a static graphic that doesn't need to change, you could simply take this approach in Sketch:

    • Create 3 identical circle layers, with yellow, green, and blue borders.
    • For each circle, use the Scissors tool to remove line segments from the circle.* For example, for the blue layer, you'd use the Scissors tool to remove all but one of its line segments. I also wrote an article about how to use the Scissors tool.
    • Note that this approach doesn't use rotate transforms. It should export easily and identically from Sketch to SVG.

    * A line segment is the path in between any two vector points, so if you wanted part of the circle to end at an arbitrary point (ex: 4 o'clock) where there isn't already a vector point you can just go into Edit mode and click along the path to add a vector point there.

    Approach 2:

    If this is intended to be a dynamic graphic that changes based on real data (like a pie chart), stroke dashes are a good idea but I would suggest doing some of the work directly in the SVG:

    • Start in Sketch by creating that same circle layer (only one).
    • This way of using stroke dashes relies heavily on 1. the start point of the circle, and 2. the path direction of the circle. We can figure those out in Sketch by entering Edit mode on the circle (return key); the selected vector point is the start point, and hitting the tab key will cycle through the other points moving in the direction of the path. Based on Sketch's defaults for a circle, we'll need to make the following adjustments to get a circle that starts at 12 o'clock and moves clockwise:
    • Rotate the circle 180°, then click Flatten in the toolbar (or in the menu bar, via Layer > Combine > Flatten). In addition to removing the rotate transform, this turns it into a custom path instead of a standard circle. It will export to SVG as a <path> element instead of a <circle>.
    • The path direction is currently counter-clockwise, so if you want it to be clockwise you should also go to Layer > Path > Reverse Order to switch the path direction. Now we have exactly the circle path that we want—no need for rotations in the SVG.

    • After exporting to SVG, I suggest copying the code into a CodePen, as I've done here in this demo. To start, move the <path> element into the <defs> area so that you can easily clone it for each of the colored segments.
    • Create 3 <use> elements that reference the <path>, each with their own stroke colors. See my CodePen link if you aren't familiar with how to do this.
    • Add a stroke-dasharray attribute to each of the <use> elements. They should have 2 values: 1. the length of the segment you want to create, and 2. the length of the gap between dashes—which should be equal to or greater than the length of the path itself. In this case the path length is about 628, which can be calculated using the little bit of Javascript I included in that CodePen (open up the console to see the resulting number). In this example, the green and blue circles take up 1/4 of the circle path, so they should have a stroke-dasharray of 157, 628.
    • Because these dashes all start at the beginning of the path, we need to offset them. We don't need to use a rotate transform for this—instead I suggest using stroke-dashoffset which was created for this exact reason. The offset takes one number value: the distance it moves the dashes in the opposite direction as the path. That means we need to use negative values to move in the same direction as the path. To offset the green segment by half of the circle, that attribute should be stroke-dashoffset="-314".
    • The nice thing about this approach is that you could easily use Javascript or CSS calc() to easily set the stroke-dasharray and stroke-dashoffset values for these colorful segments, and update them based on new data.

    Woohoo—dynamic charts!