Dashboard 2 widget positioning

How I can reach same looking layout as in your post?
noderedforum2

My layout comes like this

I want that those 3 vertical bars are all in right side same way as your buttons.

my flow

[
    {
        "id": "f999d4f28598e2d0",
        "type": "ui-template",
        "z": "51ef46c8e45f5993",
        "group": "634ef21adaf4d809",
        "page": "",
        "ui": "",
        "name": "B1",
        "order": 3,
        "width": "3",
        "height": "2",
        "head": "",
        "format": "<template>\n    <div ref=\"hng\" :class=\"icon ? 'ag-wrapper-2' : 'ag-wrapper-1'\" :style=\"`--line-color:${colors[0]};`\">\n        <div v-if=\"icon\" class=\"ag-icon\">\n            <v-icon aria-hidden=\"false\">{{icon}}</v-icon>\n        </div>\n        <div class=\"ag-content\">\n            <div class=\"ag-text\">\n                <span class=\"ag-label\">{{label}}</span>\n                <span class=\"ag-value\">{{formattedValue}}<span class=\"ag-unit\">{{unit}}</span></span>\n            </div>\n            <div class=\"ag-track\" ref=\"agLine\">\n                <div class=\"ag-track-background\"></div>\n                <div class=\"ag-track-foreground\" :style=\"{'width': linesize +'%'}\"></div>\n            </div>\n            <div class=\"ag-limits\">\n                <span class=\"ag-min\">{{min}}</span>\n                <span class=\"ag-max\">{{max}}</span>\n            </div>\n        </div>\n    </div>\n</template>\n\n<script>\n    export default {\n    data(){\n        return {\n            //Define me here\n                                             \n            label:\"Listeria\", // The label\n            //icon:\"mdi-account\", // (type: artless) (optional) the icon\n            zeroCross:false,// (type: artless) line changes color depending on value being positive or negative (at least 2 colors must be defined)\n            min:0, // Smallest expected value\n            max:8, // Highest expected value\n            unit:\"log10\",// The unit of the measurement           \n            animate:true, // Animating led's is not most performant thing in the world.                          \n            \n            // Define colors           \n            colors:[\n                    \"red\",\n                    \"red\",   \n                    \"red\",\n                    \"red\",                                                         \n                    \"orange\",\n                    \"orange\",\n                    \"orange\",\n                    \"orange\",\n                    \"#0fb60f\"\n                   ],            \n            \n            //no need to change those\n            value:0,          \n            inited:false\n        }\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        validate(data){\n            let ret\n            if(typeof data !== \"number\"){\n                ret = parseFloat(data)\n                if(isNaN(ret)){\n                    console.log(\"BAD DATA! gauge type:\",this.type, \"id:\",this.id,\"data:\",data)\n                    return null\n                }   \n            }\n            else{\n                ret = data\n            }            \n            return ret\n        },\n        changeLine:function(){\n            const line = this.getElement(\"agLine\",true);\n            if(!line){\n                console.log(\"no line found\")\n                return            \n            }\n           \n            let c = Math.floor(this.colors.length * this.percentage / 100)\n            if(c >= this.colors.length){\n                c = this.colors.length - 1\n            }\n            if(c < 0){\n                c = 0\n            }\n            if(this.zeroCross){\n                c = this.value > 0 ? (this.colors.length - 1) : 0\n            }\n            line.style.setProperty('--line-color',this.colors[c])\n\n        }\n    },\n       \n    watch: {\n        msg: function(){    \n            if(this.msg.payload !== undefined){  \n                const v = this.validate(this.msg.payload)                \n                if(v === null){\n                    return\n                }         \n                this.value = v\n                this.changeLine()              \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        linesize:function(){\n            if(this.zeroCross){\n                return Math.floor(((Math.abs(this.value) - this.min) / (this.max - this.min)) * 100);           \n            }\n            else{\n                return Math.max(0,this.percentage)\n            }\n        }\n    },\n    mounted(){\n        const line = this.getElement(\"agLine\",true);\n        line.style.setProperty('--line-color',this.colors[0])\n        if(this.animate == true){\n            if(!line){\n                console.log(\"artless init() no line found\")\n                return\n            }\n            line.style.transition = \"width 0.5s\";\n        }\n       \n        this.inited = true;\n    }\n}\n</script>\n\n<style>\n.ag-wrapper-2 {\ndisplay: grid;\ngrid-template-columns: 3em 1fr;\ngap:1em;\n}\n.ag-wrapper-1 {\ndisplay: grid;\ngrid-template-columns: 1fr;\n}\n.ag-icon{\nfont-size: 2em;\ndisplay: flex;\nflex-direction: column;\njustify-content: center;\n}\n.ag-content{\ndisplay: grid;\ngrid-template-rows: 1fr 7px 0.75em;\ngap: 2px;\n}\n.ag-text{\nfont-size: 1.25em;\nline-height: 1em;\nalign-self: end;\ndisplay: flex;\nflex-wrap: wrap;\njustify-content: space-between;\nuser-select: none;\n}\n.ag-value{\nfont-weight:bold;\n}\n.ag-unit{\nfont-size:.75em;\nfont-weight:normal;\npadding-inline-start: 0.15em;\n}\n.ag-limits{\ndisplay: flex;\njustify-content: space-between;\nfont-size: .75em;\nline-height: .75em;\nalign-content: center;\nflex-wrap: wrap;\nuser-select: none;\n}\n\n.ag-track{\nposition:relative;\ndisplay:flex;\nalign-items: center;\nwidth: 100%;\nborder-radius: 6px;\n}\n\n.ag-track-background{\nposition:absolute;\nbackground: var(--line-color,rgb(var(--v-theme-primary)));\nopacity: 0.45;\nwidth: 100%;\nheight: 50%;\nborder-radius:inherit;\n}\n.ag-track-foreground{\nposition:absolute;\nbackground-color: var(--line-color,rgb(var(--v-theme-primary)));\nwidth: 50%;\nheight: 100%;\nmax-width: 100%;\nborder-radius:inherit;\ntransition:inherit;\n}\n</style>",
        "storeOutMessages": true,
        "passthru": true,
        "resendOnRefresh": true,
        "templateScope": "local",
        "className": "",
        "x": 650,
        "y": 260,
        "wires": [
            []
        ]
    },
    {
        "id": "634ef21adaf4d809",
        "type": "ui-group",
        "name": "T1",
        "page": "4f1edd9ca629751a",
        "width": "6",
        "height": "1",
        "order": -1,
        "showTitle": false,
        "className": "",
        "visible": "true",
        "disabled": "false"
    },
    {
        "id": "4f1edd9ca629751a",
        "type": "ui-page",
        "name": "testi",
        "ui": "e7bb0a62b3d9dd57",
        "path": "/page1",
        "icon": "home",
        "layout": "grid",
        "theme": "4d024ff372f88d1f",
        "order": -1,
        "className": "",
        "visible": "true",
        "disabled": "false"
    },
    {
        "id": "e7bb0a62b3d9dd57",
        "type": "ui-base",
        "name": "UI Name",
        "path": "/dashboard",
        "includeClientData": true,
        "acceptsClientConfig": [
            "ui-notification",
            "ui-control"
        ],
        "showPathInSidebar": false
    },
    {
        "id": "4d024ff372f88d1f",
        "type": "ui-theme",
        "name": "Theme Name",
        "colors": {
            "surface": "#979191",
            "primary": "#0094ce",
            "bgPage": "#eeeeee",
            "groupBg": "#ffffff",
            "groupOutline": "#cccccc"
        },
        "sizes": {
            "pagePadding": "12px",
            "groupGap": "12px",
            "groupBorderRadius": "4px",
            "widgetGap": "12px"
        }
    }
]

