4

I'm working on an SVG gauge meter here I want to divide the semi-circle into parts when the range slider move on active stroke fill with gradient color. And want to add one more black color running track when the meter needle moves. I had tried using stroke-dasharray but after adding this all colors are coming at a time meter is not working properly.

  1. I want to divide the semi-circle into parts when the range slider move on active stroke fill with gradient color
  2. And I want to add one more black color running track when the meter needle moves.

My code here

/* set radius for all circles */
var r = 400;
var circles = document.querySelectorAll('.circle');
var total_circles = circles.length;
for (var i = 0; i < total_circles; i++) {
  circles[i].setAttribute('r', r);
}
/* set meter's wrapper dimension */
var meter_dimension = (r * 2) + 100;
var wrapper = document.querySelector('#wrapper');
wrapper.style.width = meter_dimension + 'px';
wrapper.style.height = meter_dimension + 'px';
/* add strokes to circles  */
var cf = 2 * Math.PI * r;
var semi_cf = cf / 2;
var semi_cf_1by3 = semi_cf / 3;
var semi_cf_2by3 = semi_cf_1by3 * 2;

document.querySelector('#outline_curves').setAttribute('stroke-dasharray', semi_cf + ',' + cf);
document.querySelector('#high').setAttribute('stroke-dasharray', semi_cf + ',' + cf);
document.querySelector('#avg').setAttribute('stroke-dasharray', semi_cf_2by3 + ',' + cf);
document.querySelector('#low').setAttribute('stroke-dasharray', semi_cf_1by3 + ',' + cf);
document.querySelector('#outline_ends').setAttribute('stroke-dasharray', 2 + ',' + (semi_cf - 2));
document.querySelector('#mask').setAttribute('stroke-dasharray', semi_cf + ',' + cf);
/*bind range slider event*/
var slider = document.querySelector('#slider');
var lbl = document.querySelector("#lbl");
var mask = document.querySelector('#mask');
var meter_needle = document.querySelector('#meter_needle');

function range_change_event() {
  var percent = slider.value
  var meter_value = semi_cf - ((percent * semi_cf) / 100);
  mask.setAttribute('stroke-dasharray', meter_value + ',' + cf);
  meter_needle.style.transform = 'rotate(' + (450 - ((percent * 180) / 100)) + 'deg)';
  lbl.textContent = percent + '%';
}
slider.addEventListener('input', range_change_event);
#wrapper {
  position: relative;
  margin: auto;
}

#meter {
  width: 100%;
  height: 100%;
  transform: rotate(180deg);
}

.circle {
  fill: none;
}

.outline,
#mask {
  stroke: #F1F1F1;
  stroke-width: 65;
}

.range {
  stroke-width: 60;
}

#slider,
#lbl {
  position: absolute;
}

#slider {
  position: absolute;
  transform: rotate(180deg);
  cursor: pointer;
  left: 0;
  margin: auto;
  right: 0;
  top: 58%;
  width: 94%;
}

#lbl {
  background-color: #4B4C51;
  border-radius: 2px;
  color: white;
  font-family: 'courier new';
  font-size: 15pt;
  font-weight: bold;
  padding: 4px 4px 2px 4px;
  right: -48px;
  top: 57%;
}

#meter_needle {
  height: 40%;
  left: 0;
  margin: auto;
  position: absolute;
  right: 0;
  top: 10%;
  transform-origin: bottom center;
  /*orientation fix*/
  transform: rotate(450deg);
  width: 5px;
  background-color: red;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>

