-1

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.

asimovwasright
  • 838
  • 1
  • 11
  • 28
  • SO is not the correct place to write articles. Your question as stated is too broad and lack focus. – Temani Afif Mar 04 '21 at 10:30
  • @TemaniAfif : https://stackoverflow.blog/2011/07/01/its-ok-to-ask-and-answer-your-own-questions/ – asimovwasright Mar 04 '21 at 10:32
  • How is it too broad? It describes the answer to a specific requirement. – asimovwasright Mar 04 '21 at 10:32
  • Please reopen this question/answer - The two previous 'duplicate' questions are not relevant: The first one is so poorly asked that you can't find it (at least I didn't and neither did SO when I wrote the question) The second one is also closed - because it was too poorly asked! – asimovwasright Mar 04 '21 at 10:35
  • I have added more duplicate and there is even more (cannot add more than 5) – Temani Afif Mar 04 '21 at 10:39
  • I honestly searched for days on SO and other platforms and couldn't find any of these answers. Probably because the wording is slightly different. I'll post my solution on them. – asimovwasright Mar 04 '21 at 12:55
  • I am not saying you didn't search. Searching is not always easy that's why he have duplicates. (if you are going to post your answer in the duplicate do it only in one place, don't copy it over all the duplicates) – Temani Afif Mar 04 '21 at 13:20
  • Fair enough, I did exactly that - and now they all got deleted :-( I give up.. I was just trying to help people... – asimovwasright Mar 04 '21 at 15:03

1 Answers1

2

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 :-)

asimovwasright
  • 838
  • 1
  • 11
  • 28
  • There are quite a few resources available explaining why the order of coordinate transformations is important, e.g. https://www.c-sharpcorner.com/article/significance-of-transformation-order/ or just https://mathbitsnotebook.com/Algebra2/FunctionGraphs/FGCompositeTransformations.html etc. – luk2302 Mar 04 '21 at 10:21
  • Thanks @luk2302 - that's possibly the most obscure resource - thanks for sharing! – asimovwasright Mar 04 '21 at 10:31