You have group width 6 units.
Round thing should be first in order
Round thing size should be configured 3x3
Artless (all) size should be configured 3x1

+-------+-------+-------+-----------+-----------+-----------+
| Round | Round | Round | Artless 1 | Artless 1 | Artless 1 |
+-------+-------+-------+-----------+-----------+-----------+
| Round | Round | Round | Artless 2 | Artless 2 | Artless 2 |
+-------+-------+-------+-----------+-----------+-----------+
| Round | Round | Round | Artless 3 | Artless 3 | Artless 3 |
+-------+-------+-------+-----------+-----------+-----------+

I have tried also artless 3x1 but result iwas same

There's no more tools but size of group, sizes for widgets and the order of the widgets. If to start to mess with CSS, the dashboard layout fights back hard.

Just to repeat what @hotNipi has said,
you need 2 groups sized 3x3 each.
As your page width is 6 wide, the groups will position themselves side by side.
In the left group, add your round gauge (sized 3x3).
In the right group, add 3 artless gauges (sized 3x1 each) which should stack up vertically.

heights of the groups are different and I need no gap between groups

flow

[
    {
        "id": "51ef46c8e45f5993",
        "type": "tab",
        "label": "Koemittarit",
        "disabled": false,
        "info": "",
        "env": []
    },
    {
        "id": "9ccc34f8f12f8d82",
        "type": "ui-template",
        "z": "51ef46c8e45f5993",
        "group": "48106afca278ac11",
        "page": "",
        "ui": "",
        "name": "T1",
        "order": 4,
        "width": "3",
        "height": "3",
        "head": "",
        "format": "<template>\n    <div class=\"hn-sng\">\n        <div class=\"label\">{{label}}</div>\n        <svg ref=\"hn-gauge\" width=\"100%\" height=\"100%\" viewBox=\"0 0 100 100\">\n            <g>\n                <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>                \n            </g>\n            <g>\n                <path class=\"tick-minor\" stroke-width=\"5\" d=\"M 10 90 A 47.5 47.5 25 1 1 90 90\"></path>\n                <path ref=\"arc\" class=\"tick-major\" stroke-width=\"5\" d=\"M 10 90 A 47.5 47.5 25 1 1 90 90\"></path>\n            </g>            \n            <g>\n                <text v-for=\"(item, index) in numbers\" :key=\"index\" class=\"num\" text-anchor=\"middle\" y=\"-37\" :style=\"`rotate: ${item.r}deg;`\">{{item.n}}</text>\n            </g>\n            <g>\n                <text class=\"measurement\" y=\"48\" x=\"50%\" text-anchor=\"middle\">{{measurement}}</text>\n                <text class=\"unit\" y=\"75\" x=\"50%\" text-anchor=\"middle\">{{unit}}</text>\n                <text class=\"value\" y=\"90\" x=\"50%\" text-anchor=\"middle\">{{formattedValue}}</text>\n                <text v-if=\"showUpdate\" ref=\"update\" class=\"update\" y=\"100\" x=\"0\" text-anchor=\"left\">{{formattedUpdate}}</text>\n            </g>\n            <g ref=\"o-needle\" class=\"o-needle\">\n                <path d=\"M 0,0 -1.5,0 -0.15,-40 0.15,-40 1.5,0 z\"></path>\n                <circle cx=\"0\" cy=\"0\" r=\"3\"></circle>\n            </g>\n        </svg>\n    </div>\n</template>\n<script>\nexport default{\n        data(){\n            return {\n                //define me here\n                min:0,\n                max:100,\n                unit:\"°C\",\n                label:\"T1\",\n                measurement:\"Lämpötila\",\n                //updateLabel:\"Last update:\",\n                sectors:[{start:0,end:40,color:\"lightblue\"},{start:40,end:54,color:\"orange\"},{start:54,end:80,color:\"#63cc25\"},{start:80,end:100,color:\"red\"}],\n                //showUpdate:true,\n                //no need to change\n                value:0,\n                updateTime:0                             \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            validate: function(data){\n                let ret                \n                if(typeof data !== \"number\"){\n                    ret = parseFloat(data)\n                    if(isNaN(ret)){\n                        console.log(\"BAD DATA! gauge id:\",this.id,\"data:\",data)\n                        return null\n                    }\n                }                    \n                else{\n                    ret = data\n                }                \n                return ret\n            },\n            range:function (n, p, r) {           \n                if (n < p.minIn) {\n                    n=p.minIn;\n                }\n                if (n > p.maxIn) {\n                    n = p.maxIn;\n                }\n                if(r){\n                    return Math.round(((n - p.minIn) / (p.maxIn - p.minIn) * (p.maxOut - p.minOut)) + p.minOut);\n                }\n                return ((n - p.minIn) / (p.maxIn - p.minIn) * (p.maxOut - p.minOut)) + p.minOut;\n            },\n            generateNumbers:function(min,max){               \n                const nums = [{r:238,n:0},{r:262,n:0},{r:286,n:0},{r:310,n:0},{r:335,n:0},{r:360,n:0},{r:385,n:0},{r:409,n:0},{r:434,n:0},{r:458,n:0},{r:123,n:0}];\n                let t = (max - min) / 10;\n                let i = min;\n                for (let e = 0; e < 11; ++e){\n                    nums[e].n = i\n                    i = parseFloat((i + t).toFixed(2));                   \n                }\n                return nums \n            },\n            sectorData:function(full){               \n                let params, pos, span, sec, str\n                let ret = [] \n                this.sectors.forEach((sector,idx) => {\n                    sec = {name:'sector-'+idx,color:sector.color}\n                    params = {minIn:this.min, maxIn:this.max, minOut:0, maxOut:full}                   \n                    pos = this.range(sector.start,params,false)\n                    params = {minIn:0, maxIn:this.max-this.min, minOut:0, maxOut:full}\n                    span = this.range(sector.end - sector.start,params,false)                    \n                    sec.css = \"0 \"+pos+\" \"+span+\" var(--dash)\"\n                    ret.push(sec)\n                })\n                return ret\n            },\n            rotation:function(v){\n                const params = {minIn:this.min, maxIn:this.max, minOut:-123, maxOut:123};\n                return this.range(v,params,false) +'deg'          \n            }\n\n        },\n        watch: {\n            msg: function(){\n                if(this.msg.payload != undefined){                   \n                    const v = this.validate(this.msg.payload)                   \n                    if(v == null){\n                        return\n                    }\n                    this.value = v\n                    this.updateTime = Date.now()                    \n                    this.getElement('o-needle',true).style.rotate = this.rotation(this.value)                    \n                }\n            }\n        },\n        computed: {\n            formattedValue: function () {\n                return this.value.toFixed(1)\n            },\n            formattedUpdate:function (){\n                return this.updateLabel+\" \"+this.time\n            },\n            time:function(){\n                return new Intl.DateTimeFormat('default', {hour: 'numeric', minute: 'numeric',second: 'numeric'}).format(this.updateTime);\n            },\n            numbers:function(){\n                return this.generateNumbers(this.min,this.max)\n            }                \n        },\n        mounted(){\n           \n            const dal = this.getElement('arc',true).getTotalLength()\n            const sec = this.sectorData(dal)              \n            const gauge = this.getElement('hn-gauge',true)\n            gauge.style.setProperty('--dash',dal)\n           \n            sec.forEach(s =>{\n                const sector = this.getElement(s.name,false)\n                sector.style.setProperty(\"stroke-dasharray\",s.css)\n                sector.style.setProperty(\"stroke\",s.color)\n            })            \n\n        }\n\n    }\n</script>\n<style>\n.hn-sng{\nposition:relative;\n}\n.hn-sng .label{\nposition:absolute;\nfont-size:1rem;\ncolor:currentColor;\ntext-align:center;\nwidth:100%;\noverflow: hidden;\nwhite-space: nowrap;\ntext-overflow: ellipsis;\n}\n.hn-sng .value {\nfill:currentColor;\n//fill:red;\nfont-weigth:600;\n}\n.hn-sng .unit {\nfill:currentColor;\nfont-size:0.4rem;\n}\n.hn-sng .measurement {\nfill:currentColor;\nfont-size:0.5rem;\n}\n.hn-sng .update {\nfill:currentColor;\nfill-opacity:0.6;\nfont-size:0.35rem;\n}\n.hn-sng .num{\ntransform-origin: center 64%;\ntransform: translate(50%, 64%);\nfill:currentColor;\nfill-opacity:0.6;\nfont-size:.35rem;\n}\n.hn-sng .tick-minor{\nstroke-dasharray: 0.5 0 0 3.53;\nstroke-dashoffset:-0.5;\nfill:none;\nstroke:currentColor;\nstroke-opacity:0.6;\n}\n.hn-sng .tick-major{\nstroke-dasharray: 2 0 0 18.1;\nfill:none;\nstroke:currentColor;\n}\n.hn-sng .sector{\nfill:none;\nstroke:transparent;\n}\n.hn-sng .o-needle{\ntransform-origin: center 64%;\ntransform: translate(50%, 64%);\ntransition:rotate .5s;\n}\n.hn-sng .o-needle path, .hn-sng .o-needle circle{\nfill:currentColor;\n//fill:red;\n}\n</style>",
        "storeOutMessages": true,
        "passthru": true,
        "resendOnRefresh": true,
        "templateScope": "local",
        "className": "",
        "x": 650,
        "y": 80,
        "wires": [
            []
        ]
    },
    {
        "id": "1f97fc8ae5645bd0",
        "type": "ui-template",
        "z": "51ef46c8e45f5993",
        "group": "1e0364d1c4ba9e0d",
        "page": "",
        "ui": "",
        "name": "Salmonella",
        "order": 2,
        "width": "3",
        "height": "1",
        "head": "",
        "format": "<template>\n    <div ref=\"hng\" :class=\"icon ? 'ag-wrapper-2' : 'ag-wrapper-1'\" :style=\"`--line-color:${colors[0]};`\">\n        <div v-if=\"icon\" class=\"ag-icon\">\n            <v-icon aria-hidden=\"false\">{{icon}}</v-icon>\n        </div>\n        <div class=\"ag-content\">\n            <div class=\"ag-text\">\n                <span class=\"ag-label\">{{label}}</span>\n                <span class=\"ag-value\">{{formattedValue}}<span class=\"ag-unit\">{{unit}}</span></span>\n            </div>\n            <div class=\"ag-track\" ref=\"agLine\">\n                <div class=\"ag-track-background\"></div>\n                <div class=\"ag-track-foreground\" :style=\"{'width': linesize +'%'}\"></div>\n            </div>\n            <div class=\"ag-limits\">\n                <span class=\"ag-min\">{{min}}</span>\n                <span class=\"ag-max\">{{max}}</span>\n            </div>\n        </div>\n    </div>\n</template>\n\n<script>\n    export default {\n    data(){\n        return {\n            //Define me here\n                                             \n            label:\"Salmonella\", // The label\n            //icon:\"mdi-account\", // (type: artless) (optional) the icon\n            zeroCross:false,// (type: artless) line changes color depending on value being positive or negative (at least 2 colors must be defined)\n            min:0, // Smallest expected value\n            max:8, // Highest expected value\n            unit:\"log10\",// The unit of the measurement           \n            animate:true, // Animating led's is not most performant thing in the world.                          \n            \n            // Define colors           \n            colors:[\n                    \"red\",\n                    \"red\",   \n                    \"red\",\n                    \"red\",                                                         \n                    \"orange\",\n                    \"orange\",\n                    \"orange\",\n                    \"orange\",\n                    \"#0fb60f\"\n                   ],            \n            \n            //no need to change those\n            value:0,          \n            inited:false\n        }\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        validate(data){\n            let ret\n            if(typeof data !== \"number\"){\n                ret = parseFloat(data)\n                if(isNaN(ret)){\n                    console.log(\"BAD DATA! gauge type:\",this.type, \"id:\",this.id,\"data:\",data)\n                    return null\n                }   \n            }\n            else{\n                ret = data\n            }            \n            return ret\n        },\n        changeLine:function(){\n            const line = this.getElement(\"agLine\",true);\n            if(!line){\n                console.log(\"no line found\")\n                return            \n            }\n           \n            let c = Math.floor(this.colors.length * this.percentage / 100)\n            if(c >= this.colors.length){\n                c = this.colors.length - 1\n            }\n            if(c < 0){\n                c = 0\n            }\n            if(this.zeroCross){\n                c = this.value > 0 ? (this.colors.length - 1) : 0\n            }\n            line.style.setProperty('--line-color',this.colors[c])\n\n        }\n    },\n       \n    watch: {\n        msg: function(){    \n            if(this.msg.payload !== undefined){  \n                const v = this.validate(this.msg.payload)                \n                if(v === null){\n                    return\n                }         \n                this.value = v\n                this.changeLine()              \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        linesize:function(){\n            if(this.zeroCross){\n                return Math.floor(((Math.abs(this.value) - this.min) / (this.max - this.min)) * 100);           \n            }\n            else{\n                return Math.max(0,this.percentage)\n            }\n        }\n    },\n    mounted(){\n        const line = this.getElement(\"agLine\",true);\n        line.style.setProperty('--line-color',this.colors[0])\n        if(this.animate == true){\n            if(!line){\n                console.log(\"artless init() no line found\")\n                return\n            }\n            line.style.transition = \"width 0.5s\";\n        }\n       \n        this.inited = true;\n    }\n}\n</script>\n\n<style>\n.ag-wrapper-2 {\ndisplay: grid;\ngrid-template-columns: 3em 1fr;\ngap:1em;\n}\n.ag-wrapper-1 {\ndisplay: grid;\ngrid-template-columns: 1fr;\n}\n.ag-icon{\nfont-size: 2em;\ndisplay: flex;\nflex-direction: column;\njustify-content: center;\n}\n.ag-content{\ndisplay: grid;\ngrid-template-rows: 1fr 7px 0.75em;\ngap: 2px;\n}\n.ag-text{\nfont-size: 1.25em;\nline-height: 1em;\nalign-self: end;\ndisplay: flex;\nflex-wrap: wrap;\njustify-content: space-between;\nuser-select: none;\n}\n.ag-value{\nfont-weight:bold;\n}\n.ag-unit{\nfont-size:.75em;\nfont-weight:normal;\npadding-inline-start: 0.15em;\n}\n.ag-limits{\ndisplay: flex;\njustify-content: space-between;\nfont-size: .75em;\nline-height: .75em;\nalign-content: center;\nflex-wrap: wrap;\nuser-select: none;\n}\n\n.ag-track{\nposition:relative;\ndisplay:flex;\nalign-items: center;\nwidth: 100%;\nborder-radius: 6px;\n}\n\n.ag-track-background{\nposition:absolute;\nbackground: var(--line-color,rgb(var(--v-theme-primary)));\nopacity: 0.45;\nwidth: 100%;\nheight: 50%;\nborder-radius:inherit;\n}\n.ag-track-foreground{\nposition:absolute;\nbackground-color: var(--line-color,rgb(var(--v-theme-primary)));\nwidth: 50%;\nheight: 100%;\nmax-width: 100%;\nborder-radius:inherit;\ntransition:inherit;\n}\n</style>",
        "storeOutMessages": true,
        "passthru": true,
        "resendOnRefresh": true,
        "templateScope": "local",
        "className": "",
        "x": 670,
        "y": 180,
        "wires": [
            []
        ]
    },
    {
        "id": "897c7005ca8b3a31",
        "type": "ui-template",
        "z": "51ef46c8e45f5993",
        "group": "1e0364d1c4ba9e0d",
        "page": "",
        "ui": "",
        "name": "E.Coli",
        "order": 3,
        "width": "3",
        "height": "1",
        "head": "",
        "format": "<template>\n    <div ref=\"hng\" :class=\"icon ? 'ag-wrapper-2' : 'ag-wrapper-1'\" :style=\"`--line-color:${colors[0]};`\">\n        <div v-if=\"icon\" class=\"ag-icon\">\n            <v-icon aria-hidden=\"false\">{{icon}}</v-icon>\n        </div>\n        <div class=\"ag-content\">\n            <div class=\"ag-text\">\n                <span class=\"ag-label\">{{label}}</span>\n                <span class=\"ag-value\">{{formattedValue}}<span class=\"ag-unit\">{{unit}}</span></span>\n            </div>\n            <div class=\"ag-track\" ref=\"agLine\">\n                <div class=\"ag-track-background\"></div>\n                <div class=\"ag-track-foreground\" :style=\"{'width': linesize +'%'}\"></div>\n            </div>\n            <div class=\"ag-limits\">\n                <span class=\"ag-min\">{{min}}</span>\n                <span class=\"ag-max\">{{max}}</span>\n            </div>\n        </div>\n    </div>\n</template>\n\n<script>\n    export default {\n    data(){\n        return {\n            //Define me here\n                                             \n            label:\"E.Coli\", // The label\n            //icon:\"mdi-account\", // (type: artless) (optional) the icon\n            zeroCross:false,// (type: artless) line changes color depending on value being positive or negative (at least 2 colors must be defined)\n            min:0, // Smallest expected value\n            max:8, // Highest expected value\n            unit:\"log10\",// The unit of the measurement           \n            animate:true, // Animating led's is not most performant thing in the world.                          \n            \n            // Define colors           \n            colors:[\n                    \"red\",\n                    \"red\",   \n                    \"red\",\n                    \"red\",                                                         \n                    \"orange\",\n                    \"orange\",\n                    \"orange\",\n                    \"orange\",\n                    \"#0fb60f\"\n                   ],            \n            \n            //no need to change those\n            value:0,          \n            inited:false\n        }\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        validate(data){\n            let ret\n            if(typeof data !== \"number\"){\n                ret = parseFloat(data)\n                if(isNaN(ret)){\n                    console.log(\"BAD DATA! gauge type:\",this.type, \"id:\",this.id,\"data:\",data)\n                    return null\n                }   \n            }\n            else{\n                ret = data\n            }            \n            return ret\n        },\n        changeLine:function(){\n            const line = this.getElement(\"agLine\",true);\n            if(!line){\n                console.log(\"no line found\")\n                return            \n            }\n           \n            let c = Math.floor(this.colors.length * this.percentage / 100)\n            if(c >= this.colors.length){\n                c = this.colors.length - 1\n            }\n            if(c < 0){\n                c = 0\n            }\n            if(this.zeroCross){\n                c = this.value > 0 ? (this.colors.length - 1) : 0\n            }\n            line.style.setProperty('--line-color',this.colors[c])\n\n        }\n    },\n       \n    watch: {\n        msg: function(){    \n            if(this.msg.payload !== undefined){  \n                const v = this.validate(this.msg.payload)                \n                if(v === null){\n                    return\n                }         \n                this.value = v\n                this.changeLine()              \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        linesize:function(){\n            if(this.zeroCross){\n                return Math.floor(((Math.abs(this.value) - this.min) / (this.max - this.min)) * 100);           \n            }\n            else{\n                return Math.max(0,this.percentage)\n            }\n        }\n    },\n    mounted(){\n        const line = this.getElement(\"agLine\",true);\n        line.style.setProperty('--line-color',this.colors[0])\n        if(this.animate == true){\n            if(!line){\n                console.log(\"artless init() no line found\")\n                return\n            }\n            line.style.transition = \"width 0.5s\";\n        }\n       \n        this.inited = true;\n    }\n}\n</script>\n\n<style>\n.ag-wrapper-2 {\ndisplay: grid;\ngrid-template-columns: 3em 1fr;\ngap:1em;\n}\n.ag-wrapper-1 {\ndisplay: grid;\ngrid-template-columns: 1fr;\n}\n.ag-icon{\nfont-size: 2em;\ndisplay: flex;\nflex-direction: column;\njustify-content: center;\n}\n.ag-content{\ndisplay: grid;\ngrid-template-rows: 1fr 7px 0.75em;\ngap: 2px;\n}\n.ag-text{\nfont-size: 1.25em;\nline-height: 1em;\nalign-self: end;\ndisplay: flex;\nflex-wrap: wrap;\njustify-content: space-between;\nuser-select: none;\n}\n.ag-value{\nfont-weight:bold;\n}\n.ag-unit{\nfont-size:.75em;\nfont-weight:normal;\npadding-inline-start: 0.15em;\n}\n.ag-limits{\ndisplay: flex;\njustify-content: space-between;\nfont-size: .75em;\nline-height: .75em;\nalign-content: center;\nflex-wrap: wrap;\nuser-select: none;\n}\n\n.ag-track{\nposition:relative;\ndisplay:flex;\nalign-items: center;\nwidth: 100%;\nborder-radius: 6px;\n}\n\n.ag-track-background{\nposition:absolute;\nbackground: var(--line-color,rgb(var(--v-theme-primary)));\nopacity: 0.45;\nwidth: 100%;\nheight: 50%;\nborder-radius:inherit;\n}\n.ag-track-foreground{\nposition:absolute;\nbackground-color: var(--line-color,rgb(var(--v-theme-primary)));\nwidth: 50%;\nheight: 100%;\nmax-width: 100%;\nborder-radius:inherit;\ntransition:inherit;\n}\n</style>",
        "storeOutMessages": true,
        "passthru": true,
        "resendOnRefresh": true,
        "templateScope": "local",
        "className": "",
        "x": 650,
        "y": 220,
        "wires": [
            []
        ]
    },
    {
        "id": "f999d4f28598e2d0",
        "type": "ui-template",
        "z": "51ef46c8e45f5993",
        "group": "1e0364d1c4ba9e0d",
        "page": "",
        "ui": "",
        "name": "Listeria",
        "order": 1,
        "width": "3",
        "height": "1",
        "head": "",
        "format": "<template>\n    <div ref=\"hng\" :class=\"icon ? 'ag-wrapper-2' : 'ag-wrapper-1'\" :style=\"`--line-color:${colors[0]};`\">\n        <div v-if=\"icon\" class=\"ag-icon\">\n            <v-icon aria-hidden=\"false\">{{icon}}</v-icon>\n        </div>\n        <div class=\"ag-content\">\n            <div class=\"ag-text\">\n                <span class=\"ag-label\">{{label}}</span>\n                <span class=\"ag-value\">{{formattedValue}}<span class=\"ag-unit\">{{unit}}</span></span>\n            </div>\n            <div class=\"ag-track\" ref=\"agLine\">\n                <div class=\"ag-track-background\"></div>\n                <div class=\"ag-track-foreground\" :style=\"{'width': linesize +'%'}\"></div>\n            </div>\n            <div class=\"ag-limits\">\n                <span class=\"ag-min\">{{min}}</span>\n                <span class=\"ag-max\">{{max}}</span>\n            </div>\n        </div>\n    </div>\n</template>\n\n<script>\n    export default {\n    data(){\n        return {\n            //Define me here\n                                             \n            label:\"Listeria\", // The label\n            //icon:\"mdi-account\", // (type: artless) (optional) the icon\n            zeroCross:false,// (type: artless) line changes color depending on value being positive or negative (at least 2 colors must be defined)\n            min:0, // Smallest expected value\n            max:8, // Highest expected value\n            unit:\"log10\",// The unit of the measurement           \n            animate:true, // Animating led's is not most performant thing in the world.                          \n            \n            // Define colors           \n            colors:[\n                    \"red\",\n                    \"red\",   \n                    \"red\",\n                    \"red\",                                                         \n                    \"orange\",\n                    \"orange\",\n                    \"orange\",\n                    \"orange\",\n                    \"#0fb60f\"\n                   ],            \n            \n            //no need to change those\n            value:0,          \n            inited:false\n        }\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        validate(data){\n            let ret\n            if(typeof data !== \"number\"){\n                ret = parseFloat(data)\n                if(isNaN(ret)){\n                    console.log(\"BAD DATA! gauge type:\",this.type, \"id:\",this.id,\"data:\",data)\n                    return null\n                }   \n            }\n            else{\n                ret = data\n            }            \n            return ret\n        },\n        changeLine:function(){\n            const line = this.getElement(\"agLine\",true);\n            if(!line){\n                console.log(\"no line found\")\n                return            \n            }\n           \n            let c = Math.floor(this.colors.length * this.percentage / 100)\n            if(c >= this.colors.length){\n                c = this.colors.length - 1\n            }\n            if(c < 0){\n                c = 0\n            }\n            if(this.zeroCross){\n                c = this.value > 0 ? (this.colors.length - 1) : 0\n            }\n            line.style.setProperty('--line-color',this.colors[c])\n\n        }\n    },\n       \n    watch: {\n        msg: function(){    \n            if(this.msg.payload !== undefined){  \n                const v = this.validate(this.msg.payload)                \n                if(v === null){\n                    return\n                }         \n                this.value = v\n                this.changeLine()              \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        linesize:function(){\n            if(this.zeroCross){\n                return Math.floor(((Math.abs(this.value) - this.min) / (this.max - this.min)) * 100);           \n            }\n            else{\n                return Math.max(0,this.percentage)\n            }\n        }\n    },\n    mounted(){\n        const line = this.getElement(\"agLine\",true);\n        line.style.setProperty('--line-color',this.colors[0])\n        if(this.animate == true){\n            if(!line){\n                console.log(\"artless init() no line found\")\n                return\n            }\n            line.style.transition = \"width 0.5s\";\n        }\n       \n        this.inited = true;\n    }\n}\n</script>\n\n<style>\n.ag-wrapper-2 {\ndisplay: grid;\ngrid-template-columns: 3em 1fr;\ngap:1em;\n}\n.ag-wrapper-1 {\ndisplay: grid;\ngrid-template-columns: 1fr;\n}\n.ag-icon{\nfont-size: 2em;\ndisplay: flex;\nflex-direction: column;\njustify-content: center;\n}\n.ag-content{\ndisplay: grid;\ngrid-template-rows: 1fr 7px 0.75em;\ngap: 2px;\n}\n.ag-text{\nfont-size: 1.25em;\nline-height: 1em;\nalign-self: end;\ndisplay: flex;\nflex-wrap: wrap;\njustify-content: space-between;\nuser-select: none;\n}\n.ag-value{\nfont-weight:bold;\n}\n.ag-unit{\nfont-size:.75em;\nfont-weight:normal;\npadding-inline-start: 0.15em;\n}\n.ag-limits{\ndisplay: flex;\njustify-content: space-between;\nfont-size: .75em;\nline-height: .75em;\nalign-content: center;\nflex-wrap: wrap;\nuser-select: none;\n}\n\n.ag-track{\nposition:relative;\ndisplay:flex;\nalign-items: center;\nwidth: 100%;\nborder-radius: 6px;\n}\n\n.ag-track-background{\nposition:absolute;\nbackground: var(--line-color,rgb(var(--v-theme-primary)));\nopacity: 0.45;\nwidth: 100%;\nheight: 50%;\nborder-radius:inherit;\n}\n.ag-track-foreground{\nposition:absolute;\nbackground-color: var(--line-color,rgb(var(--v-theme-primary)));\nwidth: 50%;\nheight: 100%;\nmax-width: 100%;\nborder-radius:inherit;\ntransition:inherit;\n}\n</style>",
        "storeOutMessages": true,
        "passthru": true,
        "resendOnRefresh": true,
        "templateScope": "local",
        "className": "",
        "x": 660,
        "y": 260,
        "wires": [
            []
        ]
    },
    {
        "id": "48106afca278ac11",
        "type": "ui-group",
        "name": "T1",
        "page": "4f1edd9ca629751a",
        "width": "3",
        "height": "3",
        "order": 1,
        "showTitle": false,
        "className": "",
        "visible": "true",
        "disabled": "false"
    },
    {
        "id": "1e0364d1c4ba9e0d",
        "type": "ui-group",
        "name": "Bakteerit",
        "page": "4f1edd9ca629751a",
        "width": "3",
        "height": "3",
        "order": 2,
        "showTitle": false,
        "className": "",
        "visible": "true",
        "disabled": "false"
    },
    {
        "id": "4f1edd9ca629751a",
        "type": "ui-page",
        "name": "testi",
        "ui": "e7bb0a62b3d9dd57",
        "path": "/page1",
        "icon": "home",
        "layout": "grid",
        "theme": "4d024ff372f88d1f",
        "order": -1,
        "className": "",
        "visible": "true",
        "disabled": "false"
    },
    {
        "id": "e7bb0a62b3d9dd57",
        "type": "ui-base",
        "name": "UI Name",
        "path": "/dashboard",
        "includeClientData": true,
        "acceptsClientConfig": [
            "ui-notification",
            "ui-control"
        ],
        "showPathInSidebar": false
    },
    {
        "id": "4d024ff372f88d1f",
        "type": "ui-theme",
        "name": "Theme Name",
        "colors": {
            "surface": "#979191",
            "primary": "#0094ce",
            "bgPage": "#eeeeee",
            "groupBg": "#ffffff",
            "groupOutline": "#cccccc"
        },
        "sizes": {
            "pagePadding": "12px",
            "groupGap": "12px",
            "groupBorderRadius": "4px",
            "widgetGap": "12px"
        }
    }
]

It was arrangements for things in single group but yeah. None of currently available layout options have fixed or at least common row heights. That way it is not possible to have similarities over cards. Thus the dashboard as whole is not pice of art but as the result of coincidences.

I shared an option to force the heights of rows to be locked here

It is not tested against different widgets or layout options, just a snippet to play with. Who knows, it may be useful. I don't have time enough to go through all it takes to say something strong.

This topic was automatically closed 30 days after the last reply. New replies are no longer allowed.