skip to Main Content

I’m trying to make the element shown in the image below:

Showing a partial circle radius a gradient

It is based on the Apple Watch widget for Air quality.

The circle is not fully round and the gradient changes across the line. Besides that, there is a ‘point’ that shows what the actual value is (I would like to set this with JavaScript).

I tried to make the half circle using dasharray and dashoffset. But the gradient I cannot get to cross the full line. I also cannot get the gap of the circle on the bottom without using transform: rotate on the object.

#Layer_1 {
   transform: rotate(135deg);
}
.ring {
   stroke-dasharray:314;
   stroke-dashoffset: 78;
   stroke-linecap: round;
}
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" width="500px" height="500px" viewBox="0 0 120 120" enable-background="new 0 0 120 120" xml:space="preserve">

   <circle class="ring" fill="none" stroke="url(#linear)" stroke-width="10" stroke-miterlimit="10" cx="50%" cy="50%" r="50"/>

   <defs>
       <linearGradient id="linear" x1="0%" y1="0%" x2="100%" y2="0%">
           <stop offset="0%" stop-color="#05a"></stop>
           <stop offset="50%" stop-color="#a55"></stop>
           <stop offset="100%" stop-color="#0a5"></stop>
       </linearGradient>
   </defs>
</svg>

2

Answers


  1. While SVG content does not support conical gradients directly, you can combine a HTML element with a background-image: conic-gradient() and a SVG-defined mask that cuts out the arc and the position marker.

    For the arc you can either use your technique with a circle and a stroke-dasharray. I find it easier to draw a path with an appropriate arc. The pathLength attribute will help in positioning along its length, but is not strictly needed for my solution.

    There are multiple ways to achieve a progress marker, here I am going with a fake <animateMotion> animating a ring along the path that does not really move. The position is set as the keyPoints="x;x" attribute, with x a number between 0 and 1, to be set with Javascript.

    const marker = document.querySelector('#progress animateMotion');
    
    function position (x) {
      marker.setAttribute('keyPoints', `${x};${x}`);
    }
    
    position(0.2);
    #progress #arc {
      fill: none;
      stroke: white;
      stroke-width: 10;
      stroke-linecap: round;
    }
    #progress circle {
      fill: none;
      stroke: black;
      stroke-width: 4;
    }
    
    #meter {
      position: relative;
      width: 100px;
      height: 100px;
      background-image: conic-gradient(yellow, green, blue, red, orange, yellow);
      mask: url(#progress);
    }
    <svg width="0" height="0">
      <mask id="progress" width="100" height="100">
        <path id="arc" d="M 25,85 A 40 40 0 1 1 75,85" pathLength="100" />
        <circle r="7">
          <animateMotion path="M 25,85 A 40 40 0 1 1 75,85"
                         keyTimes="0;1" begin="0s" fill="freeze"
                         keyPoints="0;0" />
        </circle>
      </mask>
    </svg>
    <div id="meter"></div>
    Login or Signup to reply.
  2. You are on the right track when it comes to the stroke dash array. In this example the circle and the focus point is made using a mask. The mask is applied to a group containing four different lines that in all makes the circular gradient. I know, the question is not so much about that gradient, but it kind of gets important now that SVG only has linear and radial gradients.

    document.forms.form01.range.addEventListener('change', e => {
      document.getElementById('point').setAttribute('transform', `rotate(${e.target.value})`);
    });
    body{
      background-color: #303030;
    }
    <form name="form01">
      <input type="range" step="10" name="range" min="0" max="270" value="30">
    </form>
    <svg xmlns="http://www.w3.org/2000/svg" width="200" viewBox="0 0 100 100">
      <defs>
        <linearGradient id="lg1" x1="0" x2="0" y1="55" y2="0" gradientUnits="userSpaceOnUse">
          <stop offset="20%" stop-color="Gold"/>
          <stop offset="90%" stop-color="SpringGreen"/>
        </linearGradient>
        <linearGradient id="lg2" x1="0" x2="0" y1="55" y2="0" gradientUnits="userSpaceOnUse">
          <stop offset="20%" stop-color="DarkOrange"/>
          <stop offset="90%" stop-color="Gold"/>
        </linearGradient>
        <linearGradient id="lg3" x1="0" x2="0" y1="55" y2="0" gradientUnits="userSpaceOnUse">
          <stop offset="20%" stop-color="red"/>
          <stop offset="90%" stop-color="DarkOrange"/>
        </linearGradient>
        <linearGradient id="lg4" x1="0" x2="0" y1="55" y2="0" gradientUnits="userSpaceOnUse">
          <stop offset="20%" stop-color="DarkOrchid"/>
          <stop offset="90%" stop-color="red"/>
        </linearGradient>
        <mask id="m1">
          <circle r="45" stroke="white" stroke-width="10" stroke-linecap="round" stroke-dasharray="270 360" pathLength="360" />
          <g id="point" transform="rotate(30)">
            <circle transform="translate(45 0)" r="6" stroke="black" fill="white" stroke-width="3"/>
          </g>
        </mask>
      </defs>
      <g transform="translate(50 50) rotate(135)" stroke-linecap="round" stroke-width="25" mask="url(#m1)">
        <line transform="translate(50 0) rotate(30)" stroke="url(#lg1)" y2="55" />
        <line transform="rotate(70) translate(50 0) rotate(30)" stroke="url(#lg2)" y2="55" />
        <line transform="rotate(140) translate(50 0) rotate(30)" stroke="url(#lg3)" y2="55" />
        <line transform="rotate(210) translate(50 0) rotate(30)" stroke="url(#lg4)" y2="55" />
      </g>
    </svg>
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search