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

What about this one? Responsive and stuff... :smiley:

Yes, you'll need to have dark background for it. Long way to be a widget ...

CODE
<template>
    <div class="fg-body">
        <div class="fg-display">
        <div class="fg-backplate">
            <div ref="display" class="fg-numbers" >
                <div v-for="(n, index) in numbers" :key="index" class="fg-num">{{n}}            
            </div>
        </div>
        <div class="fg-needle"></div>
    </div>    
    </div>
</template>

<script>
    export default {
        data() {           
            return {
                min:0,
                max:100,
                value:0
            }
        },
       
        computed: {
            numbers:function(){
                var t = (this.max - this.min) / 10;               
                let i = this.min;
                let r = []
                for (let e = 0; e < 11; ++e){
                    r.push(i),                    
                    i = parseFloat((i + t).toFixed(2));
                }
                return r 
            }
            
        },
        methods: {
            range :function (n, p, a, r) {
                if (a == "clamp") {
                    if (n < p.minin) { 
                        n=p.minin; 
                    } 
                    if (n> p.maxin) {
                        n = p.maxin;
                    }
                }
                if (a == "roll") {
                    let d = p.maxin - p.minin;
                    n = ((n - p.minin) % d + d) % d + p.minin;
                }
                let v = ((n - p.minin) / (p.maxin - p.minin) * (p.maxout - p.minout)) + p.minout;
                if (r) {
                    v = Math.round(v);
                }
                return v
            },
            percentage: function(){
                let p = 100 - ((this.value - this.min) / (this.max - this.min)) * 100
                let params = {minin:0, maxin:100, minout:2.5, maxout:97.5};

                return this.range(p,params)
            },
            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! gauge type:",this.type, "id:",this.id,"data:",data)
                        return null
                    }   
                }
                else{
                    ret = data
                }            
                return ret
            },
            move(){
                const display = this.$refs.display;
                if(!display){
                    return
                }
                display.style.left = this.percentage()+"%" 
            }
         
        },
        watch: {           
                msg: function(){    
                    if(this.msg.payload !== undefined){  
                        const v = this.validate(this.msg.payload)                
                        if(v === null){
                            return
                        }         
                        this.value = v
                        this.move()
                    }
                }
            
        },
        mounted() {
            
        },
        unmounted() {
           
        }
    }