<body>
  <div id="wrapper">
    <svg id="meter">
        <circle id="outline_curves" class="circle outline"  cx="50%" cy="50%">
        </circle>
        <circle id="high" class="circle range" cx="50%" cy="50%" stroke="#FDE47F">
        </circle>
        <circle id="avg" class="circle range" cx="50%" cy="50%" stroke="#7CCCE5">
        </circle>
        <circle id="low" class="circle range" cx="50%" cy="50%" stroke="#E04644">
        </circle>
    
        <circle id="mask" class="circle" cx="50%" cy="50%" >
        </circle>
        <circle id="outline_ends" class="circle outline" cx="50%" cy="50%">
        </circle>
    </svg>
    <svg version="1.1" id="meter_needle" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" width="16.721px" height="93.834px" viewBox="0 0 16.721 93.834" enable-background="new 0 0 16.721 93.834" xml:space="preserve">
            <path fill="#464646" d="M13.886,84.243L2.83,83.875c0,0,3.648-70.77,3.956-74.981C7.104,4.562,7.832,0,8.528,0
                c0.695,0,1.752,4.268,2.053,8.894C10.883,13.521,13.886,84.243,13.886,84.243z"/>
            <path fill="#464646" d="M16.721,85.475c0,4.615-3.743,8.359-8.36,8.359S0,90.09,0,85.475c0-4.62,3.743-8.363,8.36-8.363
                S16.721,80.855,16.721,85.475z"/>
            <circle fill="#EEEEEE" cx="8.426" cy="85.471" r="2.691"/>
</svg>
    <input id="slider" type="range" min="25" max="100" step="25" value="0" />
    <label id="lbl" id="value" for="">0%</label>
  </div>
  <script src="script.js"></script>
</body>

Can anyone suggest to me how to achieve this output. Any help will be appreciate.

<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.2.1/jquery.min.js"></script>

