Gauge's for Dashboard 2.0 made with ui_template

Wont be long (they are in labs): Sparklines — Vuetify

The space between them is the gap what is defined by dashboard.
Widget takes 100% available space.

If you don't want to mess with provided styles for all gauges in all situations but just in this card or dedicated widgets only - add a class to them to override appearance of widget

.my-level .led-level{
    padding-inline:5px;
}

where my-level is that classname you wrote where you wont it to be applied.

Otherwise to add left and right padding for all of them

.led-level{
    padding-inline:5px;
}

Whole widget can be made responsive by all means but only for dedicated use cases. It takes to break the layout at those exact sizes you as the creator of the page will define.

The responsiveness of DB2 is not magical resolver of every possible use cases. It still requires to make some compromises like placing and sizing your widgets so that they look OK on all your screens. That means you can't have that many widgets in one row if they don't fit with the smallest screen you are using.

But saying so - you have the source code of the level - you can make it to be multiple of them in one container which you can control and then make your own placement/sizing rules inside that container so it fits always.

1 Like

8 posts were split to a new topic: Dash 2.0 Sparkline

That's really cool.

Does anyone has an idea how to implement this in Grafana, too ?

@hotNipi

It's correct or it's a typo?

Hi @Giamma,
I had the same doubt as you last week, because I hadn't seen it before in my life. It is used for optional chaining.
Bart

1 Like

Ok, thank you ....

1 Like

Now that newer versions of node.js, it is safe to use that in runtime code.

However, you probably shouldn't use it for front-end browser code since it only started being supported early 2020. iOS support came with v13.4 in March 2020. So if there is a chance that your users might have older devices, best avoided.

I target my front-end code for early 2019 as a rule as this seems to cover all but the oldest devices. One way around this issue is to use a build-tool such as ESBUILD to produce production, minified versions of front-end code. It lets you write current generation code but can build to any previous JavaScript or browser version.

2 Likes

Thank you ...

I was kindly asked for a bit redesign for horizontal level (add icon, spread dots evenly, make it more responsive, get rid of filter usage ...) A bit dedicated design or so. But I'll share anyway :smiley:

It uses container query's for responsiveness. Those are set to solid values so the break points may need adjustments if you plan to use it on your dashboard. (break points can't be dynamically adjustable)

image

Code
<template>
    <div class="led-level">
        <div :class="icon ? 'led-level-grid-2':'led-level-grid-1'">
            <div v-if="icon" class="led-level-icon">
                <v-icon aria-hidden="false">{{icon}}</v-icon>
            </div>
            <div class="led-level-content">
                <div class="led-level-text">
                    <span class="led-level-label">{{label}}</span>
                    <span class="led-level-value">{{formattedValue}}<span class="led-level-unit">{{unit}}</span></span>
                </div>
                <div class="led-level-stripe">
                    <div v-for="(color, index) in colors" :key="index" class="led-level-led" :ref="'dot-' + index"></div>
                </div>
                <div class="led-level-limits">
                    <span class="led-level-limit">{{min}}</span>
                    <span v-for="(tick, index) in ticks" :key="index" class="led-level-limit" :ref="'tick-' + index">{{tick}}</span>
                    <span class="led-level-limit">{{max}}</span>
                </div>
            </div>
        </div>
    </div>
