Creating a wheel of fortune
Tags: svg, animation, charts
Published: 2021-04-27 20:24:09

---
title: Creating a wheel of fortune
published: true
description: Tutorial of how to create a svg wheel of fortune with the rotate functionality
tags: svg, animation, charts
---

Check out the demo here to be excited about what you can build✨✨  https://tacowheel.netlify.app/

A couple of weeks ago, I wanted to explore the magic of SVG✨ so I thought of creating a little sample of a wheel of fortune taking into account SVG collisions using GSAP since it's a big deal in the SVG community, to make it more fun, I consumed an API that returns taco recipes to help you choose what kind of tacos to eat on taco Tuesday 🌮.

So let's jump on this mini tutorial on how to build a wheel of fortune

## The beginning of the project

Since I'm in love with vite, I used [vite create app](https://github.com/vitejs/vite/tree/main/packages/create-app) to start a react app with vite under the hood to make it fast! besides that, I installed gsap, recharts (which I will explain in a minute), and react-uuid (to generate unique ids)

## Creating the wheel

I tried to create the wheel using handcrafted SVG, but I kinda cheat on this part, I saw this [really cool repo](https://github.com/cirocki/Fortune-Wheel) that does kinda what I want and decided to copy its wheel functionality using [recharts](https://recharts.org/en-US/) which is a react library for charts, so here's the code of how I created the wheel (which is a pie chart but don't tell anyone):
```js
//we want fixed width and height to match the arrow position that we will be creating later
<PieChart width={400} height={400} style={{ margin: "0 auto" }}>
    <Pie
        /*this is an array of items with the next structure
        [{
          id: uuid(),
          name: sliceName,
          times: 1, // this is to indicate the value of the slice
          background: randomColorFunction,
        },...]
        */
        data={items}
        labelLine={false}
        label={renderCustomizedLabel}
        fill="#8884d8"
        dataKey="times" // should match the 'times' key
        isAnimationActive={false}
        redraw={false} // to not recalculate the position of the slices after spinning
    >
        {items.map((el, index) => (
        <Cell key={`cell-${index}`} fill={el.background} />
        ))}
    </Pie>
</PieChart>
```

now, the label on it has a function to render the actual text that is positioned inside the slice, we calculate the x and y coordinates to position the text, so that function looks something like this:
```js
const renderCustomizedLabel = ({
    cx,
    cy,
    midAngle,
    innerRadius,
    outerRadius,
    index,
  }) => {
    const radius = innerRadius + (outerRadius - innerRadius) * 0.5;
    const x = cx + radius * Math.cos(-midAngle * RADIAN);
    const y = cy + radius * Math.sin(-midAngle * RADIAN);
    return (
      <text
        x={x}
        y={y}
        textAnchor="middle"
        dominantBaseline="central"
        fontSize={calcSize('fontsize')}
      >
        {name}
      </text>
    );
  };
```

after creating this component you should have something like this:
![wheel image](https://i.imgur.com/4uj01lG.png)

## Making the wheel spin
to make the wheel spin, I use the next function which incorporates the magic of GSAP
```js
let pos = 0;
async function rotate() {
    let x = randomNumber(1080, 1800); //between 4-6 wheel turns
    pos = pos + x;
    await gsap.to(".recharts-pie", // recharts automatically adds this class to the pie chart
     {
      duration: 1,
      rotation: pos,
      transformOrigin: "50% 50%",
      ease: " power2. ease-in-out",
    });
  }
```

## Adding an arrow to identify the winner

Now the fun part begins, after having the wheel spinning, we want to check which slice is the winner, to do that, I added a [little triangle created on css](https://css-tricks.com/the-shapes-of-css/#triangle-left-shape) and then I assign it an id of #arrow to it

then, to check the collision, I modified the rotate function defined before to check for the collision
```js
const [winner, setWinner] = useState(undefined);
async function rotate() {
    let x = randomNumber(1080, 1800); //between 2-4 wheel turns
    pos = pos + x;
    await gsap.to(".recharts-pie", {
      duration: 1,
      rotation: pos,
      transformOrigin: "50% 50%",
      ease: " power2. ease-in-out",
    });
    //get all the pie slices by the classname
    let pieSectors = document.getElementsByClassName("recharts-pie-sector");
    Array.from(pieSectors).forEach((sector, index) => {
        // iterate through them to check which one is hitting the arrow
      if (Draggable.hitTest(sector, "#arrow", '50px')) {
        setWinner(items[index]);
      }
    });
  }
```
One thing to notice is that the last param of the hitTest function is a threshold which you can indicate how many pixels can be overlapping between the two components to count it as a hit

## Takeaways of this project

you can check the source code in [here](https://github.com/eagleera/wheel-of-taco) and a little demo of the app in [here](https://tacowheel.netlify.app/) I added some media queries so the wheel can be playable on all the sizes and a button to call the rotate function

Also, some notes:
- the smaller the wheel, the harder it is to check for collisions since a lot of pie slices will overlap with each other
- if you start adding more slices, you will have the same overlapping problem
- right now the demo has like a 95% accuracy

Sources
- https://greensock.com/gsap/
- https://github.com/cirocki/Fortune-Wheel
- https://css-tricks.com/the-shapes-of-css/#triangle-left-shape
- https://taco-randomizer.herokuapp.com/