<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="65px" height="51.333px" viewBox="0 0 65 51.333" enable-background="new 0 0 65 51.333" xml:space="preserve">
<g>
    <defs>
        <rect id="SVGID_1_" x="-157.417" y="-176.417" width="648" height="864"/>
    </defs>
    <clipPath id="SVGID_2_">
        <use xlink:href="#SVGID_1_"  overflow="visible"/>
    </clipPath>
    <g clip-path="url(#SVGID_2_)">
        <defs>
            <rect id="SVGID_3_" x="-157.417" y="-176.417" width="648" height="864"/>
        </defs>
        <clipPath id="SVGID_4_">
            <use xlink:href="#SVGID_3_"  overflow="visible"/>
        </clipPath>
    </g>
    <g clip-path="url(#SVGID_2_)">
        <defs>
            <rect id="SVGID_5_" x="-157.417" y="-176.417" width="648" height="864"/>
        </defs>
        <clipPath id="SVGID_6_">
            <use xlink:href="#SVGID_5_"  overflow="visible"/>
        </clipPath>
    </g>
    <g clip-path="url(#SVGID_2_)">
        <defs>
            <rect id="SVGID_7_" x="-157.417" y="-176.417" width="648" height="864"/>
        </defs>
        <clipPath id="SVGID_8_">
            <use xlink:href="#SVGID_7_"  overflow="visible"/>
        </clipPath>
        <path clip-path="url(#SVGID_8_)" fill="#FFFFFF" d="M19.527,44.274c-1.653,5.015-7.059,7.738-12.071,6.085
            c-5.015-1.654-7.739-7.058-6.087-12.071c1.652-5.015,7.058-7.739,12.072-6.084C18.456,33.855,21.179,39.26,19.527,44.274"/>
        <path clip-path="url(#SVGID_8_)" fill="#58595B" d="M19.514,44.289c-1.652,5.013-7.056,7.736-12.066,6.083
            c-5.014-1.653-7.736-7.056-6.084-12.067c1.652-5.013,7.055-7.736,12.068-6.083C18.443,33.875,21.166,39.277,19.514,44.289"/>
        <path clip-path="url(#SVGID_8_)" fill="#FFFFFF" d="M17.318,43.564c-1.253,3.801-5.35,5.865-9.15,4.612
            c-3.797-1.253-5.863-5.35-4.609-9.148c1.254-3.8,5.349-5.865,9.148-4.612C16.506,35.67,18.57,39.766,17.318,43.564"/>
        <path clip-path="url(#SVGID_8_)" fill="#58595B" d="M19.266,34.944c2.242,3.113,2.33,7.115,0.57,10.255l45.249-43.24L9.721,31.148
            C13.256,30.475,17.023,31.828,19.266,34.944"/>
        <defs>
            <filter id="Adobe_OpacityMaskFilter" filterUnits="userSpaceOnUse" x="6.52" y="-11.184" width="62.369" height="60.661">
                <feFlood  style="flood-color:white;flood-opacity:1" result="back"/>
                <feBlend  in="SourceGraphic" in2="back" mode="normal"/>
            </filter>
        </defs>
        <mask maskUnits="userSpaceOnUse" x="6.52" y="-11.184" width="62.369" height="60.661" id="SVGID_9_">
            <g filter="url(#Adobe_OpacityMaskFilter)">
                <g enable-background="new    ">
                    <g enable-background="new    ">
                        <g>
                            <defs>
                                <path id="SVGID_10_" d="M17.516,29.352c2.243,0.335,3.028,2.134,3.028,2.134L57.891,6.807L17.516,29.352z"/>
                            </defs>
                            <clipPath id="SVGID_11_" clip-path="url(#SVGID_8_)">
                                <use xlink:href="#SVGID_10_"  overflow="visible"/>
                            </clipPath>
                            <defs>
                                
                                    <filter id="Adobe_OpacityMaskFilter_1_" filterUnits="userSpaceOnUse" x="6.52" y="-11.184" width="62.369" height="60.661">
                                    <feFlood  style="flood-color:white;flood-opacity:1" result="back"/>
                                    <feBlend  in="SourceGraphic" in2="back" mode="normal"/>
                                </filter>
                            </defs>
                            <mask maskUnits="userSpaceOnUse" x="6.52" y="-11.184" width="62.369" height="60.661" id="SVGID_9_">
                                <g filter="url(#Adobe_OpacityMaskFilter_1_)">
                                    <g enable-background="new    ">
                                    </g>
                                </g>
                            </mask>
                            
                                <linearGradient id="SVGID_12_" gradientUnits="userSpaceOnUse" x1="-236.1787" y1="699.8169" x2="-235.1792" y2="699.8169" gradientTransform="matrix(36.5072 -22.3825 22.3825 36.5072 -7021.3823 -30804.627)">
                                <stop  offset="0" style="stop-color:#FFFFFF"/>
                                <stop  offset="0.0466" style="stop-color:#D5D7D8"/>
                                <stop  offset="0.0983" style="stop-color:#B1B3B6"/>
                                <stop  offset="0.1557" style="stop-color:#929497"/>
                                <stop  offset="0.2174" style="stop-color:#797A7D"/>
                                <stop  offset="0.2847" style="stop-color:#626366"/>
                                <stop  offset="0.3595" style="stop-color:#4E4E50"/>
                                <stop  offset="0.445" style="stop-color:#3B3B3C"/>
                                <stop  offset="0.5476" style="stop-color:#202021"/>
                                <stop  offset="0.6848" style="stop-color:#080809"/>
                                <stop  offset="1" style="stop-color:#000000"/>
                            </linearGradient>
                            <polygon clip-path="url(#SVGID_11_)" mask="url(#SVGID_9_)" fill="url(#SVGID_12_)" points="28.547,49.477 68.889,24.744 
                                46.861,-11.184 6.52,13.549                          "/>
                        </g>
                    </g>
                </g>
            </g>
        </mask>
        <g opacity="0.8" clip-path="url(#SVGID_8_)">
            <g>
                <defs>
                    <rect id="SVGID_13_" x="6.52" y="-11.184" width="62.369" height="60.661"/>
                </defs>
                <clipPath id="SVGID_14_">
                    <use xlink:href="#SVGID_13_"  overflow="visible"/>
                </clipPath>
                <g clip-path="url(#SVGID_14_)">
                    <defs>
                        <path id="SVGID_15_" d="M17.516,29.352c2.243,0.335,3.028,2.134,3.028,2.134L57.891,6.807L17.516,29.352z"/>
                    </defs>
                    <clipPath id="SVGID_16_">
                        <use xlink:href="#SVGID_15_"  overflow="visible"/>
                    </clipPath>
                    
                        <linearGradient id="SVGID_17_" gradientUnits="userSpaceOnUse" x1="-236.1787" y1="699.8169" x2="-235.1792" y2="699.8169" gradientTransform="matrix(36.5072 -22.3825 22.3825 36.5072 -7021.3823 -30804.627)">
                        <stop  offset="0" style="stop-color:#FFFFFF"/>
                        <stop  offset="0.1467" style="stop-color:#FFFFFF"/>
                        <stop  offset="1" style="stop-color:#FFFFFF"/>
                    </linearGradient>
                    <polygon clip-path="url(#SVGID_16_)" fill="url(#SVGID_17_)" points="28.547,49.477 68.889,24.744 46.861,-11.184 6.52,13.549 
                                            "/>
                </g>
            </g>
        </g>
        <path clip-path="url(#SVGID_8_)" fill="#58595B" d="M12.709,42.044c-0.412,1.254-1.765,1.936-3.02,1.523
            c-1.253-0.414-1.934-1.765-1.52-3.019c0.412-1.253,1.764-1.936,3.017-1.521C12.44,39.441,13.121,40.792,12.709,42.044"/>
    </g>
