From 6f6ba71e61a2b97f603684fe38e272e9e74228d7 Mon Sep 17 00:00:00 2001 From: Simon Lamon <32477463+silamon@users.noreply.github.com> Date: Wed, 11 Mar 2026 10:22:44 +0100 Subject: [PATCH] Redesign gauge card (#29981) * Redesign gauge card * Fix calculation * New design, code needs optimization (ai) --- gallery/src/pages/lovelace/gauge-card.ts | 13 ++ src/components/ha-gauge.ts | 230 ++++++++++++-------- src/panels/lovelace/cards/hui-gauge-card.ts | 2 - 3 files changed, 147 insertions(+), 98 deletions(-) diff --git a/gallery/src/pages/lovelace/gauge-card.ts b/gallery/src/pages/lovelace/gauge-card.ts index c9989692cc..c79a3def95 100644 --- a/gallery/src/pages/lovelace/gauge-card.ts +++ b/gallery/src/pages/lovelace/gauge-card.ts @@ -54,6 +54,19 @@ const CONFIGS = [ needle: true `, }, + { + heading: "Rendering needle and severity levels", + config: ` +- type: gauge + entity: sensor.brightness_high + name: Brightness High + needle: true + severity: + red: 75 + green: 0 + yellow: 50 + `, + }, { heading: "Setting severity levels", config: ` diff --git a/src/components/ha-gauge.ts b/src/components/ha-gauge.ts index 55a00def00..a12552db8c 100644 --- a/src/components/ha-gauge.ts +++ b/src/components/ha-gauge.ts @@ -44,16 +44,16 @@ export class HaGauge extends LitElement { @state() private _updated = false; - @state() private _segment_label? = ""; + @state() private _segment_label?: string = ""; protected firstUpdated(changedProperties: PropertyValues) { super.firstUpdated(changedProperties); - // Wait for the first render for the initial animation to work afterNextRender(() => { this._updated = true; - this._angle = getAngle(this.value, this.min, this.max); + if (this.needle) { + this._angle = getAngle(this.value, this.min, this.max); + } this._segment_label = this._getSegmentLabel(); - this._rescaleSvg(); }); } @@ -70,70 +70,121 @@ export class HaGauge extends LitElement { } this._angle = getAngle(this.value, this.min, this.max); this._segment_label = this._getSegmentLabel(); - this._rescaleSvg(); } protected render() { + const arcRadius = 40; + const arcLength = Math.PI * arcRadius; + const valueAngle = getAngle(this.value, this.min, this.max); + const strokeOffset = arcLength * (1 - valueAngle / 180); + return svg` - - ${ - !this.needle || !this.levels - ? svg` + ` - : "" - } + /> + + + ${ + this.levels + ? [...this.levels] + .sort((a, b) => a.level - b.level) + .map((level, i, arr) => { + const startLevel = i === 0 ? this.min : arr[i].level; + const endLevel = i + 1 < arr.length ? arr[i + 1].level : this.max; + + const startAngle = getAngle(startLevel, this.min, this.max); + const endAngle = getAngle(endLevel, this.min, this.max); + const largeArc = endAngle - startAngle > 180 ? 1 : 0; + + const x1 = -arcRadius * Math.cos((startAngle * Math.PI) / 180); + const y1 = -arcRadius * Math.sin((startAngle * Math.PI) / 180); + const x2 = -arcRadius * Math.cos((endAngle * Math.PI) / 180); + const y2 = -arcRadius * Math.sin((endAngle * Math.PI) / 180); + + const firstSegment = i === 0; + const lastSegment = i === arr.length - 1; + + const paths: TemplateResult[] = []; + + if (firstSegment) { + paths.push(svg` + + `); + } else if (lastSegment) { + const offsetAngle = 0.5; + const midAngle = endAngle - offsetAngle; + const xm = -arcRadius * Math.cos((midAngle * Math.PI) / 180); + const ym = -arcRadius * Math.sin((midAngle * Math.PI) / 180); + + paths.push(svg` + + `); + + paths.push(svg` + + `); + } else { + paths.push(svg` + + `); + } + + return paths; + }) + : "" + } - ${ - this.levels - ? this.levels - .sort((a, b) => a.level - b.level) - .map((level, idx) => { - let firstPath: TemplateResult | undefined; - if (idx === 0 && level.level !== this.min) { - const angle = getAngle(this.min, this.min, this.max); - firstPath = svg``; - } - const angle = getAngle(level.level, this.min, this.max); - return svg`${firstPath}`; - }) - : "" - } ${ this.needle - ? svg` + ? svg` + + ` + : svg` + ` - : svg`` } - - - - + + ${ this._segment_label ? this._segment_label @@ -147,24 +198,13 @@ export class HaGauge extends LitElement { : ` ${this.label}` } - `; - } - - private _rescaleSvg() { - // Set the viewbox of the SVG containing the value to perfectly - // fit the text - // That way it will auto-scale correctly - const svgRoot = this.shadowRoot!.querySelector(".text")!; - const box = svgRoot.querySelector("text")!.getBBox()!; - svgRoot.setAttribute( - "viewBox", - `${box.x} ${box!.y} ${box.width} ${box.height}` - ); + + `; } private _getSegmentLabel() { if (this.levels) { - this.levels.sort((a, b) => a.level - b.level); + [...this.levels].sort((a, b) => a.level - b.level); for (let i = this.levels.length - 1; i >= 0; i--) { if (this.value >= this.levels[i].level) { return this.levels[i].label; @@ -178,40 +218,38 @@ export class HaGauge extends LitElement { :host { position: relative; } - .dial { + + .levels-base { fill: none; stroke: var(--primary-background-color); - stroke-width: 15; - } - .value { - fill: none; - stroke-width: 15; - stroke: var(--gauge-color); - transition: all 1s ease 0s; - } - .needle { - fill: var(--primary-text-color); - transition: all 1s ease 0s; + stroke-width: 10; + stroke-linecap: round; } + .level { fill: none; - stroke-width: 15; + stroke-width: 10; + stroke-linecap: butt; } - .gauge { - display: block; + + .value { + fill: none; + stroke-width: 10; + stroke: var(--gauge-color); + stroke-linecap: round; + transition: all 1s ease 0s; } - .text { - position: absolute; - max-height: 40%; - max-width: 55%; - left: 50%; - bottom: -6%; - transform: translate(-50%, 0%); + + .needle { + stroke: var(--primary-text-color); + stroke-width: 2; + stroke-linecap: round; + transform-origin: 0 0; + transition: all 1s ease 0s; } + .value-text { - font-size: 50px; fill: var(--primary-text-color); - text-anchor: middle; direction: ltr; } `; diff --git a/src/panels/lovelace/cards/hui-gauge-card.ts b/src/panels/lovelace/cards/hui-gauge-card.ts index 28b8ee9368..e871af677f 100644 --- a/src/panels/lovelace/cards/hui-gauge-card.ts +++ b/src/panels/lovelace/cards/hui-gauge-card.ts @@ -303,7 +303,6 @@ class HuiGaugeCard extends LitElement implements LovelaceCard { ha-gauge { width: 100%; - max-width: 250px; } .name { @@ -312,7 +311,6 @@ class HuiGaugeCard extends LitElement implements LovelaceCard { color: var(--primary-text-color); width: 100%; font-size: var(--ha-font-size-m); - margin-top: 8px; } `; }