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

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