skip to Main Content

I’m trying to accomplish this… in this example the green stroke is filled when the people selected (1), and the gray ones are the total of people (6)

a

other example is green stroke is filled (3) and total (6)

enter image description here

what I accomplished by now, it doesn’t fill correctly,

enter image description here

const [personQuantity, setPersonQuantity] = useState(2)
  const [payingFor, setPayingFor] = useState(1)


  useEffect(() => {
    let amountPerPerson = subtotal / personQuantity
    let perPerson = amountPerPerson * payingFor
    setPerPerson(perPerson)

    if (personQuantity >= payingFor && personQuantity > 2) {
      setActivate(true)
    } else {
      setActivate(false)
    }
  }, [personQuantity, payingFor])

  const fillPercentage = (payingFor / personQuantity) * 100

  return (
    <>
       
            <div className="h-20 w-20 ">
              <svg className="circular-chart " viewBox="0 0 36 36">
                <path
                  fill="none"
                  strokeWidth="4"
                  strokeDasharray={`${fillPercentage + 1},1`}
                  className="circle-bg bg-DARK_1 stroke-2 stroke-white"
                  d="M18 2.0845
          a 15.9155 15.9155 0 0 1 0 31.831
          a 15.9155 15.9155 0 0 1 0 -31.831"
                />
                <path
                  strokeWidth="4"
                  strokeLinecap="round"
                  fill="none"
                  className="circle stroke-LIGHT_GREEN_1  transition-opacity delay-150 ease-in-out"
                  strokeDasharray={`${fillPercentage}, 100`}
                  d="M18 2.0845
         a 15.9155 15.9155 0 0 1 0 31.831
          a 15.9155 15.9155 0 0 1 0 -31.831"
                />
              </svg>
            </div>

2

Answers


  1. as stated by enxaneta, you use path of 100 percent, so you should make the math so that the white draws the person’s number separated by a gap percent space and the green fills the number of slots including intermediate spaces.

    So assuming subtotal is the total number of persons and payingFor is the number of persons that should be green, we have :

    let pathSize = 100
    let gapSize = 1
    let percentForOne = (pathSize / subtotal)
    let greenedPercent = (percentForOne * payingFor) - gapSize 
    let notGreenedPercent = (percentForOne * (subtotal - payingFor)) + gapSize 
    

    so for white :

    strokeDasharray={`${percentForOne} - ${gapSize},${gapSize}`}
    

    then for green :

    strokeDasharray={`${greenedPercent},${notGreenedPercent}`}
    

    Please note that it will be incorrect with more than 100 persons because these would imply more than 100 gaps of 1 percent and negative space between them, so you should probably put the gapSize to 0 when subtotal is more than ~40 (esthetic value)

    As a second note, while using a path to approximate a circle do work, I recommend using <circle cx="18" cy="18" r="15.9155" pathlength=100> and in both case you will need to rotate the figure by -90deg to achieve the vertical origin of the drawing transform: rotate(-90deg)

    I did a demo on this codepen (with fixed values)

    Login or Signup to reply.
  2. Authoring react in StackOverflow is painful so I wrote a response in pure HTML / CSS, but hopefully this can help you see how this would work.

    Except for a bit of math, the initial issue is to be able to calculate the total length that you need to base your % calculations on. In this case I’m using a <circle> so it’s trivial (2 * π * r), but if you absolutely need to use a <path> there is a native JS method getTotalLength (https://developer.mozilla.org/en-US/docs/Web/API/SVGGeometryElement/getTotalLength) that can compute it for you.

    // in your case
    const length = path.current.getTotalLength() // 100.0140609741211
    
      /* in my case */
      --radius: 18;
      --length: calc(2 * 3.14 * var(--radius));
    

    Then we use stroke-dasharray that has the syntax "length visible, length hidden".

    For the segmented circle, the length hidden (--gap) is trivial and up to you. The length visible is simply total / segments - gap. For example, in circle with 3 segments, there will also be 3 gaps, so no hidden trick here.

    --one: calc(var(--length) / var(--total));
    stroke-dasharray: calc(var(--one) - var(--gap)), var(--gap)
    

    For the progress circle, the visible length would be total / segments * current (and hidden length would be the rest). However we need to adjust for the gaps if you want it to stop at the same points that the segmented circle stops. Basically -gap for the visible length, and +gap for the hidden length.

    stroke-dasharray: calc(var(--one) * var(--current) - var(--gap)), calc((var(--total) - var(--current)) * var(--one) + var(--gap));
    
    #svg {
      overflow: visible;
      height: 200px;
      transform: rotate(-90deg);
      --radius: 18;
      --length: calc(2 * 3.14 * var(--radius));
      --total: 6;
      --current: 2;
      --one: calc(var(--length) / var(--total));
      --gap: 1;
    }
    
    #segmented {
      stroke-dasharray: calc(var(--one) - var(--gap)), var(--gap);
    }
    
    #progress {
      stroke-dasharray: calc(var(--one) * var(--current) - var(--gap)), calc((var(--total) - var(--current)) * var(--one) + var(--gap));
    }
     <svg id="svg" viewBox="0 0 36 36">
       <circle id="segmented" cx="18" cy="18" r="18" fill="none" strokeWidth="4" stroke="black" />
       <circle id="progress" cx="18" cy="18" r="18" fill="none" strokeWidth="4" stroke="green" />
    </svg>

    And just for fun, here’s a full example with buttons so you can test the accuracy of this solution:

    let total = 6
    let current = 2
    const buttons = document.querySelectorAll('button')
    
    function onChange() {
      svg.style.setProperty('--total', total)
      svg.style.setProperty('--current', current)
    }
    
    buttons.item(0).addEventListener('click', () => {
      total = Math.max(0, total-1)
      onChange()
    })
    buttons.item(1).addEventListener('click', () => {
      total += 1
      onChange()
    })
    buttons.item(2).addEventListener('click', () => {
      current = Math.max(0, current-1)
      onChange()
    })
    buttons.item(3).addEventListener('click', () => {
      current += 1
      onChange()
    })
    #svg {
      overflow: visible;
      height: 200px;
      transform: rotate(-90deg);
      --radius: 18;
      --length: calc(2 * 3.14 * var(--radius));
      --total: 6;
      --current: 2;
      --one: calc(var(--length) / var(--total));
      --gap: 2;
    }
    
    #segmented {
      stroke-dasharray: calc(var(--one) - var(--gap)), var(--gap);
      transition: stroke-dasharray 300ms;
    }
    
    #progress {
      stroke-dasharray: calc(var(--one) * var(--current) - var(--gap)), calc((var(--total) - var(--current)) * var(--one) + var(--gap));
      transition: stroke-dasharray 300ms;
    }
     <svg id="svg" viewBox="0 0 36 36">
       <circle id="segmented" cx="18" cy="18" r="18" fill="none" strokeWidth="4" stroke="black" stroke-linecap="round" />
       <circle id="progress" cx="18" cy="18" r="18" fill="none" strokeWidth="4" stroke="green" stroke-linecap="round" />
    </svg>
    
    <div>
      <button>-</button>
      total
      <button>+</button>
    </div>
    <div>
      <button>-</button>
      current
      <button>+</button>
    </div>
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search