</g>
</svg>
enter image description here
Husna
  • 1,286
  • 4
  • 19
  • 48

2 Answers2

7

My approach here is a pure CSS one ,i didn't use SVG and implementing JS code in this case is lot easier.

Output

enter image description here

var black = document.getElementsByClassName('black')[0];
var gradient = document.getElementsByClassName('gradient')[0]
var tick = document.getElementsByClassName('tick')[0];
var m = document.getElementById('m');
m.addEventListener('input', function() {
  black.style.transform = gradient.style.transform = tick.style.transform = "translate(-50%,-50%)rotateZ(-" + (180 / 100) * m.value + "deg)"
});
* {
  margin: 0px;
  padding: 0px;
  font-family: "arial";
}

.gauge_main,
.white,
.black,
.tick,
.gradient,
.chamber,
.meter {
  position: absolute;
  top: 50%;
  left: 50%;
  height: 280px;
  width: 280px;
  transform: translate(-50%, -50%);
  transition: 0.4s;
}

.meter {
  background-color: white;
  height: 281px;
  width: 281px;
  border-radius: 50%;
  clip-path: polygon(0 50%, 100% 50%, 100% 100%, 0 100%);
  z-index: 1000;
}

.tick,
.chamber {
  width: 160px;
  height: 5px;
  background: linear-gradient(to right, white 50%, black 50%);
  z-index: 110;
  transform-origin: 50%;
}

.tick {
  z-index: 1001;
}

.chamber {
  left: 50%;
  background: white;
  width: 280px;
  height: 5px;
  transform-origin: 50%;
  transform: translate(-50%, -50%)rotateZ(calc(var(--i)*-20deg));
  z-index: 20;
}

.white,
.black,
.gradient {
  position: absolute;
  background-color: black;
  border-radius: 50%;
}

.white {
  height: 180px;
  width: 180px;
  background-color: white;
  z-index: 30;
}

.gradient {
  height: 280px;
  width: 280px;
  background: linear-gradient(0deg, rgba(29, 216, 255, 1) 0%, rgba(4, 106, 255, 1) 50%, rgba(208, 212, 255, 1) 50%);
}

.black {
  height: 200px;
  width: 200px;
  background: linear-gradient(to top, black 50%, rgba(208, 212, 255, 1) 50%);
  box-shadow: 0px 0px 0px 5px white;
  z-index: 25;
}

@keyframes load {
  from {
    transform: translate(-50%, -50%)rotateZ(0deg);
  }
  to {
    transform: translate(-50%, -50%)rotateZ(-180deg);
  }
}

input {
  position: absolute;
  top: 50%;
  left: 50%;
  width: 280px;
  transform: translate(-50%, 50%);
}
<div class="gauge_main">
  <div class="gradient"></div>
  <div class="white"></div>
  <div class="black"></div>
  <div class="tick"></div>
  <div style="--i:1" class="chamber"></div>
  <div style="--i:2" class="chamber"></div>
  <div style="--i:3" class="chamber"></div>
  <div style="--i:4" class="chamber"></div>
  <div style="--i:5" class="chamber"></div>
  <div style="--i:6" class="chamber"></div>
  <div style="--i:7" class="chamber"></div>
  <div style="--i:8" class="chamber"></div>
  <div style="--i:9" class="chamber"></div>
  <div class="meter"></div>