</template>
<script>
    export default {
    data(){
        return {
            //Define me here
                                               
            label:"Level long name", // The label
            icon:"mdi-account", //mdi-account (optional) the icon           
            min:0, // Smallest expected value
            max:100, // Highest expected value
            unit:"cm³",// The unit of the measurement
            dim:0.2, //How dim is led when not glowing 
            
            animate:true, // Animating led's is not most performant thing in the world. 

            colors:[ 
                "#00e300",
                "#00e300",
                "#00e300",
                "#00e300",
                "#00e300",
                "#00e300",
                "#00e300",
                "#00e300",
                "#00e300",
                "#00e300",
                "#00e300",
                "#00e300",
                "#00e300",
                "#00e300",
                "#00e300", 
                "#ffa916", 
                "#ffa916", 
                "#ffa916", 
                "#ff4c16",
                "#ff4c16"],
                
                ticks:[50,75], //optional tick values          
            
            //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]
        },
        validate(data){
            let ret
            if(typeof data !== "number"){
                ret = parseFloat(data)
                if(isNaN(ret)){
                    console.log("BAD DATA! id:",this.id,"data:",data)
                    return null
                }   
            }
            else{
                ret = data
            }            
            return ret
        },

        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.dim;
            return p;
        },
        
        tickPosition: function(tv){
        
            return Math.floor(((tv - this.min) / (this.max - this.min)) * 100)+'%';
        },
        

        lit: function(){
            if(this.inited == false){
                return
            }
            const down = this.previousValue > this.value
            let time = down ? 1 : 0.2
            let step = down ? 0.12 : 0.06
            this.colors.forEach((color,i) => {
                let dot = this.getElement("dot-"+i);
                if(!dot){
                    console.log("lit() no dots found")
                    return
                }                
                if(i<this.full()){                   
                    dot.style.opacity = 1;
                    time += step
                }
                else if(i==this.full()){                    
                    dot.style.opacity = this.half()                                   
                }
                else{                  
                    dot.style.opacity = this.dim                                  
                }
                if(down){                  
                    time -= step                                 
                }
                else{
                    time -= step
                }
                dot.style.transition = this.animate && time > 0 ? "opacity "+time+"s" : "unset";
            })
            this.previousValue = this.value
        }    
    },
    watch: {
        msg: function(){    
            if(this.msg.payload !== undefined){  
                const v = this.validate(this.msg.payload)                
                if(v === null){
                    return
                }         
                this.value = v                
                this.lit()              
            }
        }
    },
    computed: {
        formattedValue: function () {
            return this.value.toFixed(2)
        },
        percentage: function(){
            return Math.floor(((this.value - this.min) / (this.max - this.min)) * 100);
        }
    },
    mounted(){         
        this.colors.forEach((c,i) => {
                let dot = this.getElement("dot-"+i);
                if(!dot){                        
                    return
                }                    
                dot.style.backgroundColor = c                    
            }
        );
         this.ticks.forEach((t,i) => {
            let tick = this.getElement("tick-"+i);
                if(!tick){                        
                    return
                }                    
                tick.style.left = this.tickPosition(t)
            }
        ); 

        this.inited = true;
    },
    unmounted () {
       
    }
}
</script>
CSS
.led-level{
    container-type: inline-size;
    container-name: led-level;
    --break-point:160;
}

.led-level-grid-2 {    
    display:grid;    
    grid-template-columns: 2rem auto;
    gap:1rem;   
    height: 100%;
}
.led-level-grid-1 {
    display:grid;    
    grid-template-columns: 1fr;
    gap:1rem;   
    height: 100%;
}
@container led-level (max-width: 160px) { 
    .led-level-grid-2{
        grid-template-columns: 1rem auto;
    }
    .led-level-icon .v-icon{
        font-size: calc(var(--v-icon-size-multiplier)* 2em) !important;
    }
}
.led-level-icon{    
    display:flex;
    justify-content: center;
    align-items: center;    
}
.led-level-icon .v-icon{
    font-size: calc(var(--v-icon-size-multiplier)* 3em);
}
.led-level-content {
    container-type: inline-size;
    container-name: level-content;     
    display: grid;
    grid-template-rows: 1.3em minmax(3px, 1fr) .7em;
    gap: 2px;
    height: 100%;
}
.led-level-text {
    font-size: 1.25em;
    line-height: 1em;
    align-self: end;
    display: flex;    
    justify-content: space-between;
    user-select: none;
    overflow: auto;
}
.led-level-label{
    font-size: 1rem;
    text-overflow: ellipsis;
    white-space: nowrap;
    overflow: hidden;
}
.led-level-value {
    font-weight: bold;
}
.led-level-unit {
    font-size: .75em;
    font-weight: normal;
    padding-inline-start: 0.15em;
}
.led-level-stripe {
    display: flex;    
}
.led-level-led {
    background: #ffffff;
    width: 100%;
    height: 100%;    
    border-left: 1px solid rgb(var(--v-theme-group-background));
    border-right: 1px solid rgb(var(--v-theme-group-background));
    border-radius: 0px;    
}
@container level-content (max-width: 150px) {
    .led-level-led:nth-child(even){
        display:none;
    }
    .led-level-limit:not(:first-child):not(:last-child){
        display:none;
    }
}
@container level-content (max-width: 130px) {
    .led-level-label{
        display:none;
    }
    .led-level-text{
        justify-content:flex-end;
    }
}

.led-level-limits {
    display: block;
    position: relative;
    font-size: .75em;
    line-height: 1;   
    user-select: none;
}
.led-level-limit{
    position:absolute;
    transform:translate(-50%,0);
}
.led-level-limit:first-child{
    left:0;
    transform: translate(0, 0);
}
.led-level-limit:last-child{
    right:0;
    transform: translate(0, 0);
}

7 Likes