1
0
mirror of https://github.com/home-assistant/frontend.git synced 2026-04-18 07:56:44 +01:00

Gauge improvements (#30368)

* Gauge last improvements

* Change needle

* Fixup

* Feedback comments

* Update src/components/ha-gauge.ts

---------

Co-authored-by: Petar Petrov <MindFreeze@users.noreply.github.com>
This commit is contained in:
Simon Lamon
2026-03-31 13:27:22 +02:00
committed by GitHub
parent bb57a91494
commit 70be747e9d
3 changed files with 125 additions and 80 deletions

View File

@@ -134,6 +134,21 @@ const CONFIGS = [
entity: sensor.not_working entity: sensor.not_working
`, `,
}, },
{
heading: "Lower minimum",
config: `
- type: gauge
entity: sensor.brightness_high
needle: true
severity:
green: 0
yellow: 0.45
red: 0.9
min: -0.05
name: " "
max: 1.9
unit: GBP/h`,
},
]; ];
@customElement("demo-lovelace-gauge-card") @customElement("demo-lovelace-gauge-card")

View File

@@ -1,4 +1,4 @@
import type { PropertyValues, TemplateResult } from "lit"; import type { PropertyValues } from "lit";
import { css, LitElement, svg } from "lit"; import { css, LitElement, svg } from "lit";
import { customElement, property, state } from "lit/decorators"; import { customElement, property, state } from "lit/decorators";
import { styleMap } from "lit/directives/style-map"; import { styleMap } from "lit/directives/style-map";
@@ -54,6 +54,7 @@ export class HaGauge extends LitElement {
this._angle = getAngle(this.value, this.min, this.max); this._angle = getAngle(this.value, this.min, this.max);
} }
this._segment_label = this._getSegmentLabel(); this._segment_label = this._getSegmentLabel();
this._rescaleSvg();
}); });
} }
@@ -70,6 +71,7 @@ export class HaGauge extends LitElement {
} }
this._angle = getAngle(this.value, this.min, this.max); this._angle = getAngle(this.value, this.min, this.max);
this._segment_label = this._getSegmentLabel(); this._segment_label = this._getSegmentLabel();
this._rescaleSvg();
} }
protected render() { protected render() {
@@ -88,87 +90,91 @@ export class HaGauge extends LitElement {
/> />
${ ${
this.levels this.levels
? [...this.levels] ? (() => {
.sort((a, b) => a.level - b.level) const sortedLevels = [...this.levels].sort(
.map((level, i, arr) => { (a, b) => a.level - b.level
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); if (
const endAngle = getAngle(endLevel, this.min, this.max); sortedLevels.length > 0 &&
const largeArc = endAngle - startAngle > 180 ? 1 : 0; sortedLevels[0].level !== this.min
) {
sortedLevels.unshift({
level: this.min,
stroke: "var(--info-color)",
});
}
const x1 = -arcRadius * Math.cos((startAngle * Math.PI) / 180); return sortedLevels.map((level, i, arr) => {
const y1 = -arcRadius * Math.sin((startAngle * Math.PI) / 180); const startLevel = level.level;
const x2 = -arcRadius * Math.cos((endAngle * Math.PI) / 180); const endLevel =
const y2 = -arcRadius * Math.sin((endAngle * Math.PI) / 180); i + 1 < arr.length ? arr[i + 1].level : this.max;
const firstSegment = i === 0; const startAngle = getAngle(startLevel, this.min, this.max);
const lastSegment = i === arr.length - 1; const endAngle = getAngle(endLevel, this.min, this.max);
const largeArc = endAngle - startAngle > 180 ? 1 : 0;
const paths: TemplateResult[] = []; 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);
if (firstSegment) { const isFirst = i === 0;
paths.push(svg` const isLast = i === arr.length - 1;
<path
class="level"
stroke="${level.stroke}"
style="stroke-linecap: round"
d="M ${x1} ${y1} A ${arcRadius} ${arcRadius} 0 ${largeArc} 1 ${x2} ${y2}"
/>
`);
} 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` if (isFirst) {
<path return svg`
class="level" <path
stroke="${level.stroke}" class="level"
style="stroke-linecap: butt" stroke="${level.stroke}"
d="M ${x1} ${y1} A ${arcRadius} ${arcRadius} 0 ${largeArc} 1 ${xm} ${ym}" style="stroke-linecap: butt"
/> d="M ${x1} ${y1} A ${arcRadius} ${arcRadius} 0 ${largeArc} 1 ${x2} ${y2}"
`); />
`;
}
paths.push(svg` if (isLast) {
<path const offsetAngle = 0.5;
class="level" const midAngle = endAngle - offsetAngle;
stroke="${level.stroke}" const xm =
style="stroke-linecap: round" -arcRadius * Math.cos((midAngle * Math.PI) / 180);
d="M ${xm} ${ym} A ${arcRadius} ${arcRadius} 0 0 1 ${x2} ${y2}" const ym =
/> -arcRadius * Math.sin((midAngle * Math.PI) / 180);
`);
} else {
paths.push(svg`
<path
class="level"
stroke="${level.stroke}"
style="stroke-linecap: butt"
d="M ${x1} ${y1} A ${arcRadius} ${arcRadius} 0 ${largeArc} 1 ${x2} ${y2}"
/>
`);
}
return paths; return svg`
}) <path class="level" stroke="${level.stroke}" style="stroke-linecap: butt"
: "" d="M ${x1} ${y1} A ${arcRadius} ${arcRadius} 0 ${largeArc} 1 ${xm} ${ym}" />
} <path class="level" stroke="${level.stroke}" style="stroke-linecap: butt"
d="M ${xm} ${ym} A ${arcRadius} ${arcRadius} 0 0 1 ${x2} ${y2}" />
`;
}
return svg`
<path
class="level"
stroke="${level.stroke}"
style="stroke-linecap: butt"
d="M ${x1} ${y1} A ${arcRadius} ${arcRadius} 0 ${largeArc} 1 ${x2} ${y2}"
></path>
`;
});
})()
: ""
}
${ ${
this.needle this.needle
? svg` ? svg`
<line <path
class="needle" class="needle"
x1="-35.0" d="M -36,-2 L -44,-1 A 1,1,0,0,0,-44,1 L -36,2 A 2,2,0,0,0,-36,-2 Z"
y1="0"
x2="-45.0" style=${styleMap({ transform: `rotate(${this._angle}deg)` })}
y2="0" />
style=${styleMap({ transform: `rotate(${this._angle}deg)` })}
/>
` `
: svg` : svg`
<path <path
@@ -179,7 +185,8 @@ export class HaGauge extends LitElement {
/> />
` `
} }
</svg>
<svg class="text">
<text <text
class="value-text" class="value-text"
x="0" x="0"
@@ -204,6 +211,18 @@ export class HaGauge extends LitElement {
`; `;
} }
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() { private _getSegmentLabel() {
if (this.levels) { if (this.levels) {
[...this.levels].sort((a, b) => a.level - b.level); [...this.levels].sort((a, b) => a.level - b.level);
@@ -224,32 +243,43 @@ export class HaGauge extends LitElement {
.levels-base { .levels-base {
fill: none; fill: none;
stroke: var(--primary-background-color); stroke: var(--primary-background-color);
stroke-width: 8; stroke-width: 6;
stroke-linecap: round; stroke-linecap: butt;
} }
.level { .level {
fill: none; fill: none;
stroke-width: 8; stroke-width: 6;
stroke-linecap: butt; stroke-linecap: butt;
} }
.value { .value {
fill: none; fill: none;
stroke-width: 8; stroke-width: 6;
stroke: var(--gauge-color); stroke: var(--gauge-color);
stroke-linecap: round; stroke-linecap: butt;
transition: stroke-dashoffset 1s ease 0s; transition: stroke-dashoffset 1s ease 0s;
} }
.needle { .needle {
stroke: var(--primary-text-color); fill: var(--primary-text-color);
stroke-width: 2; stroke: var(--card-background-color);
color: var(--primary-text-color);
stroke-width: 1;
stroke-linecap: round; stroke-linecap: round;
transform-origin: 0 0; transform-origin: 0 0;
transition: all 1s ease 0s; transition: all 1s ease 0s;
} }
.text {
position: absolute;
max-height: 40%;
max-width: 55%;
left: 50%;
bottom: 10%;
transform: translate(-50%, 0%);
}
.value-text { .value-text {
font-size: var(--ha-font-size-l); font-size: var(--ha-font-size-l);
fill: var(--primary-text-color); fill: var(--primary-text-color);

View File

@@ -303,9 +303,8 @@ class HuiGaugeCard extends LitElement implements LovelaceCard {
.title { .title {
width: 100%; width: 100%;
font-size: var(--ha-font-size-l); font-size: var(--ha-font-size-m);
line-height: var(--ha-line-height-expanded); line-height: var(--ha-line-height-expanded);
padding: 0px 0px var(--ha-space-2) 0px;
margin: 0; margin: 0;
text-align: center; text-align: center;
box-sizing: border-box; box-sizing: border-box;
@@ -318,6 +317,7 @@ class HuiGaugeCard extends LitElement implements LovelaceCard {
ha-gauge { ha-gauge {
width: 100%; width: 100%;
max-width: 250px;
} }
`; `;
} }