Dashboard 2.0 is now Generally Available

I still use one of your contribution guages and tried to adapt to db 2.0, but it is looking funny. will figure out how to rectify, but asking in general, is it possible to adapt this to db 2.0 to have same look and feel as old dashboard ?

in dashboard 2.0 without modification, just copied and pasted the code in the template block.
image

how it looks in old dashboard
image

link to this post..

Should be possible but if just copy paste job....
To fix the layout it requires outer container to have width and height set in pixels. (equal numbers so it's square). Don't know about data handling but shouldn't be too hard... There's already examples to gain ideas how.

Cool! Very nice.
How to make it smaller?

Ho much smaller?

an half
..

About an half smaller quick way....

<template>
    <div class="round-led-level" :style="`--dot-count:${count}; --size:${size}; --ledsize:${ledSize};`" >
        <div class="round-led-level-text" style="font-size:0.75rem; inset:0;">
            <span class="round-led-level-label" >{{label}}</span>            
        </div>
        <div class="round-led-level-stripe">
            <div v-for="(color, index) in colors" :key="index" class="round-led-level-led" :ref="'dot-' + index +'-'+this.id"></div>
        </div>
        <div class="round-led-level-centered-text">            
            <span class="round-led-level-value" style="font-size:inherit;">{{formattedValue}}</span>
            <span class="round-led-level-unit">{{unit}}</span>
        </div>
        <div class="round-led-level-limits">
            <span>{{min}}</span>
            <span>{{max}}</span>
        </div>
    <div>
</template>

<script>
export default {
    data(){
        return {
            //define me here
            label:"MEASURE",
            min:0,
            max:100,
            unit:"cmĀ³",
            size:100,                                       
            animate:true,                        
            dark:0.4,           
            colors:["#4ed34e",
                    "#4ed34e",
                    "#4ed34e",
                    "#4ed34e",                    
                    "#4ed34e",
                    "#4ed34e",
                    "#4ed34e",
                    "#4ed34e",
                    "#4ed34e",
                    "#4ed34e",
                    "#4ed34e",
                    "#ffcf00",
                    "#ffcf00",
                    "#ffcf00",
                    "#ffcf00",
                    "red",
                    "red",
                    "red"],            
            //no need to change those
            value:0,
            previousValue:0,
            inited:false
        }
    },

   
    methods: {        
        getElement: function(name,base){
            if(base){
                return this.$refs[name]
            }
            return this.$refs[name][0]
        },

        full: function(){
            return Math.floor(this.colors.length*this.percentage/100)
        },
        half: function (){
            let p = this.colors.length*this.percentage/100;
            p -= this.full()
            p *= .5
            p += this.dark;
            return p;
        },

        lit: function(){
            if(this.inited == false){
                return
            }
            const down = this.previousValue > this.value

            let time = .01
            this.colors.forEach((e,i) => {
                let dot = this.getElement("dot-"+i+"-"+this.id);
                if(!dot){
                    console.log("no dots found")
                    return
                }                
                if(i<this.full()){
                    dot.style.filter= "brightness(1.1)";
                }
                else if(i==this.full()){                   
                    dot.style.filter = "brightness("+this.half()+")";
                }
                else{
                    dot.style.filter= "brightness("+this.dark+")";
                }
                if(down){
                    time = (this.colors.length - i) * .12                    
                }
                dot.style.transition = this.animate ? "filter "+time+"s" : "unset";
            })
            this.previousValue = this.value
        }
    },
    watch: {
        msg: function(){    
            if(this.msg.payload != undefined){           
                this.value = this.msg.payload                
                this.lit()
            }
        }
    },
    computed: {
        formattedValue: function () {
            return this.value.toFixed(2)
        },
        percentage: function(){
            return Math.floor(((this.value - this.min) / (this.max - this.min)) * 100);
        },
        count: function() {
            return this.colors.length;
        },        
        ledSize:function(){
            return this.size * 0.08;
        }
    },
    mounted(){
        let angle;
        const step = 270 / this.colors.length;
        const radius = this.size / 2.5;
        const s = this.ledSize / -2;        
        this.colors.forEach((c,i) => {
                let dot = this.getElement("dot-"+i+"-"+this.id);
                if(!dot){
                    console.log("no dots found")
                    return
                }
                dot.style.backgroundColor = c               
                dot.style.transition = "filter 0.1s";
                dot.style.setProperty('--dot',i);

                angle = (i*step) * Math.PI / 180;
                dot.style.left = s + radius * Math.cos(angle) + 'px';
                dot.style.top = s + radius * Math.sin(angle) + 'px';
                dot.style.transform = 'translate('+s+'px, '+s+'px)'; 
                dot.style.rotate = (angle - 0.1)+"rad";
                dot.style.boxShadow = "inset 0px 0px 2px 0px #00000099, 0px 0px 1px 0px #00000099";
                dot.style.borderTadius = "2px";

            }
        )
       
        this.inited = true;
    },
        
}
</script>


3 Likes

Nice animation!

1 Like

Just a bit heavy performance wise. As good things usually are...

2 Likes

You might want to check out this web component version that @hotNipi kindly donated.

1 Like

Sorry, showing my ignorance... how is this web component version installed/used in DB2?

Not ignorance at all.

I built an IIFE version, so all you need to do is to add a link to it like any other JS library. It will self-register. You can then add it as a tag <gauge-hotnipi></gauge-hotnipi>.

Looks like I didn't get around to finishing the docs. However, there is extensive instructions in the front of the source code that shows the various attributes you can supply.

And to be clear, it was @hotnipi who did all the hard work in the first place, I just did the component wrapper and build and put it in a repo for people to use.

Whoosh! sorry, still above my pay-grade!
Could you give an example flow?

This way it goes. You serve the js file from your shared folder, the rest is just config.

BUT !!!
It still requires quite of reorganize for CSS. It doesn't have defaults to fallback and many of variable definitions missing and so on.

So not directly usable but if CSS is for you "piece of cake" - you can do it.

<script src="/js/gauge-hotnipi.js"></script>
<template>
    <gauge-hotnipi ref="hotgauge" min="0" max="100" size="180px" platehue="220" digits='{"size":80,"distance":14}'></gauge-hotnipi>
</template>

<script>
    export default {
        methods: {
            getElement: function(name,base){
                if(base){
                    return this.$refs[name][0]                    
                }
                return this.$refs[name]                
            }
        },
        
        watch: {
            msg: function(){
                if(this.msg.payload != undefined){
                    this.getElement("hotgauge").update(this.msg.payload)                    
                }
            }
        }
    }
</script>

That heavily bended level got some improvements so you can:
change size of it just by configuring one number
adjusting led count does not break whole thing
there was more but I didn't kept the diary

So it is easier to use, (until we get the proper thing)

[{"id":"9f3c815ec5f4ba93","type":"ui-template","z":"4eb808f5e19fb7e2","group":"5d78082af21e201d","page":"","ui":"","name":"Round Led Gauge","order":7,"width":"2","height":"2","head":"","format":"<template>\n    <div class=\"round-led-level\" :style=\"`--dot-count:${count}; --size:${size}; --ledsize:${ledSize};`\" >\n        <header>\n            <div class=\"round-led-level-text\">\n                <span class=\"round-led-level-label\">{{label}}</span>\n            </div>\n        </header>\n        <div>\n            <div class=\"round-led-level-stripe\">\n                <div v-for=\"(color, index) in colors\" :key=\"index\" class=\"round-led-level-led\" :ref=\"'dot-' + index +'-'+this.id\">\n                </div>\n            </div>\n            <div class=\"round-led-level-centered-text\">\n                <span class=\"round-led-level-value\">{{formattedValue}}</span>\n                <span class=\"round-led-level-unit\">{{unit}}</span>\n            </div>\n            <div class=\"round-led-level-limits\">\n                <span>{{min}}</span>\n                <span>{{max}}</span>\n            </div>\n        </div>     \n    <div>\n</template>\n\n<script>\nexport default {\n    data(){\n        return {\n            //define me here\n            label:\"MEASURE\", // the label\n            min:0, //smallest expected value\n            max:100, //higest expected value\n            unit:\"cmĀ³\",// unit of the measurement\n            size:140, // ovaerall size. (think in pixels) under 100 will be too small.                                   \n            animate:true,// animating leds is not most performant thing in the world.                          \n            dark:0.4, // how dim is led when not glowing  \n            //define colors as you wish but the thing works clockwise.\n            //led size depends on how many colors is defined.         \n            colors:[                   \n                    \"rgb(var(--v-theme-primary))\",\n                    \"rgb(var(--v-theme-primary))\",\n                    \"rgb(var(--v-theme-primary))\",\n                    \"rgb(var(--v-theme-primary))\",\n                    \"rgb(var(--v-theme-primary))\",\n                    \"rgb(var(--v-theme-primary))\",\n                    \"rgb(var(--v-theme-primary))\",\n                    \"rgb(var(--v-theme-primary))\",\n                    \"rgb(var(--v-theme-primary))\",\n                    \"rgb(var(--v-theme-primary))\",\n                    \"rgb(var(--v-theme-warning))\",\n                    \"rgb(var(--v-theme-warning))\",\n                    \"rgb(var(--v-theme-warning))\",\n                    \"rgb(var(--v-theme-error))\",\n                    \"rgb(var(--v-theme-error))\",\n                   ],            \n            //no need to change those\n            value:0,\n            previousValue:0,\n            inited:false\n        }\n    },\n\n   \n    methods: {        \n        getElement: function(name,base){\n            if(base){\n                return this.$refs[name]\n            }\n            return this.$refs[name][0]\n        },\n\n        full: function(){\n            return Math.floor(this.colors.length*this.percentage/100)\n        },\n        half: function (){\n            let p = this.colors.length*this.percentage/100;\n            p -= this.full()\n            p *= .5\n            p += this.dark;\n            return p;\n        },\n\n        lit: function(){\n            if(this.inited == false){\n                return\n            }\n            const down = this.previousValue > this.value\n\n            let time = .01\n            this.colors.forEach((e,i) => {\n                let dot = this.getElement(\"dot-\"+i+\"-\"+this.id);\n                if(!dot){\n                    console.log(\"no dots found\")\n                    return\n                }                \n                if(i<this.full()){\n                    dot.style.filter= \"brightness(1.1)\";\n                }\n                else if(i==this.full()){                   \n                    dot.style.filter = \"brightness(\"+this.half()+\")\";\n                }\n                else{\n                    dot.style.filter= \"brightness(\"+this.dark+\")\";\n                }\n                if(down){\n                    time = (this.colors.length - i) * .12                    \n                }\n                dot.style.transition = this.animate ? \"filter \"+time+\"s\" : \"unset\";\n            })\n            this.previousValue = this.value\n        }\n    },\n    watch: {\n        msg: function(){    \n            if(this.msg.payload != undefined){           \n                this.value = this.msg.payload                \n                this.lit()\n            }\n        }\n    },\n    computed: {\n        formattedValue: function () {\n            return this.value.toFixed(2)\n        },\n        percentage: function(){\n            return Math.floor(((this.value - this.min) / (this.max - this.min)) * 100);\n        },\n        count: function() {\n            return this.colors.length;\n        },        \n        ledSize:function(){\n            const s = 4.71239 * ((this.size - (this.size*0.3))/2)            \n            return s / this.colors.length\n        }\n    },\n    mounted(){\n        let angle;\n        const step = 270 / this.colors.length; //this.ledSize/2;//270 / (this.colors.length + 2);\n        const radius = (this.size - (this.size*0.1))/2//this.size / 2;\n        const s = this.ledSize / -2;        \n        this.colors.forEach((c,i) => {\n                let dot = this.getElement(\"dot-\"+i+\"-\"+this.id);\n                if(!dot){\n                    console.log(\"no dots found\")\n                    return\n                }\n                dot.style.backgroundColor = c               \n                dot.style.transition = \"filter 0.1s\";\n                dot.style.setProperty('--dot',i);\n                angle = ((i+1)*step) * Math.PI / 180;\n                dot.style.left = s + radius * Math.cos(angle) + 'px';\n                dot.style.top = s + radius * Math.sin(angle) + 'px';\n                dot.style.transform = 'translate('+s+'px, '+s+'px)'; \n                dot.style.rotate = (angle - 0.08)+\"rad\"               \n            }\n        )\n       \n        this.inited = true;\n    },\n        \n}\n</script>\n","storeOutMessages":true,"passthru":true,"resendOnRefresh":true,"templateScope":"local","className":"","x":630,"y":360,"wires":[[]]},{"id":"2545d92a227a8572","type":"ui-template","z":"4eb808f5e19fb7e2","group":"","page":"","ui":"da26eafa8eb48ab3","name":"Round Led Gauge for Dasboard 2.0 CSS","order":0,"width":0,"height":0,"head":"","format":".round-led-level{\n    display: grid;\n    grid-template-rows: 1em 1fr;\n    height: calc(var(--size) * 1px);\n    aspect-ratio: 1/1;\n    position: relative;\n    margin: auto;\n}\n.round-led-level>div{\n    position: relative;;\n}\n.round-led-level-stripe{\n    display: block;\n    position: absolute;\n    left: 50%;\n    top: 56%;\n    rotate: 135deg;  \n}    \n.round-led-level-led {\n    background: #ffffff;\n    position: absolute;\n    width: calc(var(--ledsize) * 1px);\n    aspect-ratio: 1/1;\n    border-radius: 4px;\n    box-shadow: inset 0px 0px calc(var(--ledsize) / 3 * 1px) 0px #00000099, 0px 0px calc(var(--ledsize) / 7 * 1px) 0px #00000099;\n    filter: brightness(0.4);\n    transform-origin: center center;\n    \n\n}\n.round-led-level-text{    \n    font-size: 1rem;\n    line-height: 1rem;\n    text-align: center;\n    user-select: none;\n}\n.round-led-level-centered-text{\n    position: absolute;\n    inset: 0;\n    font-size: 1rem;\n    line-height: 1;\n    display: grid;\n    text-align: center;\n    grid-template-rows: 1fr 1fr;\n    gap: .25em;\n    user-select: none;\n    padding-block: 42%;\n    align-items: center;\n}\n.round-led-level-value{       \n    font-weight:bold;\n    font-size: calc(var(--size) * .15 * 1px);\n}\n.round-led-level-unit{\n    font-size:calc(var(--size) * .1 * 1px);\n    font-weight:normal;\n    padding-inline-start: 0.15em;\n}\n.round-led-level-limits{\n    position: absolute;\n    inset:0; \n    display: flex;\n    justify-content: space-between;\n    font-size: calc(var(--size) * .06 * 1px);\n    line-height: calc(var(--size) * .06 * 1px);\n    align-content: flex-end;\n    flex-wrap: wrap;\n    padding-inline:1em;\n    user-select: none;\n}","storeOutMessages":true,"passthru":true,"resendOnRefresh":true,"templateScope":"site:style","className":"","x":700,"y":320,"wires":[[]]},{"id":"5d78082af21e201d","type":"ui-group","name":"Some stuff","page":"71097e9858ab99f9","width":"6","height":"1","order":1,"showTitle":true,"className":"","visible":"true","disabled":"false"},{"id":"da26eafa8eb48ab3","type":"ui-base","name":"Board","path":"/dashboard"},{"id":"71097e9858ab99f9","type":"ui-page","name":"Test page","ui":"da26eafa8eb48ab3","path":"/second","icon":"home","layout":"grid","theme":"a965ccfef139317a","order":-1,"className":"","visible":"true","disabled":"false"},{"id":"a965ccfef139317a","type":"ui-theme","name":"Default","colors":{"surface":"#575757","primary":"#0fb8ad","bgPage":"#474747","groupBg":"#525252","groupOutline":"#7c837e"},"sizes":{"pagePadding":"12px","groupGap":"12px","groupBorderRadius":"4px","widgetGap":"12px"}}]

And with that I'm going to shut up in terms of indicator widget prototypes and let the thread to be Dashboard 2.0 again :slight_smile:

Don't even know what do now ... :thinking:

8 Likes

You can just load from jsdelivr if you prefer. And check out the built-in CSS. I made much of it to use CSS variables so should be easy to override if needed.

--needle-color
--zone-color-high
--zone-color-warn
--zone-color-normal
--zone-color-low

Also - from my uib-brand.css:

/* Background colours */
--surface1, --surface2, --surface3, --surface4, --surface5
/* Text colour */
--text3

I think those are all the directly overridable vars. Fairly sure everything else is calculated.

1 Like

There is more variables in use, surface and text. I'm on phone can't look right now but those do need also some attention

Updated with the foreground/background colour vars. Hopefully not missed anything.


I have also added some more documentation direct to the repo.


And here are the previous posts for reference:

I had a problem with my label when using @hotNipi 's circular gauge so I have added a few bits (sorry hotNipi). The problem; the text didn't ft into 1 line with a small gauge, so it overlapped the gauge.

There is probably a way of increasing the height of the <header> but it looked jolly complicated so I took the easy way, I copied hotNipi's method of sizing the max / min etc. text.

In the HTML

        <header>
            <div class = "round-led-level-text" :style = "`--labelsize:${labelSize};`">
                <span class = "round-led-level-label">{{label}}</span>
            </div>
        </header>

in the computed part of the script

        labelSize:function() {
            const baseSize = 200
            const fontSize = this.size / baseSize

            return fontSize

        }

and in the CSS

.round-led-level-text{    
    font-size: calc(var(--labelsize) * 1rem);
    height: 2rem;
    line-height: 1rem;
    text-align: center;
    user-select: none;

}

Looking forward to someone coming up with a more elegant solution for long labels.

1 Like

Thing is, every round thing steals a lot of usable space in both directions. Nothing to worry if you have a plenty of free space available. Screens are mostly more wide than tall (phones are opposite but yeah...)

That makes such gauges hard to fit. And that takes to make compromise decisions. Like if label doesn't fit - is it actually proper sentence for label?

That's why I usually don't even think about making label here and there multiline.

But of course - it is open source and free to use and change so everything you do is good and correct and Im very happy you have found the solution and most importantly shared it also.

Please don't :smiley:
You are very much on topic, and your work is inspirational, they add value to DB2.

5 Likes