# Gauge's for Dashboard 2.0 made with ui\_template

**URL:** <https://discourse.nodered.org/t/gauges-for-dashboard-2-0-made-with-ui-template/85955>\
**Category:** Dashboard\
**Tags:** dashboard-2\
**Created:** [27 February 2024 13:43 UTC](https://discourse.nodered.org/t/gauges-for-dashboard-2-0-made-with-ui-template/85955 "2024-02-27T13:43:09Z")\
**Posts on this page:** 20\
**Page:** 5

<div class="post-metadata">

**Author:** ![Giamma](https://avatars.discourse-cdn.com/v4/letter/g/e47774/32.png) [@Giamma](https://discourse.nodered.org/u/Giamma)\
**Post date:** [10 March 2024 14:48 UTC](https://discourse.nodered.org/t/gauges-for-dashboard-2-0-made-with-ui-template/85955/81 "2024-03-10T14:48:48Z")

</div>

Hi,  
I realized this dashboard with 3 vertical gauge.  
I modified CSS node to change the size of value but the changes take effect only on the first gauge and not for the other two ....

Where is the problem?

![xxx](https://us1.discourse-cdn.com/flex026/uploads/nodered/original/3X/c/4/c4e007f6ac7fcdca048ecc91bbbe8b3459847d44.png)

---

<div class="post-metadata">

**Author:** ![hotNipi](https://sea2.discourse-cdn.com/flex026/user_avatar/discourse.nodered.org/hotnipi/32/383_2.png) [@hotNipi](https://discourse.nodered.org/u/hotNipi)\
**Post date:** [10 March 2024 15:13 UTC](https://discourse.nodered.org/t/gauges-for-dashboard-2-0-made-with-ui-template/85955/82 "2024-03-10T15:13:08Z")

</div>

Hard to say without seeing how you did it.

---

<div class="post-metadata">

**Author:** ![Giamma](https://avatars.discourse-cdn.com/v4/letter/g/e47774/32.png) [@Giamma](https://discourse.nodered.org/u/Giamma)\
**Post date:** [10 March 2024 16:06 UTC](https://discourse.nodered.org/t/gauges-for-dashboard-2-0-made-with-ui-template/85955/83 "2024-03-10T16:06:05Z")

</div>

I understand ....  
On the top of your CSS node I added this code for the color of text node  
and this is the problem ....

```auto
.my-colored-text-widget.label-white label{
    color:rgb(250, 249, 249);
    font-size:20px;
}
.my-colored-text-widget.label-green label{
    color:rgb(173, 255, 47);
    font-size:20px;
}
.my-colored-text-widget.label-red label{
    color:rgba(255, 0, 0);
    font-size:20px;
}

.my-colored-text-widget.text-green span{
    color:rgb(173, 255, 47);
    font-size:24px;
}
.my-colored-text-widget.text-red span{
    color:rgba(255, 0, 0);
    font-size:24px;
}
.my-colored-text-widget.text-orange span{
    color:rgb(255, 145, 0);
    font-size:24px;
}
.my-colored-text-widget.text-white span{
    color:rgb(250, 249, 249);
    font-size:24px;
}
.my-colored-text-widget.text-grey span {
    color: rgb(219, 213, 213);
    font-size: 20px;
}

```

Without this code I have no problem but I have not the color in text node ....  
How can I do to repair?

---

<div class="post-metadata">

**Author:** ![hotNipi](https://sea2.discourse-cdn.com/flex026/user_avatar/discourse.nodered.org/hotnipi/32/383_2.png) [@hotNipi](https://discourse.nodered.org/u/hotNipi)\
**Post date:** [10 March 2024 16:19 UTC](https://discourse.nodered.org/t/gauges-for-dashboard-2-0-made-with-ui-template/85955/84 "2024-03-10T16:19:58Z")

</div>

I can't understand. Those are not related.

---

<div class="post-metadata">

**Author:** ![hotNipi](https://sea2.discourse-cdn.com/flex026/user_avatar/discourse.nodered.org/hotnipi/32/383_2.png) [@hotNipi](https://discourse.nodered.org/u/hotNipi)\
**Post date:** [11 March 2024 09:55 UTC](https://discourse.nodered.org/t/gauges-for-dashboard-2-0-made-with-ui-template/85955/85 "2024-03-11T09:55:22Z")

</div>

I took majority of your improvements and included.  
One thing (the division calculations) still I didn't cos my version shows a bit more accurate. But I'm not against the logic you have provided.

 ![image](https://us1.discourse-cdn.com/flex026/uploads/nodered/original/3X/a/2/a2624cd1888bd549dbdb9279d10fd65dc809c76a.png)

> **CODE is HERE**
>
> ```auto
> <script>
> export default{
> data(){
> return {
> //define me here
> min:0,
> max:1.4,
> unit:"kg",
> label:"Yet another gauge",
> measurement:"Plums",
> updateLabel:"Last update:",
> divisors:{major:7,minor:4},
> sectors:[{start:0,end:0.4,color:"skyblue"},{start:0.4,end:0.75,color:"green"},{start:0.75,end:1.4,color:"red"}],
> showUpdate:true,
> allowOverflow:true,
> valueDecimalPlaces: 2,
> majorDecimalPlaces:1,
>                 
> //no need to change
> value:0,
> updateTime:0                             
> }
> }
> }
> </script>
> <template>
> <div class="hn-sng">
> <div class="label">{{label}}</div>
> <svg ref="hn-gauge" width="100%" height="100%" viewBox="0 0 100 100">
> <g>
> <path v-for="(item, index) in sectors" :key="index" :ref="'sector-' + index" class="sector"
> stroke-width="5" d="M 10 90 A 47.5 47.5 25 1 1 90 90"></path>
> </g>
> <g>
> <path class="tick-minor" :style="{'stroke-dasharray':minor }" pathLength="492" stroke-width="5" d="M 10 90 A 47.5 47.5 25 1 1 90 90" ></path>
> <path ref="arc" class="tick-major" :style="{'stroke-dasharray':major }" pathLength="246" stroke-width="5" d="M 10 90 A 47.5 47.5 25 1 1 90 90"></path>
> </g>
> <g>
> <text v-for="(item, index) in numbers" :key="index" class="num" text-anchor="middle" y="-37"
> :style="`rotate: ${item.r}deg;`">{{item.n}}</text>
> </g>
> <g>
> <text class="measurement" y="48" x="50%" text-anchor="middle">{{measurement}}</text>
> <text class="unit" y="75" x="50%" text-anchor="middle">{{unit}}</text>
> <text class="value" y="90" x="50%" text-anchor="middle">{{formattedValue}}</text>
> <text v-if="showUpdate" ref="update" class="update" y="100" x="0"
> text-anchor="left">{{formattedUpdate}}</text>
> </g>
> <g ref="o-needle" class="o-needle">
> <path d="M 0,0 -1.5,0 -0.15,-43 0.15,-43 1.5,0 z"></path>
> <circle cx="0" cy="0" r="3"></circle>
> </g>
> </svg>
> </div>
> </template>
> 
> <script>
> export default{       
> methods:{
> getElement: function(name,base){
> if(base){
> return this.$refs[name]
> }
> return this.$refs[name][0]
> },
> validate: function(data){
> let ret                
> if(typeof data !== "number"){
> ret = parseFloat(data)
> if(isNaN(ret)){
> console.log("BAD DATA! gauge id:",this.id,"data:",data)
> return null
> }
> }                    
> else{
> ret = data
> }                
> return ret
> },
> range:function (n, p, r) {           
> if (p.maxIn > p.minIn) {
> n = Math.min(n, p.maxIn)
> n = Math.max(n, p.minIn)
> } else {
> n = Math.min(n, p.minIn)
> n = Math.max(n, p.maxIn)
> }
> if(r){
> return Math.round(((n - p.minIn) / (p.maxIn - p.minIn) * (p.maxOut - p.minOut)) + p.minOut);
> }
> return ((n - p.minIn) / (p.maxIn - p.minIn) * (p.maxOut - p.minOut)) + p.minOut;
> },
> generateNumbers:function(min,max){               
>                
> let nums = []
> let t = (max - min) / this.divisors.major;
> let i = min;
> let ro = -123
> let rp = 246 / this.divisors.major
>                
> for (let e = 0; e < this.divisors.major + 1; ++e){
> if(this.min > this.max){
> nums.unshift({n: i.toFixed(this.majorDecimalPlaces),r: ro + (e * rp) })
> }
> else{
> nums.push({n: i.toFixed(this.majorDecimalPlaces),r: ro + (e * rp) })
> }
>                     
> i = parseFloat(i + t);                   
> }
> return nums 
> },
> sectorData:function(full){  
> 
> let ret = []
> this.sectors.forEach((sector,idx) => {
> let sec = {name:'sector-'+idx,color:sector.color}
> const params = {minIn:this.min, maxIn:this.max, minOut:0, maxOut:full}
> const start = this.range(sector.start,params,false)
> const end = this.range(sector.end,params,false)
> const pos = Math.min(start, end)
> const span = Math.max(start, end) - pos
> sec.css = `0 ${pos} ${span} var(--dash)`
> ret.push(sec)
> })
> return ret
> },
> rotation:function(v){
> const overflow = this.allowOverflow ? (this.max-this.min) * 0.1 : 0
> const maxRotation = this.allowOverflow ? (123 * 1.2) : 123
> const min = this.min - overflow
> const max = this.max + overflow
> const params = {minIn:min, maxIn:max, minOut:-maxRotation, maxOut:maxRotation};
> if (v === null) {
> v = Math.min(min, max)
> }
> return this.range(v,params,false) +'deg'          
> }
> 
> },
> watch: {
> msg: function(){
> if(this.msg.payload != undefined){
> this.value = this.validate(this.msg.payload)
> this.updateTime = Date.now()                    
> this.getElement('o-needle',true).style.rotate = this.rotation(this.value)                    
> }
> }
> },
> computed: {
> formattedValue: function () {
> return this.value !== null ? this.value.toFixed(this.valueDecimalPlaces) : "---"
> },
> formattedUpdate:function (){
> return this.updateLabel+" "+this.time
> },
> time:function(){
> return new Intl.DateTimeFormat('default', {hour: 'numeric', minute: 'numeric',second: 'numeric'}).format(this.updateTime);
> },
> numbers:function(){
> return this.generateNumbers(this.min,this.max)
> },
> major:function(){
> return "2," + ((246 / this.divisors.major)-2)           
> },
> minor:function(){
> let step = 492 / this.divisors.major / this.divisors.minor
> let line = "0," + step + ","
> step --
> for(let i=0;i<this.divisors.minor-1;++i){
> line += "1,"+step+","
> }
> line = line.slice(0, -1);
> 
> return line
> },
> offset:function(){
> return -1
> }
> 
> },
> mounted(){
>            
> const dal = this.getElement('arc',true).getTotalLength()
> const sec = this.sectorData(dal)              
> const gauge = this.getElement('hn-gauge',true)
> gauge.style.setProperty('--dash',dal)
>            
> sec.forEach(s =>{
> const sector = this.getElement(s.name,false)
> sector.style.setProperty("stroke-dasharray",s.css)
> sector.style.setProperty("stroke",s.color)
> })
> this.getElement('o-needle',true).style.rotate = this.rotation(null)
> this.$nextTick(() => {
> this.getElement('o-needle',true).style.transition= "rotate .5s"
> })
>             
> 
> }
> 
> }
> </script>
> <style>
> .hn-sng {
> position: relative;
> }
> 
> .hn-sng .label {
> position: absolute;
> font-size: 1rem;
> color: currentColor;
> text-align: center;
> width: 100%;
> overflow: hidden;
> white-space: nowrap;
> text-overflow: ellipsis;
> }
> 
> .hn-sng .value {
> fill: currentColor;
> font-weigth: 600;
> }
> 
> .hn-sng .unit {
> fill: currentColor;
> font-size: 0.4rem;
> }
> 
> .hn-sng .measurement {
> fill: currentColor;
> font-size: 0.5rem;
> }
> 
> .hn-sng .update {
> fill: currentColor;
> fill-opacity: 0.6;
> font-size: 0.35rem;
> }
> 
> .hn-sng .num {
> transform-origin: center 64%;
> transform: translate(50%, 64%);
> fill: currentColor;
> fill-opacity: 0.6;
> font-size: .35rem;
> }
> 
> .hn-sng .tick-minor {       
> stroke-dashoffset: 0.5;
> fill: none;
> stroke: currentColor;
> stroke-opacity: 0.6;
> }
> 
> .hn-sng .tick-major {
> stroke-dashoffset: 1;
> fill: none;
> stroke: currentColor;
> }
> 
> .hn-sng .sector {
> fill: none;
> stroke: transparent;
> }
> .hn-sng .sector:first-of-type {
> stroke-dashoffset:-0.5;
> }
> 
> .hn-sng .o-needle {
> transform-origin: center 64%;
> transform: translate(50%, 64%);
> transition: unset;
> }
> 
> .hn-sng .o-needle path,
> .hn-sng .o-needle circle {
> fill: currentColor;
> }
> </style>
> 
> ```

---

<div class="post-metadata">

**Author:** ![Colin](https://sea2.discourse-cdn.com/flex026/user_avatar/discourse.nodered.org/colin/32/17040_2.png) [@Colin](https://discourse.nodered.org/u/Colin)\
**Post date:** [11 March 2024 10:26 UTC](https://discourse.nodered.org/t/gauges-for-dashboard-2-0-made-with-ui-template/85955/86 "2024-03-11T10:26:45Z")

</div>

> [@hotNipi](#):
>
> cos my version shows a bit more accurate.

It seems you are right. That should not be the case. I will investigate.

---

<div class="post-metadata">

**Author:** ![hotNipi](https://sea2.discourse-cdn.com/flex026/user_avatar/discourse.nodered.org/hotnipi/32/383_2.png) [@hotNipi](https://discourse.nodered.org/u/hotNipi)\
**Post date:** [11 March 2024 10:29 UTC](https://discourse.nodered.org/t/gauges-for-dashboard-2-0-made-with-ui-template/85955/87 "2024-03-11T10:29:07Z")

</div>

If you can take mine, keep the "magic numbers" of mine but apply your logic of creating divisions, it may come out pretty nice?

---

<div class="post-metadata">

**Author:** ![Colin](https://sea2.discourse-cdn.com/flex026/user_avatar/discourse.nodered.org/colin/32/17040_2.png) [@Colin](https://discourse.nodered.org/u/Colin)\
**Post date:** [11 March 2024 11:13 UTC](https://discourse.nodered.org/t/gauges-for-dashboard-2-0-made-with-ui-template/85955/88 "2024-03-11T11:13:38Z")

</div>

I worked the numbers out from first principles so it should be correct. I need to know what I have done wrong.

---

<div class="post-metadata">

**Author:** ![hotNipi](https://sea2.discourse-cdn.com/flex026/user_avatar/discourse.nodered.org/hotnipi/32/383_2.png) [@hotNipi](https://discourse.nodered.org/u/hotNipi)\
**Post date:** [11 March 2024 11:18 UTC](https://discourse.nodered.org/t/gauges-for-dashboard-2-0-made-with-ui-template/85955/89 "2024-03-11T11:18:21Z")

</div>

See here

```auto
<path class="tick-minor" :style="{'stroke-dasharray':minor }" pathLength="492" stroke-width="5" d="M 10 90 A 47.5 47.5 25 1 1 90 90" ></path>
                <path ref="arc" class="tick-major" :style="{'stroke-dasharray':major }" pathLength="246" stroke-width="5" d="M 10 90 A 47.5 47.5 25 1 1 90 90"></path>

```

I do define pathLength  
For major -\> 246 This sis same as for normal length of needle rotation  
For minor -\> 492 is double of that.  
Those can actually be arbitrary numbers but if to base on available (measureable) rotation, you can think more clear what it is.

So the 123 (half) or 246 are solid base for all calculations.

The choice of 246 is that overall shape of gauge leaves just enough free space for additions above (label) and bellow (updated text). Also the value has more space in between arc legs

---

<div class="post-metadata">

**Author:** ![hotNipi](https://sea2.discourse-cdn.com/flex026/user_avatar/discourse.nodered.org/hotnipi/32/383_2.png) [@hotNipi](https://discourse.nodered.org/u/hotNipi)\
**Post date:** [11 March 2024 12:24 UTC](https://discourse.nodered.org/t/gauges-for-dashboard-2-0-made-with-ui-template/85955/90 "2024-03-11T12:24:04Z")

</div>

Actually - all of that was just a math again...

> **CODE**
>
> ```auto
> <script>
> export default{
> data(){
> return {
> //define me here
> min:0,
> max:1.4,
> unit:"kg",
> label:"Yet another gauge",
> measurement:"Plums",
> updateLabel:"Last update:",               
> majorDivision: 0.2, // number of input units for each (numbered) major division
> minorDivision: 0.05, // number of input units for each minor division
> sectors:[{start:0,end:0.4,color:"skyblue"},{start:0.4,end:0.75,color:"green"},{start:0.75,end:1.4,color:"red"}],
> showUpdate:true,
> allowOverflow:true,
> valueDecimalPlaces: 2,
> majorDecimalPlaces:1,
>                 
> //no need to change
> value:0,
> updateTime:0                             
> }
> }
> }
> </script>
> <template>
> <div class="hn-sng">
> <div class="label">{{label}}</div>
> <svg ref="hn-gauge" width="100%" height="100%" viewBox="0 0 100 100">
> <g>
> <path v-for="(item, index) in sectors" :key="index" :ref="'sector-' + index" class="sector"
> stroke-width="5" d="M 10 90 A 47.5 47.5 25 1 1 90 90"></path>
> </g>
> <g>
> <path class="tick-minor" :style="{'stroke-dasharray':minor }" pathLength="492" stroke-width="5" d="M 10 90 A 47.5 47.5 25 1 1 90 90" ></path>
> <path ref="arc" class="tick-major" :style="{'stroke-dasharray':major }" pathLength="246" stroke-width="5" d="M 10 90 A 47.5 47.5 25 1 1 90 90"></path>
> </g>
> <g>
> <text v-for="(item, index) in numbers" :key="index" class="num" text-anchor="middle" y="-37"
> :style="`rotate: ${item.r}deg;`">{{item.n}}</text>
> </g>
> <g>
> <text class="measurement" y="48" x="50%" text-anchor="middle">{{measurement}}</text>
> <text class="unit" y="75" x="50%" text-anchor="middle">{{unit}}</text>
> <text class="value" y="90" x="50%" text-anchor="middle">{{formattedValue}}</text>
> <text v-if="showUpdate" ref="update" class="update" y="100" x="0"
> text-anchor="left">{{formattedUpdate}}</text>
> </g>
> <g ref="o-needle" class="o-needle">
> <path d="M 0,0 -1.5,0 -0.15,-43 0.15,-43 1.5,0 z"></path>
> <circle cx="0" cy="0" r="3"></circle>
> </g>
> </svg>
> </div>
> </template>
> 
> <script>
> export default{       
> methods:{
> getElement: function(name,base){
> if(base){
> return this.$refs[name]
> }
> return this.$refs[name][0]
> },
> validate: function(data){
> let ret                
> if(typeof data !== "number"){
> ret = parseFloat(data)
> if(isNaN(ret)){
> console.log("BAD DATA! gauge id:",this.id,"data:",data)
> return null
> }
> }                    
> else{
> ret = data
> }                
> return ret
> },
> range:function (n, p, r) {           
> if (p.maxIn > p.minIn) {
> n = Math.min(n, p.maxIn)
> n = Math.max(n, p.minIn)
> } else {
> n = Math.min(n, p.minIn)
> n = Math.max(n, p.maxIn)
> }
> if(r){
> return Math.round(((n - p.minIn) / (p.maxIn - p.minIn) * (p.maxOut - p.minOut)) + p.minOut);
> }
> return ((n - p.minIn) / (p.maxIn - p.minIn) * (p.maxOut - p.minOut)) + p.minOut;
> },
> generateNumbers:function(min,max){               
>                
> let nums = []
> let t = (max - min) / this.divisors.major;
> let i = min;
> let ro = -123
> let rp = 246 / this.divisors.major
>                
> for (let e = 0; e < this.divisors.major + 1; ++e){
> if(this.min > this.max){
> nums.unshift({n: i.toFixed(this.majorDecimalPlaces),r: ro + (e * rp) })
> }
> else{
> nums.push({n: i.toFixed(this.majorDecimalPlaces),r: ro + (e * rp) })
> }
>                     
> i = parseFloat(i + t);                   
> }
> return nums 
> },
> sectorData:function(full){  
> 
> let ret = []
> this.sectors.forEach((sector,idx) => {
> let sec = {name:'sector-'+idx,color:sector.color}
> const params = {minIn:this.min, maxIn:this.max, minOut:0, maxOut:full}
> const start = this.range(sector.start,params,false)
> const end = this.range(sector.end,params,false)
> const pos = Math.min(start, end)
> const span = Math.max(start, end) - pos
> sec.css = `0 ${pos} ${span} var(--dash)`
> ret.push(sec)
> })
> return ret
> },
> rotation:function(v){
> const overflow = this.allowOverflow ? (this.max-this.min) * 0.1 : 0
> const maxRotation = this.allowOverflow ? (123 * 1.2) : 123
> const min = this.min - overflow
> const max = this.max + overflow
> const params = {minIn:min, maxIn:max, minOut:-maxRotation, maxOut:maxRotation};
> if (v === null) {
> v = Math.min(min, max)
> }
> return this.range(v,params,false) +'deg'          
> }
> 
> },
> watch: {
> msg: function(){
> if(this.msg.payload != undefined){
> this.value = this.validate(this.msg.payload)
> this.updateTime = Date.now()                    
> this.getElement('o-needle',true).style.rotate = this.rotation(this.value)                    
> }
> }
> },
> computed: {
> formattedValue: function () {
> return this.value !== null ? this.value.toFixed(this.valueDecimalPlaces) : "---"
> },
> formattedUpdate:function (){
> return this.updateLabel+" "+this.time
> },
> time:function(){
> return new Intl.DateTimeFormat('default', {hour: 'numeric', minute: 'numeric',second: 'numeric'}).format(this.updateTime);
> },
> numbers:function(){
> return this.generateNumbers(this.min,this.max)
> },
> major:function(){
> return "2," + ((246 / this.divisors.major)-2)           
> },
> minor:function(){
> let step = 492 / this.divisors.major / this.divisors.minor
> let line = "0," + step + ","
> step --
> for(let i=0;i<this.divisors.minor-1;++i){
> line += "1,"+step+","
> }
> line = line.slice(0, -1);
> 
> return line
> },
> offset:function(){
> return -1
> },
> divisors:function(){
> let span
> if(this.max > this.min){
> span = this.max - this.min
> }
> else{
> span = this.min - this.max                
> }
> const ma = span / this.majorDivision
> const mi = span / ma / this.minorDivision
> return {major:ma,minor:mi}
> }
> 
> },
> mounted(){
>            
> const dal = this.getElement('arc',true).getTotalLength()
> const sec = this.sectorData(dal)              
> const gauge = this.getElement('hn-gauge',true)
> gauge.style.setProperty('--dash',dal)
>            
> sec.forEach(s =>{
> const sector = this.getElement(s.name,false)
> sector.style.setProperty("stroke-dasharray",s.css)
> sector.style.setProperty("stroke",s.color)
> })
> this.getElement('o-needle',true).style.rotate = this.rotation(null)
> this.$nextTick(() => {
> this.getElement('o-needle',true).style.transition= "rotate .5s"
> })
>             
> 
> }
> 
> }
> </script>
> <style scoped>
> .hn-sng {
> position: relative;
> }
> 
> .hn-sng .label {
> position: absolute;
> font-size: 1rem;
> color: currentColor;
> text-align: center;
> width: 100%;
> overflow: hidden;
> white-space: nowrap;
> text-overflow: ellipsis;
> }
> 
> .hn-sng .value {
> fill: currentColor;
> font-weigth: 600;
> }
> 
> .hn-sng .unit {
> fill: currentColor;
> font-size: 0.4rem;
> }
> 
> .hn-sng .measurement {
> fill: currentColor;
> font-size: 0.5rem;
> }
> 
> .hn-sng .update {
> fill: currentColor;
> fill-opacity: 0.6;
> font-size: 0.35rem;
> }
> 
> .hn-sng .num {
> transform-origin: center 64%;
> transform: translate(50%, 64%);
> fill: currentColor;
> fill-opacity: 0.6;
> font-size: .35rem;
> }
> 
> .hn-sng .tick-minor {       
> stroke-dashoffset: 0.5;
> fill: none;
> stroke: currentColor;
> stroke-opacity: 0.6;
> }
> 
> .hn-sng .tick-major {
> stroke-dashoffset: 1;
> fill: none;
> stroke: currentColor;
> }
> 
> .hn-sng .sector {
> fill: none;
> stroke: transparent;
> }
> .hn-sng .sector:first-of-type {
> stroke-dashoffset:-0.5;
> }
> 
> .hn-sng .o-needle {
> transform-origin: center 64%;
> transform: translate(50%, 64%);
> transition: unset;
> }
> 
> .hn-sng .o-needle path,
> .hn-sng .o-needle circle {
> fill: currentColor;
> }
> </style>
> 
> ```

---

<div class="post-metadata">

**Author:** ![Colin](https://sea2.discourse-cdn.com/flex026/user_avatar/discourse.nodered.org/colin/32/17040_2.png) [@Colin](https://discourse.nodered.org/u/Colin)\
**Post date:** [11 March 2024 15:51 UTC](https://discourse.nodered.org/t/gauges-for-dashboard-2-0-made-with-ui-template/85955/91 "2024-03-11T15:51:52Z")

</div>

> [@hotNipi](#):
>
> I do define pathLength  
> For major -\> 246 This sis same as for normal length of needle rotation  
> For minor -\> 492 is double of that.

In fact it isn't 246, it is 245.28. That is derived from the fact that radius is 47.5 and the end points are 80 apart.

I have been looking at why mine is not accurate. The reason is that the needle is not exactly centred in the arc. The y axis transform at the bottom of the style section should be 64.383% not 64%. Then it lines up correctly.

```auto
    .hn-sng .o-needle{        
        transform-origin: center 64.383%;
        transform: translate(50%, 64.383%);
        transition:.5s;
    }    

```

Like you, I hate all these magic numbers that are so fiddly to work out accurately. I do have some thoughts on how to improve that, but I won't have much time for the next couple of weeks so it will have to wait.

---

<div class="post-metadata">

**Author:** ![Colin](https://sea2.discourse-cdn.com/flex026/user_avatar/discourse.nodered.org/colin/32/17040_2.png) [@Colin](https://discourse.nodered.org/u/Colin)\
**Post date:** [12 March 2024 17:10 UTC](https://discourse.nodered.org/t/gauges-for-dashboard-2-0-made-with-ui-template/85955/92 "2024-03-12T17:10:13Z")

</div>

Also 64.383 should be used in .num, though here the difference is not noticeable.

```auto
    .hn-sng .num{
        transform-origin: center 64%;
        transform: translate(50%, 64%);
        fill:currentColor;
        fill-opacity:0.6;
        font-size:.35rem;
    }

```

---

<div class="post-metadata">

**Author:** ![hotNipi](https://sea2.discourse-cdn.com/flex026/user_avatar/discourse.nodered.org/hotnipi/32/383_2.png) [@hotNipi](https://discourse.nodered.org/u/hotNipi)\
**Post date:** [13 March 2024 08:26 UTC](https://discourse.nodered.org/t/gauges-for-dashboard-2-0-made-with-ui-template/85955/93 "2024-03-13T08:26:43Z")

</div>

Tweaked a bit again, yes the center point for needle was off but for me not that much. Ended with `.15`  
Also eliminated possibility to kill the browser by asking it to divide by zero.

But yeah, overall I don't think it needs to be more precise unless somebody labels it to be Rolex.  
Updated code at original post. [Gauge's for Dashboard 2.0 made with ui\_template - #6 by hotNipi](https://discourse.nodered.org/t/gauges-for-dashboard-2-0-made-with-ui-template/85955/6)

---

<div class="post-metadata">

**Author:** ![Colin](https://sea2.discourse-cdn.com/flex026/user_avatar/discourse.nodered.org/colin/32/17040_2.png) [@Colin](https://discourse.nodered.org/u/Colin)\
**Post date:** [13 March 2024 09:29 UTC](https://discourse.nodered.org/t/gauges-for-dashboard-2-0-made-with-ui-template/85955/94 "2024-03-13T09:29:41Z")

</div>

> [@hotNipi](#):
>
> I don't think it needs to be more precise unless somebody labels it to be Rolex.

Being a mathematician I feel a need to be accurate.

---

<div class="post-metadata">

**Author:** ![hotNipi](https://sea2.discourse-cdn.com/flex026/user_avatar/discourse.nodered.org/hotnipi/32/383_2.png) [@hotNipi](https://discourse.nodered.org/u/hotNipi)\
**Post date:** [13 March 2024 09:47 UTC](https://discourse.nodered.org/t/gauges-for-dashboard-2-0-made-with-ui-template/85955/95 "2024-03-13T09:47:28Z")

</div>

Having a lifetime as family member of mathematician I do feel sometimes as stupid as a chair but still, I can roughly predict how much time I still have in hands. More and more often the word waste takes over.

---

<div class="post-metadata">

**Author:** ![Colin](https://sea2.discourse-cdn.com/flex026/user_avatar/discourse.nodered.org/colin/32/17040_2.png) [@Colin](https://discourse.nodered.org/u/Colin)\
**Post date:** [13 March 2024 11:44 UTC](https://discourse.nodered.org/t/gauges-for-dashboard-2-0-made-with-ui-template/85955/96 "2024-03-13T11:44:52Z")

</div>

I have managed to find time to get rid of the magic numbers. I have done this by configuring the centre point, radius, start angle and end angle for the gauge. As well as getting rid of the magic numbers this makes it trivially easy to adjust the config of the gauge, should one wish to do so. So layouts such as these can easily be achieved without having to calculate lots of stuff.

![image](https://us1.discourse-cdn.com/flex026/uploads/nodered/original/3X/9/7/9745b67c92332c787d1bdd874485b27c4d2d6eb3.png)

> **Click here to see code**
>
> ```auto
> <!-- Gauge Template CDL v1.4.1
> Based on original work by @HotNipi 
> -->
> <script>
> export default{
> data(){
> let data = {
> //define settings here
> // Min and max scale values. Max may be less that min.
> //min: -50,
> //max: 50,
> min:0,
> max:1.4,
> //min:0,
> //max:-1.4,
> majorDivision: 0.2, // number of input units for each (numbered) major division
> minorDivision: 0.05, // number of input units for each minor division
> unit:"°C",
> label:"Hot Water",
> measurement:"Temperature",
> valueDecimalPlaces: 2, // number of decimal places to show in the value display
> majorDecimalPlaces: 1, // number of decimal places to show on the scale
> // Coloured sectors around the scale. Sectors can be in any order and it makes no difference if 
> // start and end are reversed.
> // Any gaps are left at background colour
> sectors:[{start:0,end:0.4,color:"skyblue"},{start:0.4,end:0.75,color:"green"},{start:0.75,end:1.4,color:"red"}],
> //sectors:[{start:0,end:-0.4,color:"skyblue"},{start:-0.4,end:-0.75,color:"green"},{start:-0.75,end:-1.4,color:"red"}],
> 
> // The position and alignment of the gauge inside the 100x100 svg box for the widget can be changed by modifying the settings below
> // The origin of the svg box is the top left hand corner. The bottom right hand corner is 100,100
> // Obviously, if you move the gauge you may have to move the text fields also.
> // Take care with these settings, if you put silly values in the browser showing the dashboard may lock up. If this happens,
> // close the dashboard browser tab (which may take some time as it is locked up).
> arc: {
> cx:50, // the x and y coordinates of the centre of the gauge arc
> cy: 64, 
> radius: 47.5, // the radius of the arc
> startDegrees: -123, // the angle of the start and end points of the arc. Zero is vertically up from the centre
> endDegrees: 123,	// +ve values are clockwise
> },
> 
> //don't change this
> value: null,
> }
> // calculate derived values
> // make sure startDegrees < endDegrees, but the difference is <= 360
> while (data.arc.startDegrees >= data.arc.endDegrees) {
> data.arc.startDegrees -= 360
> }
> while (data.arc.endDegrees - data.arc.startDegrees > 360) {
> data.arc.startDegrees += 360
> }
> const startRadians = data.arc.startDegrees * Math.PI/180
> const endRadians = data.arc.endDegrees * Math.PI/180
> data.arc.startx = data.arc.cx - data.arc.radius * Math.sin(startRadians-Math.PI)
> data.arc.starty = data.arc.cy + data.arc.radius * Math.cos(startRadians-Math.PI)
> data.arc.endx = data.arc.cx + data.arc.radius * Math.sin(Math.PI-endRadians)
> data.arc.endy = data.arc.cy + data.arc.radius * Math.cos(Math.PI-endRadians)
> data.arc.arcLength = 2 * Math.PI * data.arc.radius * (data.arc.endDegrees - data.arc.startDegrees)/360
> 
> // sanity checks - probably there should be more of these
> this.majorDivision = this.majorDivision <= 0 ? 1 : this.majorDivision
> this.minorDivision = this.minorDivision <= 0 ? 1 : this.minorDivision
> //console.log({arc: data.arc})
> return data
> }
> }
> </script>
> 
> <template>
> <div class="hn-sng">
> <div class="label">{{label}}</div>
> <svg ref="hn-gauge" width="100%" height="100%" viewBox="0 0 100 100">
> <g>
> <path v-for="(item, index) in sectors" :key="index" :ref="'sector-' + index" class="sector" stroke-width="5" :d="arcspec" ></path>                
> </g>
> <g>
> <path class="tick-minor" stroke-width="5" :d="arcspec" :style="tickStyle(this.minorDivision, 0.5)"></path>
> <path ref="arc" class="tick-major" stroke-width="5" :d="arcspec" :style="tickStyle(this.majorDivision, 1)"></path>
>                 
> </g>         
> <g>
> <text v-for="(item, index) in numbers" :key="index" class="num" text-anchor="middle" :y="`${10.5-this.arc.radius}`" 
> :style="`rotate: ${item.r}deg; transform-origin: ${this.arc.cx}% ${this.arc.cy}%; transform: translate(${this.arc.cx}%, ${this.arc.cy}%)`">
> {{item.n}}</text>
> </g>
> <g>
> <text class="measurement" y="48" x="50%" text-anchor="middle">{{measurement}}</text>
> <text class="unit" y="75" x="50%" text-anchor="middle">{{unit}}</text>
> <text class="value" y="90" x="50%" text-anchor="middle">{{formattedValue}}</text>
> </g>
> <g ref="o-needle" class="o-needle" v-html="needle">
> </g>
> </svg>
> </div>
> </template>
> 
> <script>
> export default{
> methods:{
> getElement: function(name,base){
> if(base){
> return this.$refs[name]
> }
> return this.$refs[name][0]
> },
> validate: function(data){
> let ret                
> if(typeof data !== "number"){
> ret = parseFloat(data)
> if(isNaN(ret)){
> console.log("BAD DATA! gauge id:",this.id,"data:",data)
> ret = null
> }
> }                    
> else{
> ret = data
> }                
> return ret
> },
> range:function (n, p, r) {
> // clamp n to be within input range
> if (p.maxIn > p.minIn) {
> n = Math.min(n, p.maxIn)
> n = Math.max(n, p.minIn)
> } else {
> n = Math.min(n, p.minIn)
> n = Math.max(n, p.maxIn)
> }
> if(r){
> return Math.round(((n - p.minIn) / (p.maxIn - p.minIn) * (p.maxOut - p.minOut)) + p.minOut);
> }
> return ((n - p.minIn) / (p.maxIn - p.minIn) * (p.maxOut - p.minOut)) + p.minOut;
> },
> generateNumbers:function(min,max,majorDivision){    
> let minDegrees, maxDegrees, startValue
> if (max > min) {
> minDegrees = this.arc.startDegrees
> maxDegrees = this.arc.endDegrees
> startValue = min    
> } else {
> minDegrees = this.arc.endDegrees
> maxDegrees = this.arc.startDegrees
> startValue = max              
> }
> // Calculate number of major divisions, adding on a bit and rounding down in case last one is just off the end
> const numDivs = Math.floor(Math.abs(max-min) / majorDivision + 0.1)
> const degRange = maxDegrees-minDegrees
> const degPerDiv = degRange * majorDivision/Math.abs(max-min)
> let nums = []
> for (let div=0; div<=numDivs; div++) {
> let degrees = div*degPerDiv + minDegrees
> const n = (startValue + div * majorDivision).toFixed(this.majorDecimalPlaces)
> nums.push({r: degrees, n: n})
> }
> return nums 
> },
> sectorData:function(full){               
> let ret = []
> this.sectors.forEach((sector,idx) => {
> let sec = {name:'sector-'+idx,color:sector.color}
> const params = {minIn:this.min, maxIn:this.max, minOut:0, maxOut:full}
> const start = this.range(sector.start,params,false)
> const end = this.range(sector.end,params,false)
> const pos = Math.min(start, end)
> const span = Math.max(start, end) - pos
> sec.css = `0 ${pos} ${span} var(--dash)`
> ret.push(sec)
> })
> return ret
> },
> rotation:function(v){
> // allow pointer to go 10% off ends of scale, but not more than half way to the other end of the scale
> const deltaDeg = this.arc.endDegrees - this.arc.startDegrees
> const gapDeg = 360 - deltaDeg
> const overflowFactor = Math.min(0.1, gapDeg/2/deltaDeg)
> const overflow = (this.max-this.min)*overflowFactor
> const angleOverflow = (deltaDeg)*overflowFactor 
> const min = this.min - overflow
> const max = this.max + overflow
> const minAngle = this.arc.startDegrees - angleOverflow
> const maxAngle = this.arc.endDegrees + angleOverflow
> const params = {minIn:min, maxIn:max, minOut:minAngle, maxOut:maxAngle};
> if (v === null) {
> v = Math.min(min, max)
> }
> return `${this.range(v,params,false)}deg`
> },
> tickStyle: function(division, width) {
> // division is the number of input units per tick
> // width is the width (length?) of the tick in svg units
> 
> // total arc length in svg units
> const arcLength = this.arc.arcLength
> // length in user units
> const range = Math.abs(this.max - this.min)
> const tickPeriod = division/range * arcLength
> // marker is width wide, so gap is tickPeriod-width
> // stroke-dashoffset sets the first tick to half width
> return `stroke-dasharray: ${width} ${tickPeriod-width}; stroke-dashoffset: ${width/2};`
> },
> },
> watch: {
> msg: function(){
> // allow undefined payload through as it will show the invalid data state
> const v = this.validate(this.msg.payload)                   
> // v is null if payload is invalid, this is coped with then it is displayed
> this.value = v
> this.getElement('o-needle',true).style.rotate = this.rotation(this.value)
> }
> },
> computed: {
> needle: function() {
> const cx = this.arc.cx
> const cy = this.arc.cy
> const length = this.arc.radius - 4.5
> return `<path d="M ${cx},${cy} ${cx-1.5},${cy} ${cx-0.15},${cy-length} ${cx+0.15},${cy-length} ${cx+1.5},${cy} z"></path> 
> <circle cx="${this.arc.cx}" cy="${this.arc.cy}" r="3"></circle>`
> },
> arcspec: function() {
> const delta = this.arc.endDegrees - this.arc.startDegrees
> // if more than 180 deg sweep then large-arg-flag should be 1
> const largeArcFlag = delta > 180 ? 1 : 0
> 
> return `M ${this.arc.startx} ${this.arc.starty} A ${this.arc.radius} ${this.arc.radius} 0 ${largeArcFlag} 1 ${this.arc.endx} ${this.arc.endy}`
> },
> formattedValue: function () {
> // Show --- for the value until a valid value is recevied
> return this.value !== null ? this.value.toFixed(this.valueDecimalPlaces) : "---"
> },
> numbers:function(){
> return this.generateNumbers(this.min,this.max,this.majorDivision)
> },
> },
> mounted(){
>            
> const dal = this.getElement('arc',true).getTotalLength()
> const sec = this.sectorData(dal)              
> const gauge = this.getElement('hn-gauge',true)
> gauge.style.setProperty('--dash',dal)
> sec.forEach(s =>{
> const sector = this.getElement(s.name,false)
> sector.style.setProperty("stroke-dasharray",s.css)
> sector.style.setProperty("stroke",s.color)
> })
> // set the needle centre of rotation
> this.getElement('o-needle',true).style["transform-origin"] = `${this.arc.cx}% ${this.arc.cy}%`
> // initialise the needle off the bottom
> this.getElement('o-needle',true).style.rotate = this.rotation(null) 
> }
> 
> }
> </script>
> 
> <style>
> .hn-sng{
> position:relative;
> }
> .hn-sng .label{
> position:absolute;
> font-size:1rem;
> color:currentColor;
> text-align:center;
> width:100%;
> overflow: hidden;
> white-space: nowrap;
> text-overflow: ellipsis;
> }
> .hn-sng .value {
> fill:currentColor;
> }
> .hn-sng .unit {
> fill:currentColor;
> font-size:0.4rem;
> }
> .hn-sng .measurement {
> fill:currentColor;
> font-size:0.5rem;
> }
> .hn-sng .num{
> fill:currentColor;
> fill-opacity:0.6;
> font-size:.35rem;
> }
> .hn-sng .tick-minor{
> fill:none;
> stroke:currentColor;
> stroke-opacity:0.6;
> }
> .hn-sng .tick-major{
> fill:none;
> stroke:currentColor;
> }
> .hn-sng .sector{        
> fill:none;
> stroke:transparent;      
> } 
> .hn-sng .o-needle{        
> transition:.5s;
> }
> .hn-sng .o-needle path, .hn-sng .o-needle circle{
> fill:black;
> }
>     
> </style>
> 
> ```

---

<div class="post-metadata">

**Author:** ![dceejay](https://sea2.discourse-cdn.com/flex026/user_avatar/discourse.nodered.org/dceejay/32/38_2.png) [@dceejay](https://discourse.nodered.org/u/dceejay)\
**Post date:** [13 March 2024 11:45 UTC](https://discourse.nodered.org/t/gauges-for-dashboard-2-0-made-with-ui-template/85955/97 "2024-03-13T11:45:58Z")

</div>

Fantastic work !  
(both of you)

---

<div class="post-metadata">

**Author:** ![hotNipi](https://sea2.discourse-cdn.com/flex026/user_avatar/discourse.nodered.org/hotnipi/32/383_2.png) [@hotNipi](https://discourse.nodered.org/u/hotNipi)\
**Post date:** [13 March 2024 12:02 UTC](https://discourse.nodered.org/t/gauges-for-dashboard-2-0-made-with-ui-template/85955/98 "2024-03-13T12:02:45Z")

</div>

Nice.

For sure - different layouts like quarter or full circle don't use available space wisely thus it maybe requires some additional options to get full out of it but as for most of the hard work is done it's a walk in the park to make it look however you want then ..

---

<div class="post-metadata">

**Author:** ![Colin](https://sea2.discourse-cdn.com/flex026/user_avatar/discourse.nodered.org/colin/32/17040_2.png) [@Colin](https://discourse.nodered.org/u/Colin)\
**Post date:** [13 March 2024 14:08 UTC](https://discourse.nodered.org/t/gauges-for-dashboard-2-0-made-with-ui-template/85955/99 "2024-03-13T14:08:11Z")

</div>

Can CSS `transform-origin` and `translate` only take parameters in pixels, not svg co-ordinates? It seems to me that is the case which does complicate things if one wants to change the size of the viewport.

---

<div class="post-metadata">

**Author:** ![hotNipi](https://sea2.discourse-cdn.com/flex026/user_avatar/discourse.nodered.org/hotnipi/32/383_2.png) [@hotNipi](https://discourse.nodered.org/u/hotNipi)\
**Post date:** [13 March 2024 15:45 UTC](https://discourse.nodered.org/t/gauges-for-dashboard-2-0-made-with-ui-template/85955/100 "2024-03-13T15:45:06Z")

</div>

This maybe can help then: [transform-box - CSS: Cascading Style Sheets | MDN](https://developer.mozilla.org/en-US/docs/Web/CSS/transform-box)

[Previous page](https://discourse.nodered.org/t/gauges-for-dashboard-2-0-made-with-ui-template/85955.md?page=4)

[Next page](https://discourse.nodered.org/t/gauges-for-dashboard-2-0-made-with-ui-template/85955.md?page=6)