</div>
<input type="range" id="m" name="meter" min="0" max="100" value="0">
ac_mmi
  • 2,302
  • 1
  • 5
  • 14
  • By default circle dots will be grey and then fill with color and when I try to increase the guage main width then other things are mashup. – Husna Feb 06 '21 at 06:37
  • I Appreciate your effort. As you can see above circle default color is grey. And complete meter width, height, if I change, will not conflict with the output. I was unable to make a video. – Husna Feb 06 '21 at 07:23
  • @Husna no problem – ac_mmi Feb 06 '21 at 07:24
  • It is good to use my previous code in that i used `stroke-dasharray` to fill circle so you can think of any other alternative of this and then for circle division we can use ``stroke-dasharray`` in css. `The main point is that in js how can we remove stroke-dasharray and use other alternative to fill circle` – Husna Feb 06 '21 at 07:30
  • @Husna i used gradient and gave the gradient-color composition to 50% for each color making it into a semi circle and gave 180deg rotation to the same – ac_mmi Feb 06 '21 at 07:40
  • can you please add smooth animation of the needle in Paul LeBeau's answer. – Husna Feb 14 '21 at 14:46
  • @Husna just add transition property in .needle css i tried it in his answer and it worked – ac_mmi Feb 14 '21 at 14:57
  • Yeah thanks. Before i was trying with keyframes. – Husna Feb 14 '21 at 16:50
  • can you check this meter needle I replaced with SVG then needle direction is not coming properly i had tried like this needleElem.setAttribute("transform", "rotate(" + (357 - ((value * 180) / 100)) + ")"); still not coming. can you please suggest me. needle SVG i updated above. – Husna Feb 15 '21 at 08:07
6

Here's a working SVG example, with a little bit of javascript.

It's not clear from your question what exactly you want the coloured divisions to look like. For now I've just made them solid red. But you can make them look however you'd like by changing the fill of the <rect id="meter-colours" .../>.

let METER_DIVISIONS = 10;
let METER_DIVISIONS_GAP = 4;

// Initialise meter
let meterDivisionsElem = document.getElementById("meter-divisions");
// Calculate size of each meter division
let totalLen = meterDivisionsElem.getAttribute("r") * Math.PI;
let numGaps = METER_DIVISIONS - 1;
let divLen = (totalLen - METER_DIVISIONS_GAP * numGaps) / METER_DIVISIONS;
// Set meter divisions dash pattern
let pat = divLen + ' ' + METER_DIVISIONS_GAP + ' ';
meterDivisionsElem.setAttribute("stroke-dasharray", pat.repeat(numGaps) + ' ' + divLen + ' ' + totalLen);

// The slider form element
let sliderElem = document.getElementById("slider");
// The slider label element
let sliderLabelElem = document.getElementById("lbl");
// The needle element
let needleElem = document.getElementById("needle");
// The black bar element
let blackBarElem = document.getElementById("black-bar");
let blackBarLen = blackBarElem.getAttribute("r") * Math.PI;
// The meter colours element
let meterColoursElem = document.getElementById("meter-colours");

function setMeterTo(value)
{
  // Update slider label
  sliderLabelElem.textContent = value + '%';

  // Update needle position
  let rotation = value * 180 / 100;
  needleElem.setAttribute("transform", "rotate(" + rotation + ")");

  // Update black bar length
  let barLen = value * blackBarLen / 100;
  blackBarElem.setAttribute("stroke-dasharray", barLen + ' ' + (2 * blackBarLen));

  // Update coloured division section of the meter
  meterColoursElem.setAttribute("transform", "rotate(" + rotation + ")");
}

// Add an input change handler to the slider
sliderElem.addEventListener("input", function(evt) {
  setMeterTo(evt.target.value);
});

