Gauge's for Dashboard 2.0 made with ui_template

Couldn't sleep this night too well, spent couple of night hours...

image

Code is HERE
<script>
    export default{
        data(){
            return {
                //define me here
                min:0,
                max:100,
                label:"Gauge",
                measurement:"Temperature",
                unit:"°C",
                majorDivision:10, // number of input units for each (numbered) major division
                minorDivision: 2, // number of input units for each minor division
                sectors:[{start:0,end:10,color:"skyblue"},{start:70,end:90,color:"#e99200"},{start:90,end:100,color:"red"}],
                valueDecimalPlaces: 1,
                majorDecimalPlaces:0,
                allowOverflow:true, // needle can travel over scale boundaries
                showUpdate:true,
                updateLabel:"Last update:",               
                
                //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.25,-43 0.25,-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 mad = this.majorDivision <= 0 ? 1 : this.majorDivision;
                const mid = this.minorDivision <= 0 ? 1 : this.minorDivision;
                const ma = span / mad
                const mi = span / ma / mid
                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>
    .hn-sng {
        position: relative;
        user-select: none;
    }

    .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.15%;
        transform: translate(50%, 64.15%);
        fill: currentColor;
        fill-opacity: 0.6;
        font-size: .35rem;
    }

    .hn-sng .tick-minor {       
        stroke-dashoffset: 0;
        fill: none;
        stroke: currentColor;
        stroke-opacity: 0.6;
    }

    .hn-sng .tick-major {
        stroke-dashoffset: 0.75;
        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.15%;
        transform: translate(50%, 64.15%);
        transition: unset;
    }

    .hn-sng .o-needle path,
    .hn-sng .o-needle circle {
        fill: currentColor;
    }
</style>
11 Likes