</script>
<style>
  .fg-body {
        position: relative;
        width: 100%;
        height: 100%;
        display: flex;
        align-content: center;
        flex-wrap: wrap;
    }

    .fg-numbers {
        position:relative;
        display: flex;
        flex-direction: row;
        justify-content: space-between;
        height: 100%;
        font-size: 1.25rem;
        word-wrap: normal;
        align-content: center;
        flex-wrap: wrap;
        transform: translateX(-50%);
        transition: left 1s ease-in-out;
    }


    .fg-backplate {
        width: 600px;
        flex-shrink: 0;
        position: relative;        
    }

    .fg-numbers:before {
        content: "";
        position: absolute;
        right: 0%;
        width: 100%;
        top: 0px;
        height: 25%;
       
        background: linear-gradient(to right,
                #00000050,
                #00000050 50%,
                transparent 50%,
                transparent);
        background-size: 0.6% 100%;
    }

    .fg-numbers:after {
        content: "Gauges by hotNipi ®";
        position: absolute;
        font-size: 9px;
        left: -90px;
        top: 9px;
        color: #00000080;
    }

    .fg-display {
        width: 100%;
        height: 100%;
        display:flex;
        justify-content: center;
        position: relative;
        box-sizing: border-box;
        overflow: hidden;
        border: 3px solid black;
        background-color: #cfcfcf;
        outline: 1px solid #4d4d4d;
        outline-offset: -2px;
        border-radius: 6px;
        box-shadow: inset 0 0 9px 1px black;
    }

    .fg-display:before,
    .fg-display:after {
        content: "";
        position: absolute;
        inset: 0;

    }

    .fg-display:before {
        background: linear-gradient(270deg, #000000b0, transparent 20%);
    }

    .fg-display:after {
        background: linear-gradient(90deg, #000000b0, transparent 20%);
    }

    .fg-num {
        position: relative;
        text-align: center;
        top: 3px;
        width: 30px;
        color: black;        
    }

    .fg-needle {
        position: absolute;
        left: calc(50% - 1px);
        top: 1px;
        width: 2px;
        height: 21px;
        background-color: #eb1e1e;
        box-shadow: 0px 0px 7px 2px #0000008c;
    }
</style>
5 Likes

As always. Super nice.

1 Like

A call to try it and maybe you have ideas to make it better smarter fancier ...
It is going to be a widget. edgewise-meter

Just copy the code into the ui_template and go.

CODE
<template>   
    <div class="fg-wrapper" :style="styles">
        <div v-if="label" class="fg-label">{{label}}</div>
        <div class="fg-body" :class="{'fg-dark': dark, 'fg-light': !dark}">
            <div ref="display" class="fg-display">
                <div class="fg-backplate">
                    <div ref="plate" class="fg-numbers" >
                        <div v-for="(n, index) in numbers" :key="index" class="fg-num">{{n}}</div>                                    
                    </div>
                    <div v-if="sizeError" class="fg-size-error fg-blonk">
                       <p>The configured size is too small. The digits cannot be placed in exact locations.</p>
                    </div>
                </div>
                <div  ref="needle" class="fg-needle"></div>
            </div>    
        </div>
    </div>
</template>

<script>
    export default {
        data() {           
            return {              
                min:0,
                max:10,
                count:20,// (even int) not all min - max - count combinations can be nicely divided to make a scale. real widget will help with that.   
                size:800,// adjust space between digits. too small will show runtime error
                dark:false, // turn on if the group background is dark 
                sectors:[{start:0,color:"lightblue"},{start:3,color:"transparent"},{start:7,color:"#ffc55c"},{start:9,color:"#fc5b5b"}],
                label:"T-out °C",
                smallDigits:true,                
                logo:"",

                //no need to change 
                value:0,
                sizeError:false,
            }
        },
       
        computed: {
            
            numbers:function(){
                var t = (this.max - this.min) / this.count;               
                let i = this.min;
                let r = []
                for (let e = 0; e < this.count+1; ++e){
                    r.push(i),                    
                    i = parseFloat((i + t).toFixed(2));
                }
                return r 
            },
            styles:function() {
                return {                   
                    "--logo": this.logo ? JSON.stringify(this.logo) : undefined,
                    "--size": this.size,
                    "--small-digits":this.smallDigits ? "small" : "inherit",
                    "--gradient":this.sectorGradient(),
                    "grid-template-columns":this.label ? "auto 1fr" : "1fr",
                }
            }            
        },
        methods: {
            sectorGradient: function(){
                let gradient = "repeating-linear-gradient(to right, #00000040, #00000040 40%, transparent 40%, transparent)"
              
                if(this.sectors.length == 0){
                    return gradient
                }

                gradient = gradient.concat(', linear-gradient(to right,')
                let pos
                let total = this.sectors.length -1
              
                this.sectors.forEach((s,i) => {
                    if(i == 0){
                        if(s.start == this.min){
                            gradient = gradient.concat(s.color,", ")
                            if(i + 1 <= total){
                                pos = this.position(this.sectors[i+1].start).toString()
                                gradient = gradient.concat(s.color," ",pos,"%, ")
                            }
                        }
                        else{
                            gradient = gradient.concat('transparent, transparent ')
                            pos = this.position(s.start).toString()
                            gradient = gradient.concat(pos,"%, ")
                            if(i + 1 <= total){
                                pos = this.position(s.start).toString()
                                gradient = gradient.concat(s.color," ",pos,"%, ")
                                pos = this.position(this.sectors[i+1].start).toString()
                                gradient = gradient.concat(s.color," ",pos,"%, ")
                            }
                        }
                    }
                     else if(i == total){
                        pos = this.position(s.start).toString()
                        gradient = gradient.concat(s.color," ",pos,"%, ")
                        gradient = gradient.concat(s.color)
                    } 
                    else{
                        pos = this.position(s.start).toString()
                        gradient = gradient.concat(s.color," ",pos,"%, ")
                        if(i + 1 <= total){
                           pos = this.position(this.sectors[i+1].start).toString()
                           gradient = gradient.concat(s.color," ",pos,"%, ")
                        }
                    }
                })
                gradient = gradient.concat(")")
                return gradient
            },
            range :function (n, p, a, r) {
                if (a == "clamp") {
                    if (n < p.minin) { 
                        n=p.minin; 
                    } 
                    if (n> p.maxin) {
                        n = p.maxin;
                    }
                }
                if (a == "roll") {
                    let d = p.maxin - p.minin;
                    n = ((n - p.minin) % d + d) % d + p.minin;
                }
                let v = ((n - p.minin) / (p.maxin - p.minin) * (p.maxout - p.minout)) + p.minout;
                if (r) {
                    v = Math.round(v);
                }
                return v
            },
            position: function(target){
                const v = target ?? this.value
                const l = this.size/(this.count+1)/2
                const d = l * 100 / this.size                
                let p = ((v - this.min) / (this.max - this.min)) * 100
                p = target ? p : 100 - p

                const params = {minin:0, maxin:100, minout:d, maxout:100-d};
                return this.range(p,params)
            },
            
            
            validate(data){
                let ret
                if(typeof data !== "number"){
                    ret = parseFloat(data)
                    if(isNaN(ret)){
                        console.log("BAD DATA! gauge type:",this.type, "id:",this.id,"data:",data)
                        return null
                    }   
                }
                else{
                    ret = data
                }            
                return ret
            },
            move(){
                const plate = this.$refs.plate;
                const display = this.$refs.display;
                if(!display || !plate){
                    return
                }
                plate.style.left = this.position()+"%"  
            }
         
        },
        watch: {           
                msg: function(){    
                    if(this.msg.payload !== undefined){  
                        const v = this.validate(this.msg.payload)                
                        if(v === null){
                            return
                        }         
                        this.value = v
                        this.move()
                    }
                }
            
        },
        mounted() {
            setTimeout(()=>{
                const plate = this.$refs.plate;
                const needle = this.$refs.needle
                if(!plate || !needle){
                    return
                }
                let w = 0                
                Array.from(plate.children).forEach(n => {
                    w += parseFloat(getComputedStyle(n).getPropertyValue('width'))                   
                })
                if(w - this.size > 0.2){ 
                    this.sizeError = true             
                    plate.classList.add('fg-blink')
                    needle.classList.add('fg-blink')

                }
            },100)           
           

        },
        unmounted() {
           
        }
    }
</script>
<style>

.fg-light{
    --mix:20%;
    --fg-shadow:rgba(var(--v-theme-on-group-background),var(--mix));
    --fg-background:rgb(var(--v-theme-group-background));
    --fg-outline:rgba(var(--v-theme-on-group-background),40%);
}
.fg-dark{
    --mix: 75%;
    --fg-shadow: rgba(var(--v-theme-group-background), var(--mix));
    --fg-background: rgb(var(--v-theme-on-group-background));
    --fg-outline: rgba(var(--v-theme-group-background), 100%);
}
.fg-wrapper{
    display: grid;   
    gap: 1rem;
    align-items: center;
    height: 100%;
}
.fg-label {
    min-width: 1rem;
    overflow: hidden;
    white-space: nowrap;
    text-overflow: ellipsis;
}
.fg-body {   
    position: relative;
    min-width: 3rem;
    width: 100%;
    height: 100%;
    display: flex;
    align-content: center;       
}

.fg-body.fg-light {    
    filter: drop-shadow(0px 2px 4px var(--fg-shadow));
}

.fg-display {
    width: 100%;
    height: 100%;
    display:flex;
    justify-content: center;
    position: relative;
    box-sizing: border-box;
    overflow: hidden;
    border: 3px solid var(--fg-shadow);
    background-color: var(--fg-background);
    outline: 1px solid var(--fg-outline);
    outline-offset: -2px;
    border-radius: 6px;
    box-shadow: inset 0 0 9px 1px var(--fg-shadow);
    transition: background-color 1s;
}

.fg-display::after {
    content: "";
    position: absolute;
    inset: 0;
    background: linear-gradient(90deg, var(--fg-shadow), transparent 30%, transparent 70%, var(--fg-shadow));
}

.fg-backplate {
    width: calc(var(--size) * 1px);
    flex-shrink: 0;
    position: relative;        
}

.fg-numbers {
    position:relative;
    display: flex;
    flex-direction: row;
    justify-content: space-between;
    height: 100%;
    font-size: 1.25rem;
    word-wrap: normal;
    align-content: center;   
    transform: translateX(-50%);
    transition: left 1s ease-in-out;
}

.fg-numbers :nth-child(even){
    font-size: var(--small-digits);
}

.fg-numbers:after {
    content: "";
    position: absolute;
    right: 0%;
    width: 100%;
    top: 0px;
    height: 25%;    
    background: var(--gradient,repeating-linear-gradient(to right, #00000040, #00000040 40%, transparent 40%, transparent));
    background-size: 4px, 100%;
}
.fg-numbers:before {
    content: var(--logo,"Made in Estonia hotNipi ® Gauges");
    position: absolute;
    font-size: xx-small;   
    text-align: end;
    inset: 0;
    width: 20ch;
    translate: -120%;
    color: var(--fg-outline);
}

.fg-num {
    position: relative;
    text-align: center;   
    width: 100%;
    color: black;
    align-self: flex-end;
    border-bottom: 3px solid transparent;          
}

.fg-needle {
    position: absolute;
    left: calc(50% - 0.5px);
    top: 0;
    width: 1px;
    height: 100%;
    background-color: #eb1e1e;
    box-shadow: 0px 0px 7px 2px var(--fg-shadow);
}
.fg-dark .fg-needle {
     box-shadow: 0px 0px 12px 1px var(--fg-shadow);
}
.fg-size-error {
    position: absolute;
    inset: 0;
    font-size: x-small;
    color:red;
    text-align: center;
    display: flex; 
    justify-content:center;
}
.fg-size-error > p{
    max-width: 140px;
}

@keyframes blink {
  50% {
    opacity: 0.0;
  }
}
.fg-blink{   
  animation: blink 4s step-start 0s infinite;
}
.fg-blonk{    
  animation: blink 4s step-start 2s infinite;
}

</style>
3 Likes

For fun, I just asked ChatGPT 4o the following question:

Create a web component that outputs a visible gauge that looks like the attached image. The output must scale and be responsive to page sizes and layouts and user font size preferences. The gauge's scale changes left and right, the needle stays static in the centre. The gauge mimics a physical edgewise meter. There should be attributes to set the maximum and minimum ranges, an optional label and the HTML output should be reasonably accessible (at least WCAG 2.2 A).

It has given me an answer - no idea whether it will work and it won't let me share it I'm afraid. I'll give it a go and see if it makes any sense at all.

:man_mage:

It actually is simple thing to create with plain html/css but what makes it complex is the configuration options and forced container system where it will be used. So no doubt the AI can make some plain and straight thing. I'll stay away. Making things from scratch is fun.

2 Likes

It's a lovely gauge, I can almost see the moving coil hauling the scale backwards and forwards!
"Made in Estonia" is a nice touch too. :grinning:

I see that your .fg-numbers div has an ease-in-out transition, but it looks to me rather abrupt.

What do you think of a bezier transition to simulate moving a significant weight, with a little overrun?

default
Default

bezier
transition: left 1s cubic-bezier( 0.6, 0.06, 0.132, 1.15 );

I would like the numbers to fade a little out of focus at the sides. I tried to come up with a frosted glass overlay to do this but can't work out how to fade the effect in and out.

4 Likes

I like the creativity but I'm lazy enough to be happy if something does some heavy lifting for me so I can concentrate on the design rather than the raw engineering. The architects mindset perhaps. :slight_smile:

I very much doubt that ChatGPT will come at all close to something finely engineered like your gauges though @hotNipi

It is :wink: , but I doubt the one deliberately don't use the "logo" option ..

It is matter of taste kind of. I don't think it should be something the user should be able to easily configure but as CSS overrides are fee and easy to use , I think that is enough of freedom for that case.

I tried. And it is possible and stuff. But I have a friend with pretty good understanding of digital art and all that flew out the window.
Key points :

  1. Always readable
  2. Muddy look is no no
  3. Easy to render
  4. If it doesn't make it better it makes it worse
  5. Less is more

There was more :smiley:

Just needs a bit of screeching sound to be perfect ..

1 Like

Well, you will doubtless be glad to know that your creativity and engineering skills are still safe for now - I'm not sure just yet what it produced but it certainly does not work! :blush:

1 Like