setting line bars on the circumference of a circle

Viewed 94

I'm trying to create the below picture using CSS and javascript.

enter image description here

I'm successfully creating the semi-circle, but I have no idea how I can align the different line bars around the circumference of the circle. I pray to the CSS gods some of you can help me in my quest.

This is the code I have so far

.roll-degrees{
  position: absolute;
  top: 0px;
  z-index: 1;
}

.roll-curve{
  width:400px;
  height:120px;  
  border:solid 3px #000;
  border-color: black transparent transparent transparent;
  border-radius: 50%/120px 120px 0px 0;
}
<div class="roll-degrees">
  <div class="roll-curve"></div>
</div>

Updated based on the answer: Now I'm trying to display the arrow at the bottom of the circle. Code below. Is it possible I need to add/subtract the dimensions of the arrow?

.roll-degrees-arrow{
  --width: 3px;
  --height: 16px;
  position: absolute;
  width: 0; 
  height: 0; 
  border-left: 15px solid transparent;
  border-right: 15px solid transparent;
  border-bottom: 25px solid white;
  z-index: 1;
  --arrow-angle-direction: 1; /* default towards right*/
  top: calc(50% - var(--height));
  left: calc(50% - var(--width) / 2);
  transform:
        rotate(calc(var(--arrow-angle-direction) * var(--arrow-angle)))
        translateY(calc(-1 * var(--circle-radius)))
        ;
}
<div class="roll-degrees-wrapper">
     <div class="roll-degrees">
          <div class="roll-degrees-arrow"></div>
          ...
     </div>
</div>
1 Answers

Initial Setup

First, it's easier to do the math using a proper circle rather than an oval, so let's make the roll-curve element a circle (then we can crop the rest using a container). I've also added an additional wrapper around the whole thing that can be used to cut the size, while the original wrapper (roll-degrees) will be the appropriate size for the full circle in order to make tick positioning easier.

Ticks

Then the approach is to control the exact location of each tick by applying absolute positioning (starting at the circle's center) and moving the tick using transform: translateY(). Center all of the ticks in the middle of the roll-curve circle, rotate each, and translate each out to the perimeter of the circle from there. By rotating first, we can just translate each tick up by the radius of the circle and "up" will be in the appropriate direction.

Marker

As for setting the position of the marker dynamically, you can use largely the same approach as for the ticks (using the same CSS styling). You'll just need a way of translating a value to an angle (which can be done fairly simply---see the example below for details) and an angle direction.

The example below includes a range input, but you can provide a value to the marker any way you like.

Example

const minValue = 0;
const maxValue = 100;

function getAngleForValue(value) {
    const angleRange = 90;
    const angleCenter = angleRange / 2;
    return value / maxValue * angleRange - angleCenter;
}

const marker = document.querySelector("#marker");

function setMarkerPosition(value) {
    const angle = getAngleForValue(value);
    const absAngle = Math.abs(angle);
    const angleDirection = Math.sign(angle);
    marker.style.setProperty("--tick-angle", `${absAngle}deg`);
    marker.style.setProperty("--tick-angle-direction", angleDirection);
}

const markerRangeInput = document.querySelector("#marker-range-input");
markerRangeInput.min = minValue;
markerRangeInput.max = maxValue;
markerRangeInput.value = (maxValue - minValue) / 2;

markerRangeInput.addEventListener("change", (event) => {
    setMarkerPosition(event.target.value)
})
* {
    box-sizing: border-box;
}

body {
    background-color: blue;
}

.roll-degrees-wrapper {
    --circle-radius: 200px;
    height: calc(var(--circle-radius) / 2);
    position: relative;
    overflow: hidden;

    --tick-1-angle: 45deg;
    --tick-2-angle: 30deg;
    --tick-3-angle: 15deg;
    --tick-4-angle: 0deg; /* middle */
}

.roll-degrees {
    box-sizing: content-box;
    --size: calc(var(--circle-radius) * 2);
    width: var(--size);
    height: var(--size);
    padding: 16px;
    position: relative;
}

.roll-curve {
    --size: calc(var(--circle-radius) * 2);
    width: var(--size);
    height: var(--size);
    border: solid 3px #000;
    border-color: transparent;
    border-top-color: white;
    border-radius: 50%;
}

.tick {
    --width: 3px;
    --height: 16px;
    width: var(--width);
    height: var(--height);
    background-color: white;
    position: absolute;
    --v-offset: var(--height);
    --h-offset: var(--width);
    top: calc(50% - var(--v-offset));
    left: calc(50% - var(--h-offset) / 2);
    --tick-angle-direction: 1;
    transform:
        rotate(calc(var(--tick-angle-direction) * var(--tick-angle)))
        translateY(calc(-1 * var(--circle-radius)))
    ;
    transform-origin: bottom;
}

.tick--tall {
    --height: 24px;
}

.tick:nth-child(1),
.tick:nth-child(2),
.tick:nth-child(3) {
    --tick-angle-direction: -1;
}

.tick:nth-child(1),
.tick:nth-child(7) {
    --tick-angle: var(--tick-1-angle);
}

.tick:nth-child(2),
.tick:nth-child(6) {
    --tick-angle: var(--tick-2-angle);
}

.tick:nth-child(3),
.tick:nth-child(5) {
    --tick-angle: var(--tick-3-angle);
}

.tick:nth-child(4) {
    --tick-angle: var(--tick-4-angle);
}

.tick.marker {
    --marker-half-size: 14px;
    --marker-full-size: calc(var(--marker-half-size) * 2);
    --v-offset: var(--marker-half-size);
    --h-offset: var(--marker-full-size);
    --tick-angle: 0deg;
    background-color: transparent;
    width: 0;
    height: 0;
    border: var(--marker-half-size) solid transparent;
    --marker-color: red;
    border-top-color: var(--marker-color);
    border-bottom-color: var(--marker-color);
    transform-origin: center;
}
<div class="roll-degrees-wrapper">
    <div class="roll-degrees">
        <div class="tick tick--tall"></div>
        <div class="tick"></div>
        <div class="tick tick--tall"></div>
        <div class="tick"></div>
        <div class="tick tick--tall"></div>
        <div class="tick"></div>
        <div class="tick tick--tall"></div>
        <div id="marker" class="marker tick"></div>
        <div class="roll-curve"></div>
    </div>
</div>

<input id="marker-range-input" type="range">

Note: You'll probably want to do a bit better styling on the marker or use an icon of some sort, but this serves as a simple example.

Related