// Initialise meter to start value
setMeterTo(25);
#wrapper {
  width: 600px;
}

#slider {
  width: 100%;
}

#needle {
  stroke-linecap: round;
}
<div id="wrapper">
  <svg id="meter" viewBox="0 0 200 110">
    <defs>
      <circle id="meter-divisions" r="90" stroke-width="20" stroke-dasharray="282.7"/>
      
      <mask id="meter-divisions-mask">
        <use xlink:href="#meter-divisions" stroke="white"/>
      </mask>
    </defs>
    <g transform="translate(100,100) scale(1,-1)" fill="none">
      <!-- The grey meter divisions -->
      <use xlink:href="#meter-divisions" stroke="lightgrey"/>
      <!-- The inner semicircular black bar -->
      <circle id="black-bar" r="74" stroke="black" stroke-width="4" stroke-dasharray="232.5"/>
      <!-- the needle -->
      <line id="needle" x2="70" y2="0" stroke="black" stroke-width="2" stroke-dasharray="282.7"/>
      <!-- The coloured form of the ,meter divisions that gets rotated into view with the needle.
           It is initially off screen but gets revealed as it rotates. It is masked by using
           a shape matching the gray divisions -->
      <g mask="url(#meter-divisions-mask">
        <rect id="meter-colours" x="-100" y="-100" width="200" height="100" fill="red"/>
      </g>
    </g>
  </svg>

  <input id="slider" type="range" min="25" max="100" step="25" value="25" />
  <label id="lbl" id="value" for="">0%</label>
</div>

Update re new needle image

First let's simplify your needle image:

<svg width="650px" viewBox="0 0 65 51.333">
<g>
  <path fill="#58595B" fill-rule="evenodd" d="M19.514,44.289c-1.652,5.013-7.056,7.736-12.066,6.083c-5.014-1.653-7.736-7.056-6.084-12.067c1.652-5.013,7.055-7.736,12.068-6.083C18.443,33.875,21.166,39.277,19.514,44.289 M17.318,43.564c-1.253,3.801-5.35,5.865-9.15,4.612c-3.797-1.253-5.863-5.35-4.609-9.148c1.254-3.8,5.349-5.865,9.148-4.612C16.506,35.67,18.57,39.766,17.318,43.564 M12.709,42.044c-0.412,1.254-1.765,1.936-3.02,1.523c-1.253-0.414-1.934-1.765-1.52-3.019c0.412-1.253,1.764-1.936,3.017-1.521C12.44,39.441,13.121,40.792,12.709,42.044"/>
  <path fill="#58595B" d="M19.266,34.944c2.242,3.113,2.33,7.115,0.57,10.255l45.249-43.24L9.721,31.148C13.256,30.475,17.023,31.828,19.266,34.944"/>
  <path fill="#FFFFFF" fill-opacity="0.8" d="M17.516,29.352c2.243,0.335,3.028,2.134,3.028,2.134L57.891,6.807L17.516,29.352z"/>
</g>
</svg>

To use this in your meter, we'll need to locate an accurate centre of rotation (the dot in the circle), and re-orient it so that it has zero angle. We may also need to adjust its scale to fit the meter.

By analysis and experimentation, the centre of rotation is at (10.44, 41.3) and the rotation needed to zero out the rotation is 35.8 deg.

Since the meter is centred at (0,0) we need to start by shifting and rotating the new needle by those amounts.

