csssvgpathstrokestroke-dasharray

Dashes doesn't start full at the beginning in circle svg


I want to create a dashed progress circle for grades. I want to make the length of dashes function of the grade max. (Ex : if the grade is 3.5/5, the dashes will be 24px long).

The problem is that the first dash (the one on top right) is not at the wanted length.

<svg xmlns="http://www.w3.org/2000/svg" [attr.viewBox]="_getViewBox()">

  <!-- The grey circle plain for background-->
  <path
  #background
  fill="none"
  [style.stroke-width]="stroke"
  [style.stroke]="'#EAEAEA'"
  [attr.transform]="getPathTransform()"/>

  <!-- The blue circle plain for progress -->
  <path
  #path
  fill="none"
  [style.stroke-width]="stroke"
  [style.stroke]="resolveColor(color)"
  [attr.transform]="getPathTransform()"/>

  <!-- The white dashed circle for delimitation of dashes -->
  <path
  #dash
  fill="none"
  [style.stroke-width]="stroke"
  [style.stroke]="'#ffffff'"
  stroke-dasharray="4.3,23.7"
  [attr.transform]="getPathTransform()"/>
</svg>

note that if i keep this value on the last path > stroke-dasharray="4.3,23.7" i get this : good progress circle

But it's just because i came accross the good value to put here for a radius of 70. Because if i want to change it (to make it function of the max grade for exemple) i have this :

wrong image

What i want is to have a coherent value for the number of dashes and for the length of dashes, and i'm a little lost, i don't know what to do


Solution

  • In this example I use a mask to create the stroke-dasharray. The dasharray is calculated based on the max-score (total). There is always a gap of 5 units between dashes (out of a total/pathLength of 1000).

    The mask is then applied to two circles. The gray circle is always a full circle and the length of the DarkTurquoise circle depends on the score.

    Both circles are rotated -90 deg, so that the score 0 is at 12 o click.

    const c1 = document.getElementById('c1');
    const c2 = document.getElementById('c2');
    const t1 = document.getElementById('t1');
    
    const update = (total, score) => {
      c1.setAttribute('stroke-dasharray', `${(1000-total*5)/total} 5`);
      c2.setAttribute('stroke-dasharray', `${1000/total*score} 1000`);
      t1.textContent = `${score}/${total}`;
    };
    
    document.forms.form01.total.addEventListener('change', e => {
      let total = parseInt(e.target.value);
      let score = parseInt(e.target.form.score.value);
      e.target.form.score.setAttribute('max', total);
      if (score >= total) score = total;
      update(total, score);
    });
    
    document.forms.form01.score.addEventListener('change', e => {
      let total = parseInt(e.target.form.total.value);
      let score = parseInt(e.target.value);
      update(total, score);
    });
    body {
      display: flex;
    }
    
    form {
      display: flex;
      flex-direction: column;
    }
    <form name="form01">
      <label>Total: <input name="total" type="range" min="5" max="30" value="10"/></label>
      <label>Score: <input name="score" type="range" min="0" max="10" value="5"/></label>
    </form>
    <svg viewBox="0 0 100 100" width="250" xmlns="http://www.w3.org/2000/svg">
      <defs>
        <mask id="mask01">
          <circle id="c1" cx="50" cy="50" r="40" fill="none" stroke="white" stroke-width="10" stroke-dasharray="95 5" pathLength="1000"/>
        </mask>
      </defs>
      <g transform="rotate(-90 50 50)">
      <circle mask="url(#mask01)" cx="50" cy="50" r="40" fill="none" stroke="Gainsboro" stroke-width="10" />
      <circle id="c2" mask="url(#mask01)" cx="50" cy="50" r="40" fill="none" stroke="DarkTurquoise" stroke-width="10" stroke-dasharray="500 1000" pathLength="1000" />
      </g>
      <text id="t1" x="50" y="50" dominant-baseline="middle" text-anchor="middle" font-family="sans-serif" fill="DarkTurquoise">5/10</text>
    </svg>