From 5a06b39f510515333000b90afd27268f2dc65512 Mon Sep 17 00:00:00 2001 From: Noley Holland Date: Thu, 20 Aug 2026 08:32:05 -0700 Subject: [PATCH 1/2] Auto gauge-34 sizes to its cell instead of overflowing a collapsed row --- .../flows/dashboard-gauge-autosize.json | 167 ++++++++++++++++++ cypress/tests/widgets/gauge-autosize.spec.js | 18 ++ ui/src/layouts/Group.vue | 3 + ui/src/widgets/ui-gauge/types/UIGaugeDial.vue | 7 +- 4 files changed, 194 insertions(+), 1 deletion(-) create mode 100644 cypress/fixtures/flows/dashboard-gauge-autosize.json create mode 100644 cypress/tests/widgets/gauge-autosize.spec.js diff --git a/cypress/fixtures/flows/dashboard-gauge-autosize.json b/cypress/fixtures/flows/dashboard-gauge-autosize.json new file mode 100644 index 000000000..5855d5809 --- /dev/null +++ b/cypress/fixtures/flows/dashboard-gauge-autosize.json @@ -0,0 +1,167 @@ +[ + { + "id": "gauge-autosize-tab", + "type": "tab", + "label": "Gauge Autosize", + "disabled": false, + "info": "" + }, + { + "id": "dashboard-ui-base", + "type": "ui-base", + "name": "UI Name", + "path": "/dashboard", + "includeClientData": true, + "acceptsClientConfig": ["ui-notification", "ui-control"] + }, + { + "id": "dashboard-ui-theme", + "type": "ui-theme", + "name": "Theme", + "colors": { + "surface": "#ffffff", + "primary": "#0094ce", + "bgPage": "#eeeeee", + "groupBg": "#ffffff", + "groupOutline": "#cccccc" + } + }, + { + "id": "dashboard-ui-page-1", + "type": "ui-page", + "name": "Page 1", + "ui": "dashboard-ui-base", + "path": "/page1", + "icon": "home", + "layout": "grid", + "theme": "dashboard-ui-theme", + "order": 1, + "className": "", + "visible": "true", + "disabled": false + }, + { + "id": "dashboard-ui-group", + "type": "ui-group", + "name": "Gauges", + "page": "dashboard-ui-page-1", + "width": "6", + "height": "1", + "order": 1, + "showTitle": true, + "className": "", + "visible": "true", + "disabled": "false" + }, + { + "id": "dashboard-ui-gauge-auto", + "type": "ui-gauge", + "z": "gauge-autosize-tab", + "group": "dashboard-ui-group", + "name": "auto", + "order": 1, + "value": "payload", + "valueType": "msg", + "width": "3", + "height": "0", + "gtype": "gauge-34", + "gstyle": "needle", + "title": "auto", + "alwaysShowTitle": true, + "floatingTitlePosition": "top-left", + "units": "%", + "icon": "", + "prefix": "", + "suffix": "", + "segments": [ + { "from": "0", "color": "#53a51c", "text": "", "textType": "label" }, + { "from": "60", "color": "#f6c000", "text": "", "textType": "label" }, + { "from": "80", "color": "#ca3838", "text": "", "textType": "label" } + ], + "min": "0", + "max": "100", + "sizeThickness": "16", + "sizeGap": "4", + "sizeKeyThickness": "8", + "styleRounded": true, + "styleGlow": false, + "className": "", + "x": 400, + "y": 100, + "wires": [[]] + }, + { + "id": "dashboard-ui-gauge-fixed", + "type": "ui-gauge", + "z": "gauge-autosize-tab", + "group": "dashboard-ui-group", + "name": "fixed", + "order": 2, + "value": "payload", + "valueType": "msg", + "width": "3", + "height": "4", + "gtype": "gauge-34", + "gstyle": "needle", + "title": "fixed", + "alwaysShowTitle": true, + "floatingTitlePosition": "top-left", + "units": "%", + "icon": "", + "prefix": "", + "suffix": "", + "segments": [ + { "from": "0", "color": "#53a51c", "text": "", "textType": "label" }, + { "from": "60", "color": "#f6c000", "text": "", "textType": "label" }, + { "from": "80", "color": "#ca3838", "text": "", "textType": "label" } + ], + "min": "0", + "max": "100", + "sizeThickness": "16", + "sizeGap": "4", + "sizeKeyThickness": "8", + "styleRounded": true, + "styleGlow": false, + "className": "", + "x": 400, + "y": 160, + "wires": [[]] + }, + { + "id": "dashboard-ui-gauge-half-auto", + "type": "ui-gauge", + "z": "gauge-autosize-tab", + "group": "dashboard-ui-group", + "name": "half-auto", + "order": 3, + "value": "payload", + "valueType": "msg", + "width": "3", + "height": "0", + "gtype": "gauge-half", + "gstyle": "needle", + "title": "half-auto", + "alwaysShowTitle": true, + "floatingTitlePosition": "top-left", + "units": "%", + "icon": "", + "prefix": "", + "suffix": "", + "segments": [ + { "from": "0", "color": "#53a51c", "text": "", "textType": "label" }, + { "from": "60", "color": "#f6c000", "text": "", "textType": "label" }, + { "from": "80", "color": "#ca3838", "text": "", "textType": "label" } + ], + "min": "0", + "max": "100", + "sizeThickness": "16", + "sizeGap": "4", + "sizeKeyThickness": "8", + "styleRounded": true, + "styleGlow": false, + "className": "", + "x": 400, + "y": 220, + "wires": [[]] + } +] diff --git a/cypress/tests/widgets/gauge-autosize.spec.js b/cypress/tests/widgets/gauge-autosize.spec.js new file mode 100644 index 000000000..c29c7f9e5 --- /dev/null +++ b/cypress/tests/widgets/gauge-autosize.spec.js @@ -0,0 +1,18 @@ +describe('Node-RED Dashboard 2.0 - Gauge auto-height (#1875)', () => { + beforeEach(() => { + cy.deployFixture('dashboard-gauge-autosize') + cy.visit('/dashboard/page1') + }) + + it('gives an auto-height gauge the default height instead of collapsing to one row', () => { + cy.get('#nrdb-ui-widget-dashboard-ui-gauge-auto').children().eq(0).should('have.css', 'grid-row-end', 'span 3') + }) + + it('leaves a fixed-height gauge unchanged', () => { + cy.get('#nrdb-ui-widget-dashboard-ui-gauge-fixed').children().eq(0).should('have.css', 'grid-row-end', 'span 4') + }) + + it('does not force a default height on an auto half-gauge (fix is scoped to gauge-34)', () => { + cy.get('#nrdb-ui-widget-dashboard-ui-gauge-half-auto').children().eq(0).should('not.have.css', 'grid-row-end', 'span 3') + }) +}) diff --git a/ui/src/layouts/Group.vue b/ui/src/layouts/Group.vue index 4d5f43fac..2f568b560 100644 --- a/ui/src/layouts/Group.vue +++ b/ui/src/layouts/Group.vue @@ -136,6 +136,9 @@ export default { if (widget.type === 'ui-chart') { return 8 } + if (widget.type === 'ui-gauge' && widget.props.gtype === 'gauge-34') { + return 3 + } } return height }, diff --git a/ui/src/widgets/ui-gauge/types/UIGaugeDial.vue b/ui/src/widgets/ui-gauge/types/UIGaugeDial.vue index 266f21656..d526d0ce4 100644 --- a/ui/src/widgets/ui-gauge/types/UIGaugeDial.vue +++ b/ui/src/widgets/ui-gauge/types/UIGaugeDial.vue @@ -136,7 +136,12 @@ export default { this.sizes.titleHeight = this.$refs.title ? this.$refs.title.clientHeight : 0 this.width = this.$refs.container.clientWidth - this.height = this.props.height ? this.$refs.container.clientHeight - this.sizes.titleHeight : (this.props.gtype === 'gauge-half' ? 150 : 300) + const available = this.$refs.container.clientHeight - this.sizes.titleHeight + if (this.props.gtype === 'gauge-34' && !this.props.height) { + this.height = available > 0 ? available : 300 + } else { + this.height = this.props.height ? available : (this.props.gtype === 'gauge-half' ? 150 : 300) + } // heights for the SVG const w = this.width From c6cc6262973b38cd69b9ebb81dc86296f9d225e7 Mon Sep 17 00:00:00 2001 From: Noley Holland Date: Thu, 20 Aug 2026 12:27:42 -0700 Subject: [PATCH 2/2] Update capped gauges to pivot off the arc center to align dial --- ui/src/widgets/ui-gauge/types/UIGaugeDial.vue | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/ui/src/widgets/ui-gauge/types/UIGaugeDial.vue b/ui/src/widgets/ui-gauge/types/UIGaugeDial.vue index d526d0ce4..2f0f2c5ca 100644 --- a/ui/src/widgets/ui-gauge/types/UIGaugeDial.vue +++ b/ui/src/widgets/ui-gauge/types/UIGaugeDial.vue @@ -162,7 +162,8 @@ export default { const limit = this.$refs['limits-min'] const labelRoom = limit ? Math.ceil(limit.getBBox().height) : 16 - if (gaugeArea + labelRoom < h) { + this.capped = gaugeArea + labelRoom < h + if (this.capped) { this.height = gaugeArea this.$refs.gauge.style.flexGrow = '0' this.$refs.gauge.setAttribute('height', gaugeArea + labelRoom) @@ -334,7 +335,13 @@ export default { // this.svg.select('#needle-container') this.svg.select('#needle-container') + .style('transform-box', this.capped ? 'view-box' : null) .style('transform-origin', () => { + if (this.capped) { + const cx = this.width / 2 + const cy = this.props.gtype === 'gauge-half' ? this.height + this.sizes.fudge : this.height / 2 + return `${cx}px ${cy}px` + } return this.sizes.angle > Math.PI ? 'center center' : 'center bottom' }) .transition().duration(duration) @@ -347,7 +354,8 @@ export default { const rotate = d3.interpolate(start, end)(t) const deg = rotate * (180 / Math.PI) // -6 is fudge factor to ensure needle is visible and doesn't have half hanging out of SVG window - return `translate(0, ${vue.props.gtype === 'gauge-half' ? vue.sizes.fudge.toString() : '0'}px)rotate(${deg}deg)` + const yOffset = (vue.props.gtype === 'gauge-half' && !vue.capped) ? vue.sizes.fudge : 0 + return `translate(0, ${yOffset}px)rotate(${deg}deg)` } return tween })