<svg width="650px" viewBox="0 0 65 51.333">
<g transform="rotate(35.8) translate(-10.44, -41.3)">
  <path fill="#58595B" fill-rule="evenodd" d="M19.514,44.289c-1.652,5.013-7.056,7.736-12.066,6.083c-5.014-1.653-7.736-7.056-6.084-12.067c1.652-5.013,7.055-7.736,12.068-6.083C18.443,33.875,21.166,39.277,19.514,44.289 M17.318,43.564c-1.253,3.801-5.35,5.865-9.15,4.612c-3.797-1.253-5.863-5.35-4.609-9.148c1.254-3.8,5.349-5.865,9.148-4.612C16.506,35.67,18.57,39.766,17.318,43.564 M12.709,42.044c-0.412,1.254-1.765,1.936-3.02,1.523c-1.253-0.414-1.934-1.765-1.52-3.019c0.412-1.253,1.764-1.936,3.017-1.521C12.44,39.441,13.121,40.792,12.709,42.044"/>
  <path fill="#58595B" d="M19.266,34.944c2.242,3.113,2.33,7.115,0.57,10.255l45.249-43.24L9.721,31.148C13.256,30.475,17.023,31.828,19.266,34.944"/>
  <path fill="#FFFFFF" fill-opacity="0.8" d="M17.516,29.352c2.243,0.335,3.028,2.134,3.028,2.134L57.891,6.807L17.516,29.352z"/>
</g>
</svg>

The needle is now horizontal, and positioned at (0,0).

We can now integrate it into the previous solution:

let METER_DIVISIONS = 10;
let METER_DIVISIONS_GAP = 4;

// Initialise meter
let meterDivisionsElem = document.getElementById("meter-divisions");
// Calculate size of each meter division
let totalLen = meterDivisionsElem.getAttribute("r") * Math.PI;
let numGaps = METER_DIVISIONS - 1;
let divLen = (totalLen - METER_DIVISIONS_GAP * numGaps) / METER_DIVISIONS;
// Set meter divisions dash pattern
let pat = divLen + ' ' + METER_DIVISIONS_GAP + ' ';
meterDivisionsElem.setAttribute("stroke-dasharray", pat.repeat(numGaps) + ' ' + divLen + ' ' + totalLen);

// The slider form element
let sliderElem = document.getElementById("slider");
// The slider label element
let sliderLabelElem = document.getElementById("lbl");
// The needle element
let needleElem = document.getElementById("needle");
// The black bar element
let blackBarElem = document.getElementById("black-bar");
let blackBarLen = blackBarElem.getAttribute("r") * Math.PI;
// The meter colours element
let meterColoursElem = document.getElementById("meter-colours");

function setMeterTo(value)
{
  // Update slider label
  sliderLabelElem.textContent = value + '%';

  // Update needle position
  let rotation = value * 180 / 100;
  needleElem.setAttribute("transform", "rotate(" + (-rotation) + ")");

  // Update black bar length
  let barLen = value * blackBarLen / 100;
  blackBarElem.setAttribute("stroke-dasharray", barLen + ' ' + (2 * blackBarLen));

  // Update coloured division section of the meter
  meterColoursElem.setAttribute("transform", "rotate(" + rotation + ")");
}

// Add an input change handler to the slider
sliderElem.addEventListener("input", function(evt) {
  setMeterTo(evt.target.value);
});

// Initialise meter to start value
setMeterTo(25);
#wrapper {
  width: 600px;
}

#slider {
  width: 100%;
}

