Short-format numbers in energy-distribution-card (#24716)

This commit is contained in:
karwosts 2025-03-24 04:43:11 -07:00 committed by GitHub
parent 6fbc7b2efe
commit 9f05f4df50
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 163 additions and 52 deletions

View File

@ -29,6 +29,7 @@ import type {
import { fetchStatistics, getStatisticMetadata } from "./recorder"; import { fetchStatistics, getStatisticMetadata } from "./recorder";
import { calcDateRange } from "../common/datetime/calc_date_range"; import { calcDateRange } from "../common/datetime/calc_date_range";
import type { DateRange } from "../common/datetime/calc_date_range"; import type { DateRange } from "../common/datetime/calc_date_range";
import { formatNumber } from "../common/number/format_number";
const energyCollectionKeys: (string | undefined)[] = []; const energyCollectionKeys: (string | undefined)[] = [];
@ -924,3 +925,31 @@ const computeConsumptionDataPartial = (
return outData; return outData;
}; };
export const formatConsumptionShort = (
hass: HomeAssistant,
consumption: number | null,
unit: string
): string => {
if (!consumption) {
return `0 ${unit}`;
}
const units = ["kWh", "MWh", "GWh", "TWh"];
let pickedUnit = unit;
let val = consumption;
let unitIndex = units.findIndex((u) => u === unit);
if (unitIndex >= 0) {
while (val >= 1000 && unitIndex < units.length - 1) {
val /= 1000;
unitIndex++;
}
pickedUnit = units[unitIndex];
}
return (
formatNumber(val, hass.locale, {
maximumFractionDigits: val < 10 ? 2 : val < 100 ? 1 : 0,
}) +
" " +
pickedUnit
);
};

View File

@ -17,7 +17,6 @@ import type { PropertyValues } from "lit";
import { css, html, LitElement, svg, nothing } from "lit"; import { css, html, LitElement, svg, nothing } from "lit";
import { customElement, property, state } from "lit/decorators"; import { customElement, property, state } from "lit/decorators";
import { classMap } from "lit/directives/class-map"; import { classMap } from "lit/directives/class-map";
import { formatNumber } from "../../../../common/number/format_number";
import "../../../../components/ha-card"; import "../../../../components/ha-card";
import "../../../../components/ha-svg-icon"; import "../../../../components/ha-svg-icon";
import type { EnergyData } from "../../../../data/energy"; import type { EnergyData } from "../../../../data/energy";
@ -26,6 +25,7 @@ import {
getEnergyDataCollection, getEnergyDataCollection,
getEnergyGasUnit, getEnergyGasUnit,
getEnergyWaterUnit, getEnergyWaterUnit,
formatConsumptionShort,
} from "../../../../data/energy"; } from "../../../../data/energy";
import { calculateStatisticsSumGrowth } from "../../../../data/recorder"; import { calculateStatisticsSumGrowth } from "../../../../data/recorder";
import { SubscribeMixin } from "../../../../mixins/subscribe-mixin"; import { SubscribeMixin } from "../../../../mixins/subscribe-mixin";
@ -308,10 +308,11 @@ class HuiEnergyDistrubutionCard
rel="noopener no referrer" rel="noopener no referrer"
> >
<ha-svg-icon .path=${mdiLeaf}></ha-svg-icon> <ha-svg-icon .path=${mdiLeaf}></ha-svg-icon>
${formatNumber(lowCarbonEnergy || 0, this.hass.locale, { ${formatConsumptionShort(
maximumFractionDigits: 1, this.hass,
})} lowCarbonEnergy,
kWh "kWh"
)}
</a> </a>
<svg width="80" height="30"> <svg width="80" height="30">
<line x1="40" y1="0" x2="40" y2="30"></line> <line x1="40" y1="0" x2="40" y2="30"></line>
@ -326,12 +327,11 @@ class HuiEnergyDistrubutionCard
> >
<div class="circle"> <div class="circle">
<ha-svg-icon .path=${mdiSolarPower}></ha-svg-icon> <ha-svg-icon .path=${mdiSolarPower}></ha-svg-icon>
${formatNumber( ${formatConsumptionShort(
totalSolarProduction || 0, this.hass,
this.hass.locale, totalSolarProduction,
{ maximumFractionDigits: 1 } "kWh"
)} )}
kWh
</div> </div>
</div>` </div>`
: hasGas || hasWater : hasGas || hasWater
@ -346,13 +346,14 @@ class HuiEnergyDistrubutionCard
> >
<div class="circle"> <div class="circle">
<ha-svg-icon .path=${mdiFire}></ha-svg-icon> <ha-svg-icon .path=${mdiFire}></ha-svg-icon>
${formatNumber(gasUsage || 0, this.hass.locale, { ${formatConsumptionShort(
maximumFractionDigits: 1,
})}
${getEnergyGasUnit(
this.hass, this.hass,
prefs, gasUsage,
this._data.statsMetadata getEnergyGasUnit(
this.hass,
prefs,
this._data.statsMetadata
)
)} )}
</div> </div>
<svg width="80" height="30"> <svg width="80" height="30">
@ -383,10 +384,11 @@ class HuiEnergyDistrubutionCard
> >
<div class="circle"> <div class="circle">
<ha-svg-icon .path=${mdiWater}></ha-svg-icon> <ha-svg-icon .path=${mdiWater}></ha-svg-icon>
${formatNumber(waterUsage || 0, this.hass.locale, { ${formatConsumptionShort(
maximumFractionDigits: 1, this.hass,
})} waterUsage,
${getEnergyWaterUnit(this.hass)} getEnergyWaterUnit(this.hass)
)}
</div> </div>
<svg width="80" height="30"> <svg width="80" height="30">
<path d="M40 0 v30" id="water" /> <path d="M40 0 v30" id="water" />
@ -420,10 +422,11 @@ class HuiEnergyDistrubutionCard
class="small" class="small"
.path=${mdiArrowLeft} .path=${mdiArrowLeft}
></ha-svg-icon ></ha-svg-icon
>${formatNumber(returnedToGrid, this.hass.locale, { >${formatConsumptionShort(
maximumFractionDigits: 1, this.hass,
})} returnedToGrid,
kWh "kWh"
)}
</span>` </span>`
: ""} : ""}
<span class="consumption"> <span class="consumption">
@ -432,10 +435,11 @@ class HuiEnergyDistrubutionCard
class="small" class="small"
.path=${mdiArrowRight} .path=${mdiArrowRight}
></ha-svg-icon>` ></ha-svg-icon>`
: ""}${formatNumber(totalFromGrid, this.hass.locale, { : ""}${formatConsumptionShort(
maximumFractionDigits: 1, this.hass,
})} totalFromGrid,
kWh "kWh"
)}
</span> </span>
</div> </div>
<span class="label" <span class="label"
@ -453,10 +457,11 @@ class HuiEnergyDistrubutionCard
})}" })}"
> >
<ha-svg-icon .path=${mdiHome}></ha-svg-icon> <ha-svg-icon .path=${mdiHome}></ha-svg-icon>
${formatNumber(totalHomeConsumption, this.hass.locale, { ${formatConsumptionShort(
maximumFractionDigits: 1, this.hass,
})} totalHomeConsumption,
kWh "kWh"
)}
${homeSolarCircumference !== undefined || ${homeSolarCircumference !== undefined ||
homeLowCarbonCircumference !== undefined homeLowCarbonCircumference !== undefined
? html`<svg> ? html`<svg>
@ -550,29 +555,23 @@ class HuiEnergyDistrubutionCard
class="small" class="small"
.path=${mdiArrowDown} .path=${mdiArrowDown}
></ha-svg-icon ></ha-svg-icon
>${formatNumber( >${formatConsumptionShort(
totalBatteryIn || 0, this.hass,
this.hass.locale, totalBatteryIn,
{ "kWh"
maximumFractionDigits: 1,
}
)} )}
kWh</span </span>
>
<span class="battery-out"> <span class="battery-out">
<ha-svg-icon <ha-svg-icon
class="small" class="small"
.path=${mdiArrowUp} .path=${mdiArrowUp}
></ha-svg-icon ></ha-svg-icon
>${formatNumber( >${formatConsumptionShort(
totalBatteryOut || 0, this.hass,
this.hass.locale, totalBatteryOut,
{ "kWh"
maximumFractionDigits: 1,
}
)} )}
kWh</span </span>
>
</div> </div>
<span class="label" <span class="label"
>${this.hass.localize( >${this.hass.localize(
@ -603,10 +602,11 @@ class HuiEnergyDistrubutionCard
</svg> </svg>
<div class="circle"> <div class="circle">
<ha-svg-icon .path=${mdiWater}></ha-svg-icon> <ha-svg-icon .path=${mdiWater}></ha-svg-icon>
${formatNumber(waterUsage || 0, this.hass.locale, { ${formatConsumptionShort(
maximumFractionDigits: 1, this.hass,
})} waterUsage,
${getEnergyWaterUnit(this.hass)} getEnergyWaterUnit(this.hass)
)}
</div> </div>
<span class="label" <span class="label"
>${this.hass.localize( >${this.hass.localize(

82
test/data/energy.test.ts Normal file
View File

@ -0,0 +1,82 @@
import { assert, describe, it } from "vitest";
import {
type FrontendLocaleData,
NumberFormat,
TimeFormat,
FirstWeekday,
DateFormat,
TimeZone,
} from "../../src/data/translation";
import { formatConsumptionShort } from "../../src/data/energy";
import type { HomeAssistant } from "../../src/types";
describe("Energy Short Format Test", () => {
// Create default to not have to specify a not relevant TimeFormat over and over again.
const defaultLocale: FrontendLocaleData = {
language: "en",
number_format: NumberFormat.language,
time_format: TimeFormat.language,
date_format: DateFormat.language,
time_zone: TimeZone.local,
first_weekday: FirstWeekday.language,
};
const hass = { locale: defaultLocale } as HomeAssistant;
it("Formats", () => {
assert.strictEqual(formatConsumptionShort(hass, 0, "kWh"), "0 kWh");
assert.strictEqual(formatConsumptionShort(hass, 0, "GWh"), "0 GWh");
assert.strictEqual(formatConsumptionShort(hass, 0, "gal"), "0 gal");
assert.strictEqual(
formatConsumptionShort(hass, 0.12345, "kWh"),
"0.12 kWh"
);
assert.strictEqual(
formatConsumptionShort(hass, 10.12345, "kWh"),
"10.1 kWh"
);
assert.strictEqual(
formatConsumptionShort(hass, 500.12345, "kWh"),
"500 kWh"
);
assert.strictEqual(
formatConsumptionShort(hass, 1512.34567, "kWh"),
"1.51 MWh"
);
assert.strictEqual(
formatConsumptionShort(hass, 15123.4567, "kWh"),
"15.1 MWh"
);
assert.strictEqual(
formatConsumptionShort(hass, 151234.5678, "kWh"),
"151 MWh"
);
assert.strictEqual(
formatConsumptionShort(hass, 1512345.6789, "kWh"),
"1.51 GWh"
);
assert.strictEqual(
formatConsumptionShort(hass, 15123456789.9, "kWh"),
"15.1 TWh"
);
assert.strictEqual(
formatConsumptionShort(hass, 15123456789000.9, "kWh"),
"15,123 TWh"
);
assert.strictEqual(formatConsumptionShort(hass, 1000.1, "GWh"), "1 TWh");
assert.strictEqual(
formatConsumptionShort(hass, 10000.12345, "gal"),
"10,000 gal"
);
// Don't really modify negative numbers, but make sure it's something sane.
assert.strictEqual(
formatConsumptionShort(hass, -1234.56, "kWh"),
"-1,234.56 kWh"
);
});
});