0

So I am trying to build this custom component to indicate direction in a form of a pie chart, where the direction is indicated by the chart's slice.

My direction indicator

I am using CSS to achieve this, but I only managed to make the slice static. I need the slice to change angles based on component props (start and end angles).

I am having troubles passing custom css variables to the element and making css conic-gradient function work with the given variables.

I don't know if it matters, but for the context, this is in React project, using MUI library with tailwind.

Below is my component:

(Notice how I set properties of the element by using plain document. Is this wrong? It feels wrong, but I didn't find other way, how to set custom css varaibles.)

import "./DirectionIndicator.css";

type Props = {
    startAngle: number
    endAngle: number
}

export default function DirectionIndicator(props: Props) {

    const element = document?.getElementById("pieSlice")
    if (element) {
        element.style.setProperty("--startAngle", props.startAngle.toString())
        element.style.setProperty("--endAngle", props.endAngle.toString())
    }

    return (
        <div className={"pieContainer"}>
            <div className={"pieBackground"}>
                <div id="pieSlice" className={"slice"}/>
                <div className={"innerCircle"}>
                    <div className={"content"}>
                        <b>N</b>
                    </div>
                </div>
            </div>
        </div>
    )
}

and this is my css:

(Notice in class .slice there are two lines containing conic-gradient function. They are identical, except one is using the --startAngle and --endAngle variables instead of hardcoded values. When I use the line with hardcoded values, css works fine. When I switch for the one with variables, slice doesn't show. I don't understand what's wrong. I tried to remove setting the values in component via JS and use the default values from CSS file, but that doesn't work neither. Indicator remains empty.)

Empty direction indicator

.pieContainer {
    height: var(--containerSize);
    position: relative;
    --containerSize: 100px;
    --innerCircleSize: 70px;
    --directionColor: #590;
    --innerCircleColor: #fff;
}

.pieBackground {
    position: absolute;
    width: var(--containerSize);
    height: var(--containerSize);
    border-radius: 100%;
    box-shadow: 0px 2px 5px rgba(0,0,0,0.5);
}

.slice {
    --startAngle: 0;
    --endAngle: 12;
    transition: all 1000ms;
    width: var(--containerSize);
    height: var(--containerSize);
    border-radius: 100%;
    /*background: conic-gradient(from var(--startAngle)deg, var(--directionColor) var(--endAngle)deg, transparent 1deg);*/
    background: conic-gradient(from 60deg, var(--directionColor) 120deg, transparent 1deg);
}

.innerCircle {
    position: absolute;
    width: var(--innerCircleSize);
    height: var(--innerCircleSize);
    background-color: var(--innerCircleColor);
    border-radius: 100%;
    top:  calc((var(--containerSize) - var(--innerCircleSize)) / 2);
    left:  calc((var(--containerSize) - var(--innerCircleSize)) / 2);
    box-shadow: 2px 5px 8px rgba(0,0,0,0.5) inset;
    color: black;
}

.innerCircle .content {
    position: absolute;
    display: block;
    width: var(--innerCircleSize);
    top: 5px;
    left: 0;
    text-align: center;
    font-size: 12px;
}

I will be very grateful for any tips on how to fix this. Thank you!

1 Answer 1

1

(Notice how I set properties of the element by using plain document. Is this wrong?

It is wrong. You should set style attribute properties directly on the JSX element.

For the CSS, omit the deg suffix from the CSS variable values. When the CSS resolves, there is effectively always spaces surrounding var() functions. Thus the conic-gradient() value would become invalid. Instead, consider having the deg as part of the value in the React code:

function DirectionIndicator(props) {
  const style = {
    '--startAngle': `${props.startAngle.toString()}deg`,
    '--endAngle': `${props.endAngle.toString()}deg`,
  };

  return (
    <div className={"pieContainer"}>
      <div className={"pieBackground"}>
        <div className={"slice"} style={style} />
        <div className={"innerCircle"}>
          <div className={"content"}>
            <b>N</b>
          </div>
        </div>
      </div>
    </div>
  )
}

function App() {
  return <DirectionIndicator startAngle={90} endAngle={135} />
}

ReactDOM.createRoot(document.getElementById('app')).render(<App/>);
.pieContainer {
  height: var(--containerSize);
  position: relative;
  --containerSize: 100px;
  --innerCircleSize: 70px;
  --directionColor: #590;
  --innerCircleColor: #fff;
}

.pieBackground {
  position: absolute;
  width: var(--containerSize);
  height: var(--containerSize);
  border-radius: 100%;
  box-shadow: 0px 2px 5px rgba(0, 0, 0, 0.5);
}

.slice {
  --startAngle: 0;
  --endAngle: 12;
  transition: all 1000ms;
  width: var(--containerSize);
  height: var(--containerSize);
  border-radius: 100%;
  background: conic-gradient(from var(--startAngle), var(--directionColor) var(--endAngle), transparent 1deg);
}

.innerCircle {
  position: absolute;
  width: var(--innerCircleSize);
  height: var(--innerCircleSize);
  background-color: var(--innerCircleColor);
  border-radius: 100%;
  top: calc((var(--containerSize) - var(--innerCircleSize)) / 2);
  left: calc((var(--containerSize) - var(--innerCircleSize)) / 2);
  box-shadow: 2px 5px 8px rgba(0, 0, 0, 0.5) inset;
  color: black;
}

.innerCircle .content {
  position: absolute;
  display: block;
  width: var(--innerCircleSize);
  top: 5px;
  left: 0;
  text-align: center;
  font-size: 12px;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/18.2.0/umd/react.production.min.js" integrity="sha512-8Q6Y9XnTbOE+JNvjBQwJ2H8S+UV4uA6hiRykhdtIyDYZ2TprdNmWOUaKdGzOhyr4dCyk287OejbPvwl7lrfqrQ==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/18.2.0/umd/react-dom.production.min.js" integrity="sha512-MOCpqoRoisCTwJ8vQQiciZv0qcpROCidek3GTFS6KTk2+y7munJIlKCVkFCYY+p3ErYFXCjmFjnfTTRSC1OHWQ==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>

<div id="app"></div>

Sign up to request clarification or add additional context in comments.

2 Comments

This worked! What a quick fix, you are legend. Before accepting the answer though, I am getting type error on the style. Is there way how to fix it? TS2559: Type { '--startAngle': string; '--endAngle': string; } has no properties in common with type Properties<string | number, string & {}> index.d.ts(1942, 9): The expected type comes from property style which is declared here on type DetailedHTMLProps<HTMLAttributes<HTMLDivElement>, HTMLDivElement>

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.