#needle {
  stroke-linecap: round;
}
<div id="wrapper">
  <svg id="meter" viewBox="0 0 200 110">
    <defs>
      <circle id="meter-divisions" r="90" stroke-width="20" stroke-dasharray="282.7"/>
      
      <mask id="meter-divisions-mask">
        <use xlink:href="#meter-divisions" stroke="white"/>
      </mask>
    </defs>
    <g transform="translate(100,100)">
      <g transform="scale(1,-1)" fill="none">
        <!-- The grey meter divisions -->
        <use xlink:href="#meter-divisions" stroke="lightgrey"/>
        <!-- The inner semicircular black bar -->
        <circle id="black-bar" r="74" stroke="black" stroke-width="4" stroke-dasharray="232.5"/>
        <!-- The coloured form of the ,meter divisions that gets rotated into view with the needle.
             It is initially off screen but gets revealed as it rotates. It is masked by using
             a shape matching the gray divisions -->
        <g mask="url(#meter-divisions-mask">
          <rect id="meter-colours" x="-100" y="-100" width="200" height="100" fill="red"/>
        </g>
      </g>
      
      <!-- the needle -->
      <g id="needle">
        <g transform="rotate(35.8) translate(-10.44, -41.3)">
          <path fill="#58595B" fill-rule="evenodd" d="M19.514,44.289c-1.652,5.013-7.056,7.736-12.066,6.083c-5.014-1.653-7.736-7.056-6.084-12.067c1.652-5.013,7.055-7.736,12.068-6.083C18.443,33.875,21.166,39.277,19.514,44.289 M17.318,43.564c-1.253,3.801-5.35,5.865-9.15,4.612c-3.797-1.253-5.863-5.35-4.609-9.148c1.254-3.8,5.349-5.865,9.148-4.612C16.506,35.67,18.57,39.766,17.318,43.564 M12.709,42.044c-0.412,1.254-1.765,1.936-3.02,1.523c-1.253-0.414-1.934-1.765-1.52-3.019c0.412-1.253,1.764-1.936,3.017-1.521C12.44,39.441,13.121,40.792,12.709,42.044"/>
          <path fill="#58595B" d="M19.266,34.944c2.242,3.113,2.33,7.115,0.57,10.255l45.249-43.24L9.721,31.148C13.256,30.475,17.023,31.828,19.266,34.944"/>
          <path fill="#FFFFFF" fill-opacity="0.8" d="M17.516,29.352c2.243,0.335,3.028,2.134,3.028,2.134L57.891,6.807L17.516,29.352z"/>
        </g>
      </g>

    </g>
    
  </svg>

  <input id="slider" type="range" min="25" max="100" step="25" value="25" />
  <label id="lbl" id="value" for="">0%</label>
</div>
Paul LeBeau
  • 97,474
  • 9
  • 154
  • 181
  • Wow This is exactly what I want to achieve I really appreciate your effort. I have a small query in this here default value is 0 i want to make 25 `min="25" max="100" step="25" value="25"` i written value 25 but still needle at 0 only i want default value will be 25 – Husna Feb 07 '21 at 09:54
  • I've updated my answer. Is this what you want? Or do you want the needle to start at the bottom, instead of part-way up? – Paul LeBeau Feb 07 '21 at 11:31
  • Yes this is what I want thanks for the time you gave. – Husna Feb 08 '21 at 05:00
  • Hi paul here meter needle I replaced with SVG then needle direction is not coming properly i had tried like this `needleElem.setAttribute("transform", "rotate(" + (357 - ((value * 180) / 100)) + ")");` still not coming. can you please suggest me. needle SVG i updated above. – Husna Feb 13 '21 at 06:38
  • wow your explanation and your effort to achieve the desired output. – Husna Feb 15 '21 at 12:58
  • how you calculate the center of rotation I made a needle a little bit smaller and moved a little top inside the circle using `scale(0.8)` like this `setAttribute("transform", "rotate(" + rotation + ") scale(0.8)");` than the needle bottom circle does not fix at the same place it starts moving. how can I calculate translate value. – Husna Feb 16 '21 at 16:04
  • This worked fine for me: `needleElem.setAttribute("transform", "rotate(" + (-rotation) + ") scale(0.8)");` – Paul LeBeau Feb 16 '21 at 17:36
  • I updated the image in my question needle will be inside a circle. `scale()` is working fine but the rotation() and translate() position not coming properly :( – Husna Feb 16 '21 at 18:14
  • Sorry, I don't understand. Please create an example somewhere (eg CodePen, JSFiddle etc) so I can see what you mean. – Paul LeBeau Feb 17 '21 at 02:21
  • I have changed the needle position inside the circle then I unable to set accurate rotate() and translate(); values. https://jsfiddle.net/MehrajKhan/164a9k5y/ – Husna Feb 17 '21 at 08:38
  • Here jsfiddle of my code jsfiddle.net/MehrajKhan/164a9k5y – Husna Feb 17 '21 at 11:59
  • You can't change the `transform` in the needle SVG. It has to be what it is so that the needle rotates around (0,0). Instead add it to the `needleElem.setAttribute("transform", ...)`. See: http://jsfiddle.net/vzm2noxc/ – Paul LeBeau Feb 17 '21 at 18:06