Couldn't sleep this night too well, spent couple of night hours...
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>