Search code examples
javascripthtmlcssgeometrypie-chart

CSS Circle with fully interactive & accessible pie wedges?


The goal:

How do you create a circle in CSS with accessible, interactive pie wedges, without using a chart library (which often create pie wedges that are not at all interactive or accessible)

What if you need to create buttons as circle wedges?

All answers welcome, but I'm writing this Jeopardy style question to help others with my solution.


Solution

  • Solution:

    I've searched everywhere, and found many solutions - all of which are very superficial, they fill the design aspects but don't give you any usable or accessible elements in the end.

    [tl;dr] Codepen

    In it's simplest form you create a container, and make sure it's overflow is hidden and that it's a perfect circle with border-radius: 50%

    In the markup you can add the inner elements - they can be buttons, divs, a ul/ol with list etc... It doesn't really matter what you put in there, it's the CSS calculations that count. Here's my example HTML

    <div class="pie">
      <button>
        <span class="text">1</span>    
      </button>
      <button>
        <span class="text">2</span>
      </button>
      <button>
        <span class="text">3</span>
      </button>
    </div>
    

    In my example I named the container .pie, here's the important CSS:

    .pie {
      border-radius: 50%;
      height: 150px;
      overflow: hidden;
      position: relative;
      width: 150px;
    }
    

    Height and width obviously only need to match each other, but could be anything.

    Then you give the inner elements CSS to make them all appear initially as first-quarter quadrants of the pie container.

    button {
      bottom: 50%;
      height: 100%;
      left: 50%;
      position: absolute;
      transform-origin: bottom left;
      width: 100%;
    }
    

    You could conceptualize what you have so far as this:

    Quadrant

    The transform-origin probably seems out of place there, but it makes sense later, and is the key to the whole thing...

    The final key to making the wedges is the calculation for transformation of each square into a wedge. This is done by using an ordered combination of:

    • transform: rotate()
    • transform: skeyY()

    To make the calculations we need to know the number of degrees each wedge should take in the circle. Let's say share = 120 which is correct for our example with 3 equal shares, and we need an iterator, let's use i = 0 (I'm sure you can see how this will translate into a dynamic JS function for any number of wedges...)

    Now the calculation is as follows per wedge in order of appearance:

    rotate = (i * share)deg skeyY = (share - 90)deg i++

    Minus 90 because the wedge starts out square

    Basically the rotation turns the wedge on it's bottom left corner (which is the center of the pie) the number of degrees of all the wedges that are before it. The skewY skews the wedge from being a rectangle to being a wedge of the right degrees.

    Then we have to counter the skewY and rotation on the inner element (especially if you want text there), the calculation for that is:

    rotate = (share / 2)deg skewY(-(share - 90)deg

    This will reverse the transformation and rotate the text to appear 45 degrees relative to it's containing 'wedge'.

    Now your markup will look like this:

    <div class="pie">
      <button style="transform: rotate(0deg) skewY(30deg)">
        <span style="transform: skewY(-30deg) rotate(60deg)" class="text">1</span>    
      </button>
      <button style="transform: rotate(120deg) skewY(30deg)">
        <span style="transform: skewY(-30deg) rotate(60deg)" class="text">2</span>
      </button>
      <button style="transform: rotate(240deg) skewY(30deg)">
        <span style="transform: skewY(-30deg) rotate(60deg)"class="text">3</span>
      </button>
    </div>
    

    Fair warning The order or the transform properties is important. Try switching the order and it won't work. I don't have enough time to figure that out, so if anyone here wants to explain that - go for it!

    Here's how it looks in the end, with a bit of extra css so you can see the outcome better.

    .pie {
      background-color: rgba(0,0,0,0.5);
      position: relative;
      width: 150px;
      height: 150px;
      border-radius: 50%;
      overflow: hidden;
    }
    .pie button,
    .pie button:focus {
      outline: none;
      background-color: lightgreen;
      border: thin solid white;
      position: absolute;
      cursor: pointer;
      width: 100%;
      height: 100%;
      left: 50%;
      bottom: 50%;
      transform-origin: bottom left;
      transition: all 200ms ease-out;
    }
    .pie button:hover,
    .pie button:focus:hover {
      box-shadow: 0px 0px 10px 5px #515151;
      z-index: 1;
    }
    .pie button .text,
    .pie button:focus .text {
      position: absolute;
      bottom: 30px;
      padding: 0px;
      color: #333;
      left: 30px;
    }
    <div class="pie">
      <button style="transform: rotate(0deg) skewY(30deg)">
        <span style="transform: skewY(-30deg) rotate(60deg)" class="text">1</span>    
      </button>
      <button style="transform: rotate(120deg) skewY(30deg)">
        <span style="transform: skewY(-30deg) rotate(60deg)" class="text">2</span>
      </button>
      <button style="transform: rotate(240deg) skewY(30deg)">
        <span style="transform: skewY(-30deg) rotate(60deg)"class="text">3</span>
      </button>
    </div>

    And if you're interested in making it dynamic, here's a very simple implementation:

    codepen

    Responsiveness

    Yes - you only need to change the width and height of the pie. Could be a css var, or with a media query...

    @media (min-width: 1920px) {
      .pie {
        height: 350px;
        width: 350px;
      }
    }
    

    Caveats and thoughts...

    The only real caveat is that you are limited to a minimum of three wedges. The maximum is really dependent on the inner content of the wedges as well as the overall size of the 'pie'...

    If you do find you need 1 & 2 wedge support, I wrote some conditions in my own project which I'm happy to share.

    If you're interested, this could most likely be quite easily adapted into a simple pie chart engine - but I don't have the time to figure out the details.

    Hope someone in need finds this, and it helps :-)