mirror of
https://github.com/home-assistant/frontend.git
synced 2025-07-22 00:36:34 +00:00
Energy dashboard tweaks and fixes (#9643)
* Energy dashboard tweaks and fixes * Make headers smaller * Change button styling in onboarding * Disable add when no stat choosen * Oops * Update hui-energy-carbon-consumed-gauge-card.ts * Update hui-energy-distribution-card.ts
This commit is contained in:
parent
6e7af18494
commit
1531e99528
@ -1,5 +1,22 @@
|
||||
import { FrontendLocaleData, NumberFormat } from "../../data/translation";
|
||||
|
||||
export const numberFormatToLocale = (
|
||||
localeOptions: FrontendLocaleData
|
||||
): string | string[] | undefined => {
|
||||
switch (localeOptions.number_format) {
|
||||
case NumberFormat.comma_decimal:
|
||||
return ["en-US", "en"]; // Use United States with fallback to English formatting 1,234,567.89
|
||||
case NumberFormat.decimal_comma:
|
||||
return ["de", "es", "it"]; // Use German with fallback to Spanish then Italian formatting 1.234.567,89
|
||||
case NumberFormat.space_comma:
|
||||
return ["fr", "sv", "cs"]; // Use French with fallback to Swedish and Czech formatting 1 234 567,89
|
||||
case NumberFormat.system:
|
||||
return undefined;
|
||||
default:
|
||||
return localeOptions.language;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Formats a number based on the user's preference with thousands separator(s) and decimal character for better legibility.
|
||||
*
|
||||
@ -9,27 +26,12 @@ import { FrontendLocaleData, NumberFormat } from "../../data/translation";
|
||||
*/
|
||||
export const formatNumber = (
|
||||
num: string | number,
|
||||
locale?: FrontendLocaleData,
|
||||
localeOptions?: FrontendLocaleData,
|
||||
options?: Intl.NumberFormatOptions
|
||||
): string => {
|
||||
let format: string | string[] | undefined;
|
||||
|
||||
switch (locale?.number_format) {
|
||||
case NumberFormat.comma_decimal:
|
||||
format = ["en-US", "en"]; // Use United States with fallback to English formatting 1,234,567.89
|
||||
break;
|
||||
case NumberFormat.decimal_comma:
|
||||
format = ["de", "es", "it"]; // Use German with fallback to Spanish then Italian formatting 1.234.567,89
|
||||
break;
|
||||
case NumberFormat.space_comma:
|
||||
format = ["fr", "sv", "cs"]; // Use French with fallback to Swedish and Czech formatting 1 234 567,89
|
||||
break;
|
||||
case NumberFormat.system:
|
||||
format = undefined;
|
||||
break;
|
||||
default:
|
||||
format = locale?.language;
|
||||
}
|
||||
const locale = localeOptions
|
||||
? numberFormatToLocale(localeOptions)
|
||||
: undefined;
|
||||
|
||||
// Polyfill for Number.isNaN, which is more reliable than the global isNaN()
|
||||
Number.isNaN =
|
||||
@ -39,13 +41,13 @@ export const formatNumber = (
|
||||
};
|
||||
|
||||
if (
|
||||
localeOptions?.number_format !== NumberFormat.none &&
|
||||
!Number.isNaN(Number(num)) &&
|
||||
Intl &&
|
||||
locale?.number_format !== NumberFormat.none
|
||||
Intl
|
||||
) {
|
||||
try {
|
||||
return new Intl.NumberFormat(
|
||||
format,
|
||||
locale,
|
||||
getDefaultFormatOptions(num, options)
|
||||
).format(Number(num));
|
||||
} catch (error) {
|
||||
|
@ -152,7 +152,17 @@ export default class HaChartBase extends LitElement {
|
||||
.querySelector("canvas")!
|
||||
.getContext("2d")!;
|
||||
|
||||
this.chart = new (await import("../../resources/chartjs")).Chart(ctx, {
|
||||
const ChartConstructor = (await import("../../resources/chartjs")).Chart;
|
||||
|
||||
const computedStyles = getComputedStyle(this);
|
||||
|
||||
ChartConstructor.defaults.borderColor =
|
||||
computedStyles.getPropertyValue("--divider-color");
|
||||
ChartConstructor.defaults.color = computedStyles.getPropertyValue(
|
||||
"--secondary-text-color"
|
||||
);
|
||||
|
||||
this.chart = new ChartConstructor(ctx, {
|
||||
type: this.chartType,
|
||||
data: this.data,
|
||||
options: this._createOptions(),
|
||||
|
@ -109,6 +109,8 @@ class StateHistoryChartLine extends LitElement {
|
||||
hitRadius: 5,
|
||||
},
|
||||
},
|
||||
// @ts-expect-error
|
||||
locale: numberFormatToLocale(this.hass.locale),
|
||||
};
|
||||
}
|
||||
if (changedProps.has("data")) {
|
||||
|
@ -5,6 +5,7 @@ import { customElement, property, state } from "lit/decorators";
|
||||
import { getColorByIndex } from "../../common/color/colors";
|
||||
import { formatDateTimeWithSeconds } from "../../common/datetime/format_date_time";
|
||||
import { computeDomain } from "../../common/entity/compute_domain";
|
||||
import { numberFormatToLocale } from "../../common/string/format_number";
|
||||
import { computeRTL } from "../../common/util/compute_rtl";
|
||||
import { TimelineEntity } from "../../data/history";
|
||||
import { HomeAssistant } from "../../types";
|
||||
@ -186,6 +187,8 @@ export class StateHistoryChartTimeline extends LitElement {
|
||||
propagate: true,
|
||||
},
|
||||
},
|
||||
// @ts-expect-error
|
||||
locale: numberFormatToLocale(this.hass.locale),
|
||||
};
|
||||
}
|
||||
if (changedProps.has("data")) {
|
||||
|
@ -16,6 +16,7 @@ import { customElement, property, state } from "lit/decorators";
|
||||
import { getColorByIndex } from "../../common/color/colors";
|
||||
import { isComponentLoaded } from "../../common/config/is_component_loaded";
|
||||
import { computeStateName } from "../../common/entity/compute_state_name";
|
||||
import { numberFormatToLocale } from "../../common/string/format_number";
|
||||
import {
|
||||
Statistics,
|
||||
statisticsHaveType,
|
||||
@ -119,7 +120,7 @@ class StatisticsChart extends LitElement {
|
||||
: {},
|
||||
},
|
||||
time: {
|
||||
tooltipFormat: "datetimeseconds",
|
||||
tooltipFormat: "datetime",
|
||||
},
|
||||
},
|
||||
y: {
|
||||
@ -157,6 +158,8 @@ class StatisticsChart extends LitElement {
|
||||
hitRadius: 5,
|
||||
},
|
||||
},
|
||||
// @ts-expect-error
|
||||
locale: numberFormatToLocale(this.hass.locale),
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -212,7 +212,15 @@ export class DialogEnergyGridFlowSettings
|
||||
<mwc-button @click=${this.closeDialog} slot="secondaryAction">
|
||||
${this.hass.localize("ui.common.cancel")}
|
||||
</mwc-button>
|
||||
<mwc-button @click=${this._save} slot="primaryAction">
|
||||
<mwc-button
|
||||
@click=${this._save}
|
||||
.disabled=${!this._source[
|
||||
this._params!.direction === "from"
|
||||
? "stat_energy_from"
|
||||
: "stat_energy_to"
|
||||
]}
|
||||
slot="primaryAction"
|
||||
>
|
||||
${this.hass.localize("ui.common.save")}
|
||||
</mwc-button>
|
||||
</ha-dialog>
|
||||
@ -231,32 +239,42 @@ export class DialogEnergyGridFlowSettings
|
||||
}
|
||||
|
||||
private _numberPriceChanged(ev: CustomEvent) {
|
||||
this._source!.number_energy_price = Number(ev.detail.value);
|
||||
this._source!.entity_energy_price = null;
|
||||
this._costStat = null;
|
||||
this._source = {
|
||||
...this._source!,
|
||||
number_energy_price: Number(ev.detail.value),
|
||||
entity_energy_price: null,
|
||||
};
|
||||
}
|
||||
|
||||
private _priceStatChanged(ev: CustomEvent) {
|
||||
this._costStat = ev.detail.value;
|
||||
this._source!.entity_energy_price = null;
|
||||
this._source!.number_energy_price = null;
|
||||
this._source = {
|
||||
...this._source!,
|
||||
entity_energy_price: null,
|
||||
number_energy_price: null,
|
||||
};
|
||||
}
|
||||
|
||||
private _priceEntityChanged(ev: CustomEvent) {
|
||||
this._source!.entity_energy_price = ev.detail.value;
|
||||
this._source!.number_energy_price = null;
|
||||
this._costStat = null;
|
||||
this._source = {
|
||||
...this._source!,
|
||||
entity_energy_price: ev.detail.value,
|
||||
number_energy_price: null,
|
||||
};
|
||||
}
|
||||
|
||||
private _statisticChanged(ev: CustomEvent<{ value: string }>) {
|
||||
this._source![
|
||||
this._params!.direction === "from" ? "stat_energy_from" : "stat_energy_to"
|
||||
] = ev.detail.value;
|
||||
this._source![
|
||||
this._params!.direction === "from"
|
||||
this._source = {
|
||||
...this._source!,
|
||||
[this._params!.direction === "from"
|
||||
? "stat_energy_from"
|
||||
: "stat_energy_to"]: ev.detail.value,
|
||||
[this._params!.direction === "from"
|
||||
? "entity_energy_from"
|
||||
: "entity_energy_to"
|
||||
] = ev.detail.value;
|
||||
: "entity_energy_to"]: ev.detail.value,
|
||||
};
|
||||
}
|
||||
|
||||
private async _save() {
|
||||
|
@ -140,7 +140,11 @@ export class DialogEnergySolarSettings
|
||||
<mwc-button @click=${this.closeDialog} slot="secondaryAction">
|
||||
${this.hass.localize("ui.common.cancel")}
|
||||
</mwc-button>
|
||||
<mwc-button @click=${this._save} slot="primaryAction">
|
||||
<mwc-button
|
||||
@click=${this._save}
|
||||
.disabled=${!this._source.stat_energy_from}
|
||||
slot="primaryAction"
|
||||
>
|
||||
${this.hass.localize("ui.common.save")}
|
||||
</mwc-button>
|
||||
</ha-dialog>
|
||||
@ -191,7 +195,7 @@ export class DialogEnergySolarSettings
|
||||
}
|
||||
|
||||
private _statisticChanged(ev: CustomEvent<{ value: string }>) {
|
||||
this._source!.stat_energy_from = ev.detail.value;
|
||||
this._source = { ...this._source!, stat_energy_from: ev.detail.value };
|
||||
}
|
||||
|
||||
private async _save() {
|
||||
|
@ -61,15 +61,15 @@ export class EnergySetupWizard extends LitElement implements LovelaceCard {
|
||||
></ha-energy-device-settings>`}
|
||||
<div class="buttons">
|
||||
${this._step > 0
|
||||
? html`<mwc-button @click=${this._back}
|
||||
? html`<mwc-button outlined @click=${this._back}
|
||||
>${this.hass.localize("ui.panel.energy.setup.back")}</mwc-button
|
||||
>`
|
||||
: html`<div></div>`}
|
||||
${this._step < 2
|
||||
? html`<mwc-button outlined @click=${this._next}
|
||||
? html`<mwc-button unelevated @click=${this._next}
|
||||
>${this.hass.localize("ui.panel.energy.setup.next")}</mwc-button
|
||||
>`
|
||||
: html`<mwc-button raised @click=${this._setupDone}>
|
||||
: html`<mwc-button unelevated @click=${this._setupDone}>
|
||||
${this.hass.localize("ui.panel.energy.setup.done")}
|
||||
</mwc-button>`}
|
||||
</div>
|
||||
|
@ -50,7 +50,7 @@ export class EnergyStrategy {
|
||||
if (hasGrid) {
|
||||
view.cards!.push({
|
||||
title: "Energy usage",
|
||||
type: "energy-summary-graph",
|
||||
type: "energy-usage-graph",
|
||||
prefs: energyPrefs,
|
||||
});
|
||||
}
|
||||
@ -64,11 +64,10 @@ export class EnergyStrategy {
|
||||
});
|
||||
}
|
||||
|
||||
// Only include if we have a grid.
|
||||
if (hasGrid) {
|
||||
if (hasGrid || hasSolar) {
|
||||
view.cards!.push({
|
||||
title: "Costs",
|
||||
type: "energy-costs-table",
|
||||
title: "Sources",
|
||||
type: "energy-sources-table",
|
||||
prefs: energyPrefs,
|
||||
});
|
||||
}
|
||||
|
@ -105,7 +105,7 @@ class HuiEnergyCarbonGaugeCard extends LitElement implements LovelaceCard {
|
||||
(totalSolarProduction || 0) -
|
||||
(totalGridReturned || 0);
|
||||
|
||||
value = round((1 - highCarbonEnergy / totalEnergyConsumed) * 100);
|
||||
value = round((highCarbonEnergy / totalEnergyConsumed) * 100);
|
||||
}
|
||||
|
||||
return html`
|
||||
|
@ -1,272 +0,0 @@
|
||||
// @ts-ignore
|
||||
import dataTableStyles from "@material/data-table/dist/mdc.data-table.min.css";
|
||||
import {
|
||||
css,
|
||||
CSSResultGroup,
|
||||
html,
|
||||
LitElement,
|
||||
TemplateResult,
|
||||
unsafeCSS,
|
||||
} from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import { computeStateName } from "../../../../common/entity/compute_state_name";
|
||||
import { round } from "../../../../common/number/round";
|
||||
import { formatNumber } from "../../../../common/string/format_number";
|
||||
import "../../../../components/chart/statistics-chart";
|
||||
import "../../../../components/ha-card";
|
||||
import {
|
||||
EnergyInfo,
|
||||
getEnergyInfo,
|
||||
GridSourceTypeEnergyPreference,
|
||||
} from "../../../../data/energy";
|
||||
import {
|
||||
calculateStatisticSumGrowth,
|
||||
fetchStatistics,
|
||||
Statistics,
|
||||
} from "../../../../data/history";
|
||||
import { HomeAssistant } from "../../../../types";
|
||||
import { LovelaceCard } from "../../types";
|
||||
import { EnergyDevicesGraphCardConfig } from "../types";
|
||||
|
||||
@customElement("hui-energy-costs-table-card")
|
||||
export class HuiEnergyCostsTableCard
|
||||
extends LitElement
|
||||
implements LovelaceCard
|
||||
{
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
|
||||
@state() private _config?: EnergyDevicesGraphCardConfig;
|
||||
|
||||
@state() private _stats?: Statistics;
|
||||
|
||||
@state() private _energyInfo?: EnergyInfo;
|
||||
|
||||
public getCardSize(): Promise<number> | number {
|
||||
return 3;
|
||||
}
|
||||
|
||||
public setConfig(config: EnergyDevicesGraphCardConfig): void {
|
||||
this._config = config;
|
||||
}
|
||||
|
||||
public willUpdate() {
|
||||
if (!this.hasUpdated) {
|
||||
this._getEnergyInfo().then(() => this._getStatistics());
|
||||
}
|
||||
}
|
||||
|
||||
protected render(): TemplateResult {
|
||||
if (!this.hass || !this._config) {
|
||||
return html``;
|
||||
}
|
||||
|
||||
if (!this._stats) {
|
||||
return html`Loading...`;
|
||||
}
|
||||
|
||||
const source = this._config.prefs.energy_sources?.find(
|
||||
(src) => src.type === "grid"
|
||||
) as GridSourceTypeEnergyPreference | undefined;
|
||||
|
||||
if (!source) {
|
||||
return html`No grid source found.`;
|
||||
}
|
||||
|
||||
let totalEnergy = 0;
|
||||
let totalCost = 0;
|
||||
|
||||
return html` <ha-card .header="${this._config.title}">
|
||||
<div class="mdc-data-table">
|
||||
<div class="mdc-data-table__table-container">
|
||||
<table class="mdc-data-table__table" aria-label="Dessert calories">
|
||||
<thead>
|
||||
<tr class="mdc-data-table__header-row">
|
||||
<th
|
||||
class="mdc-data-table__header-cell"
|
||||
role="columnheader"
|
||||
scope="col"
|
||||
>
|
||||
Grid source
|
||||
</th>
|
||||
<th
|
||||
class="mdc-data-table__header-cell mdc-data-table__header-cell--numeric"
|
||||
role="columnheader"
|
||||
scope="col"
|
||||
>
|
||||
Energy
|
||||
</th>
|
||||
<th
|
||||
class="mdc-data-table__header-cell mdc-data-table__header-cell--numeric"
|
||||
role="columnheader"
|
||||
scope="col"
|
||||
>
|
||||
Cost
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="mdc-data-table__content">
|
||||
${source.flow_from.map((flow) => {
|
||||
const entity = this.hass.states[flow.stat_energy_from];
|
||||
const energy =
|
||||
calculateStatisticSumGrowth(
|
||||
this._stats![flow.stat_energy_from]
|
||||
) || 0;
|
||||
totalEnergy += energy;
|
||||
const cost_stat =
|
||||
flow.stat_cost ||
|
||||
this._energyInfo!.cost_sensors[flow.stat_energy_from];
|
||||
const cost =
|
||||
(cost_stat &&
|
||||
calculateStatisticSumGrowth(this._stats![cost_stat])) ||
|
||||
0;
|
||||
totalCost += cost;
|
||||
return html`<tr class="mdc-data-table__row">
|
||||
<th class="mdc-data-table__cell" scope="row">
|
||||
${entity ? computeStateName(entity) : flow.stat_energy_from}
|
||||
</th>
|
||||
<td
|
||||
class="mdc-data-table__cell mdc-data-table__cell--numeric"
|
||||
>
|
||||
${round(energy)} kWh
|
||||
</td>
|
||||
<td
|
||||
class="mdc-data-table__cell mdc-data-table__cell--numeric"
|
||||
>
|
||||
${formatNumber(cost, this.hass.locale, {
|
||||
style: "currency",
|
||||
currency: this.hass.config.currency!,
|
||||
})}
|
||||
</td>
|
||||
</tr>`;
|
||||
})}
|
||||
${source.flow_to.map((flow) => {
|
||||
const entity = this.hass.states[flow.stat_energy_to];
|
||||
const energy =
|
||||
(calculateStatisticSumGrowth(
|
||||
this._stats![flow.stat_energy_to]
|
||||
) || 0) * -1;
|
||||
totalEnergy += energy;
|
||||
const cost_stat =
|
||||
flow.stat_compensation ||
|
||||
this._energyInfo!.cost_sensors[flow.stat_energy_to];
|
||||
const cost =
|
||||
((cost_stat &&
|
||||
calculateStatisticSumGrowth(this._stats![cost_stat])) ||
|
||||
0) * -1;
|
||||
totalCost += cost;
|
||||
return html`<tr class="mdc-data-table__row">
|
||||
<th class="mdc-data-table__cell" scope="row">
|
||||
${entity ? computeStateName(entity) : flow.stat_energy_to}
|
||||
</th>
|
||||
<td
|
||||
class="mdc-data-table__cell mdc-data-table__cell--numeric"
|
||||
>
|
||||
${round(energy)} kWh
|
||||
</td>
|
||||
<td
|
||||
class="mdc-data-table__cell mdc-data-table__cell--numeric"
|
||||
>
|
||||
${formatNumber(cost, this.hass.locale, {
|
||||
style: "currency",
|
||||
currency: this.hass.config.currency!,
|
||||
})}
|
||||
</td>
|
||||
</tr>`;
|
||||
})}
|
||||
<tr class="mdc-data-table__row total">
|
||||
<th class="mdc-data-table__cell" scope="row">Total</th>
|
||||
<td class="mdc-data-table__cell mdc-data-table__cell--numeric">
|
||||
${round(totalEnergy)} kWh
|
||||
</td>
|
||||
<td class="mdc-data-table__cell mdc-data-table__cell--numeric">
|
||||
${formatNumber(totalCost, this.hass.locale, {
|
||||
style: "currency",
|
||||
currency: this.hass.config.currency!,
|
||||
})}
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</ha-card>`;
|
||||
}
|
||||
|
||||
private async _getEnergyInfo() {
|
||||
this._energyInfo = await getEnergyInfo(this.hass);
|
||||
}
|
||||
|
||||
private async _getStatistics(): Promise<void> {
|
||||
const startDate = new Date();
|
||||
startDate.setHours(0, 0, 0, 0);
|
||||
startDate.setTime(startDate.getTime() - 1000 * 60 * 60); // subtract 1 hour to get a startpoint
|
||||
|
||||
const statistics: string[] = Object.values(this._energyInfo!.cost_sensors);
|
||||
const prefs = this._config!.prefs;
|
||||
for (const source of prefs.energy_sources) {
|
||||
if (source.type === "solar") {
|
||||
continue;
|
||||
}
|
||||
|
||||
// grid source
|
||||
for (const flowFrom of source.flow_from) {
|
||||
statistics.push(flowFrom.stat_energy_from);
|
||||
if (flowFrom.stat_cost) {
|
||||
statistics.push(flowFrom.stat_cost);
|
||||
}
|
||||
}
|
||||
for (const flowTo of source.flow_to) {
|
||||
statistics.push(flowTo.stat_energy_to);
|
||||
if (flowTo.stat_compensation) {
|
||||
statistics.push(flowTo.stat_compensation);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
this._stats = await fetchStatistics(
|
||||
this.hass!,
|
||||
startDate,
|
||||
undefined,
|
||||
statistics
|
||||
);
|
||||
}
|
||||
|
||||
static get styles(): CSSResultGroup {
|
||||
return css`
|
||||
${unsafeCSS(dataTableStyles)}
|
||||
.mdc-data-table {
|
||||
width: 100%;
|
||||
border: 0;
|
||||
}
|
||||
.mdc-data-table__header-cell,
|
||||
.mdc-data-table__cell {
|
||||
color: var(--primary-text-color);
|
||||
border-bottom-color: var(--divider-color);
|
||||
}
|
||||
.mdc-data-table__row:not(.mdc-data-table__row--selected):hover {
|
||||
background-color: rgba(var(--rgb-primary-text-color), 0.04);
|
||||
}
|
||||
.total {
|
||||
--mdc-typography-body2-font-weight: 500;
|
||||
}
|
||||
.total .mdc-data-table__cell {
|
||||
border-top: 1px solid var(--divider-color);
|
||||
}
|
||||
ha-card {
|
||||
height: 100%;
|
||||
}
|
||||
.content {
|
||||
padding: 16px;
|
||||
}
|
||||
.has-header {
|
||||
padding-top: 0;
|
||||
}
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"hui-energy-costs-table-card": HuiEnergyCostsTableCard;
|
||||
}
|
||||
}
|
@ -16,6 +16,10 @@ import { customElement, property, state } from "lit/decorators";
|
||||
import { classMap } from "lit/directives/class-map";
|
||||
import { getColorByIndex } from "../../../../common/color/colors";
|
||||
import { computeStateName } from "../../../../common/entity/compute_state_name";
|
||||
import {
|
||||
formatNumber,
|
||||
numberFormatToLocale,
|
||||
} from "../../../../common/string/format_number";
|
||||
import "../../../../components/chart/ha-chart-base";
|
||||
import "../../../../components/ha-card";
|
||||
import {
|
||||
@ -106,7 +110,10 @@ export class HuiEnergyDevicesGraphCard
|
||||
}
|
||||
|
||||
return html`
|
||||
<ha-card .header="${this._config.title}">
|
||||
<ha-card>
|
||||
${this._config.title
|
||||
? html`<h1 class="card-header">${this._config.title}</h1>`
|
||||
: ""}
|
||||
<div
|
||||
class="content ${classMap({
|
||||
"has-header": !!this._config.title,
|
||||
@ -144,12 +151,15 @@ export class HuiEnergyDevicesGraphCard
|
||||
mode: "nearest",
|
||||
callbacks: {
|
||||
label: (context) =>
|
||||
`${context.dataset.label}: ${
|
||||
Math.round(context.parsed.x * 100) / 100
|
||||
} kWh`,
|
||||
`${context.dataset.label}: ${formatNumber(
|
||||
context.parsed.x,
|
||||
this.hass.locale
|
||||
)} kWh`,
|
||||
},
|
||||
},
|
||||
},
|
||||
// @ts-expect-error
|
||||
locale: numberFormatToLocale(this.hass.locale),
|
||||
};
|
||||
}
|
||||
|
||||
@ -236,6 +246,9 @@ export class HuiEnergyDevicesGraphCard
|
||||
ha-card {
|
||||
height: 100%;
|
||||
}
|
||||
.card-header {
|
||||
padding-bottom: 0;
|
||||
}
|
||||
.content {
|
||||
padding: 16px;
|
||||
}
|
||||
|
@ -10,7 +10,7 @@ import { css, html, LitElement, svg } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import { classMap } from "lit/directives/class-map";
|
||||
import { ifDefined } from "lit/directives/if-defined";
|
||||
import { round } from "../../../../common/number/round";
|
||||
import { formatNumber } from "../../../../common/string/format_number";
|
||||
import { subscribeOne } from "../../../../common/util/subscribe-one";
|
||||
import "../../../../components/ha-card";
|
||||
import "../../../../components/ha-svg-icon";
|
||||
@ -140,13 +140,9 @@ class HuiEnergyDistrubutionCard extends LitElement implements LovelaceCard {
|
||||
}
|
||||
|
||||
if (highCarbonConsumption !== null) {
|
||||
const gridPctHighCarbon = highCarbonConsumption / totalConsumption;
|
||||
lowCarbonConsumption = totalGridConsumption - highCarbonConsumption;
|
||||
|
||||
lowCarbonConsumption =
|
||||
totalGridConsumption - totalGridConsumption * gridPctHighCarbon;
|
||||
|
||||
const homePctGridHighCarbon =
|
||||
(gridPctHighCarbon * totalGridConsumption) / totalConsumption;
|
||||
const homePctGridHighCarbon = highCarbonConsumption / totalConsumption;
|
||||
|
||||
homeHighCarbonCircumference =
|
||||
CIRCLE_CIRCUMFERENCE * homePctGridHighCarbon;
|
||||
@ -158,6 +154,15 @@ class HuiEnergyDistrubutionCard extends LitElement implements LovelaceCard {
|
||||
}
|
||||
}
|
||||
|
||||
homeSolarCircumference = CIRCLE_CIRCUMFERENCE * 0.1;
|
||||
|
||||
homeHighCarbonCircumference = CIRCLE_CIRCUMFERENCE * 0.8;
|
||||
|
||||
homeLowCarbonCircumference =
|
||||
CIRCLE_CIRCUMFERENCE -
|
||||
(homeSolarCircumference || 0) -
|
||||
homeHighCarbonCircumference;
|
||||
|
||||
return html`
|
||||
<ha-card .header=${this._config.title}>
|
||||
<div class="card-content">
|
||||
@ -165,29 +170,39 @@ class HuiEnergyDistrubutionCard extends LitElement implements LovelaceCard {
|
||||
? html`<div class="row">
|
||||
${lowCarbonConsumption === undefined
|
||||
? html`<div class="spacer"></div>`
|
||||
: html`
|
||||
<div class="circle-container low-carbon">
|
||||
<span class="label">Non-fossil</span>
|
||||
<a
|
||||
class="circle"
|
||||
href=${ifDefined(electricityMapUrl)}
|
||||
target="_blank"
|
||||
rel="noopener no referrer"
|
||||
>
|
||||
<ha-svg-icon .path="${mdiLeaf}"></ha-svg-icon>
|
||||
${round(lowCarbonConsumption, 1)} kWh
|
||||
</a>
|
||||
<svg width="80" height="30">
|
||||
<line x1="40" y1="0" x2="40" y2="30"></line>
|
||||
</svg>
|
||||
</div>
|
||||
`}
|
||||
: html`<div class="circle-container low-carbon">
|
||||
<span class="label">Non-fossil</span>
|
||||
<a
|
||||
class="circle"
|
||||
href=${ifDefined(electricityMapUrl)}
|
||||
target="_blank"
|
||||
rel="noopener no referrer"
|
||||
>
|
||||
<ha-svg-icon .path="${mdiLeaf}"></ha-svg-icon>
|
||||
${lowCarbonConsumption
|
||||
? formatNumber(
|
||||
lowCarbonConsumption,
|
||||
this.hass.locale,
|
||||
{ maximumFractionDigits: 1 }
|
||||
)
|
||||
: "-"}
|
||||
kWh
|
||||
</a>
|
||||
<svg width="80" height="30">
|
||||
<line x1="40" y1="0" x2="40" y2="30"></line>
|
||||
</svg>
|
||||
</div>`}
|
||||
${hasSolarProduction
|
||||
? html`<div class="circle-container solar">
|
||||
<span class="label">Solar</span>
|
||||
<div class="circle">
|
||||
<ha-svg-icon .path="${mdiSolarPower}"></ha-svg-icon>
|
||||
${round(totalSolarProduction || 0, 1)} kWh
|
||||
${formatNumber(
|
||||
totalSolarProduction || 0,
|
||||
this.hass.locale,
|
||||
{ maximumFractionDigits: 1 }
|
||||
)}
|
||||
kWh
|
||||
</div>
|
||||
</div>`
|
||||
: ""}
|
||||
@ -204,7 +219,11 @@ class HuiEnergyDistrubutionCard extends LitElement implements LovelaceCard {
|
||||
class="small"
|
||||
.path=${mdiArrowRight}
|
||||
></ha-svg-icon>`
|
||||
: ""}${round(totalGridConsumption, 1)}
|
||||
: ""}${formatNumber(
|
||||
totalGridConsumption,
|
||||
this.hass.locale,
|
||||
{ maximumFractionDigits: 1 }
|
||||
)}
|
||||
kWh
|
||||
</span>
|
||||
${productionReturnedToGrid !== null
|
||||
@ -213,7 +232,12 @@ class HuiEnergyDistrubutionCard extends LitElement implements LovelaceCard {
|
||||
class="small"
|
||||
.path=${mdiArrowLeft}
|
||||
></ha-svg-icon
|
||||
>${round(productionReturnedToGrid, 1)} kWh
|
||||
>${formatNumber(
|
||||
productionReturnedToGrid,
|
||||
this.hass.locale,
|
||||
{ maximumFractionDigits: 1 }
|
||||
)}
|
||||
kWh
|
||||
</span>`
|
||||
: ""}
|
||||
</div>
|
||||
@ -228,41 +252,44 @@ class HuiEnergyDistrubutionCard extends LitElement implements LovelaceCard {
|
||||
})}"
|
||||
>
|
||||
<ha-svg-icon .path="${mdiHome}"></ha-svg-icon>
|
||||
${round(totalConsumption, 1)} kWh
|
||||
${formatNumber(totalConsumption, this.hass.locale, {
|
||||
maximumFractionDigits: 1,
|
||||
})}
|
||||
kWh
|
||||
${homeSolarCircumference !== undefined ||
|
||||
homeLowCarbonCircumference !== undefined
|
||||
? html`<svg>
|
||||
${homeSolarCircumference !== undefined
|
||||
? svg`
|
||||
<circle
|
||||
class="solar"
|
||||
cx="40"
|
||||
cy="40"
|
||||
r="38"
|
||||
stroke-dasharray="${homeSolarCircumference} ${
|
||||
? svg`<circle
|
||||
class="solar"
|
||||
cx="40"
|
||||
cy="40"
|
||||
r="38"
|
||||
stroke-dasharray="${homeSolarCircumference} ${
|
||||
CIRCLE_CIRCUMFERENCE - homeSolarCircumference
|
||||
}"
|
||||
shape-rendering="geometricPrecision"
|
||||
stroke-dashoffset="0"
|
||||
/>`
|
||||
shape-rendering="geometricPrecision"
|
||||
stroke-dashoffset="-${
|
||||
CIRCLE_CIRCUMFERENCE - homeSolarCircumference
|
||||
}"
|
||||
/>`
|
||||
: ""}
|
||||
${homeHighCarbonCircumference
|
||||
? svg`
|
||||
<circle
|
||||
class="low-carbon"
|
||||
cx="40"
|
||||
cy="40"
|
||||
r="38"
|
||||
stroke-dasharray="${homeLowCarbonCircumference} ${
|
||||
CIRCLE_CIRCUMFERENCE - homeLowCarbonCircumference!
|
||||
${homeLowCarbonCircumference
|
||||
? svg`<circle
|
||||
class="low-carbon"
|
||||
cx="40"
|
||||
cy="40"
|
||||
r="38"
|
||||
stroke-dasharray="${homeLowCarbonCircumference} ${
|
||||
CIRCLE_CIRCUMFERENCE - homeLowCarbonCircumference
|
||||
}"
|
||||
stroke-dashoffset="${
|
||||
((homeSolarCircumference || 0) +
|
||||
homeHighCarbonCircumference!) *
|
||||
-1
|
||||
}"
|
||||
shape-rendering="geometricPrecision"
|
||||
/>`
|
||||
stroke-dashoffset="-${
|
||||
CIRCLE_CIRCUMFERENCE -
|
||||
homeLowCarbonCircumference -
|
||||
(homeSolarCircumference || 0)
|
||||
}"
|
||||
shape-rendering="geometricPrecision"
|
||||
/>`
|
||||
: ""}
|
||||
<circle
|
||||
class="grid"
|
||||
@ -271,11 +298,11 @@ class HuiEnergyDistrubutionCard extends LitElement implements LovelaceCard {
|
||||
r="38"
|
||||
stroke-dasharray="${homeHighCarbonCircumference ??
|
||||
CIRCLE_CIRCUMFERENCE -
|
||||
homeSolarCircumference!} ${homeHighCarbonCircumference
|
||||
homeSolarCircumference!} ${homeHighCarbonCircumference !==
|
||||
undefined
|
||||
? CIRCLE_CIRCUMFERENCE - homeHighCarbonCircumference
|
||||
: homeSolarCircumference}"
|
||||
stroke-dashoffset="${(homeSolarCircumference || 0) *
|
||||
-1}"
|
||||
stroke-dashoffset="0"
|
||||
shape-rendering="geometricPrecision"
|
||||
/>
|
||||
</svg>`
|
||||
@ -315,7 +342,11 @@ class HuiEnergyDistrubutionCard extends LitElement implements LovelaceCard {
|
||||
vector-effect="non-scaling-stroke"
|
||||
></path>
|
||||
${productionReturnedToGrid && hasSolarProduction
|
||||
? svg`<circle r="1" class="return" vector-effect="non-scaling-stroke">
|
||||
? svg`<circle
|
||||
r="1"
|
||||
class="return"
|
||||
vector-effect="non-scaling-stroke"
|
||||
>
|
||||
<animateMotion
|
||||
dur="${
|
||||
6 -
|
||||
@ -332,8 +363,11 @@ class HuiEnergyDistrubutionCard extends LitElement implements LovelaceCard {
|
||||
</circle>`
|
||||
: ""}
|
||||
${totalSolarProduction
|
||||
? svg`
|
||||
<circle r="1" class="solar" vector-effect="non-scaling-stroke">
|
||||
? svg`<circle
|
||||
r="1"
|
||||
class="solar"
|
||||
vector-effect="non-scaling-stroke"
|
||||
>
|
||||
<animateMotion
|
||||
dur="${
|
||||
6 -
|
||||
@ -351,7 +385,11 @@ class HuiEnergyDistrubutionCard extends LitElement implements LovelaceCard {
|
||||
</circle>`
|
||||
: ""}
|
||||
${totalGridConsumption
|
||||
? svg`<circle r="1" class="grid" vector-effect="non-scaling-stroke">
|
||||
? svg`<circle
|
||||
r="1"
|
||||
class="grid"
|
||||
vector-effect="non-scaling-stroke"
|
||||
>
|
||||
<animateMotion
|
||||
dur="${
|
||||
6 -
|
||||
@ -471,12 +509,15 @@ class HuiEnergyDistrubutionCard extends LitElement implements LovelaceCard {
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
}
|
||||
.circle-container.low-carbon {
|
||||
margin-right: 4px;
|
||||
}
|
||||
.circle-container.solar {
|
||||
margin-left: 4px;
|
||||
height: 130px;
|
||||
}
|
||||
.spacer {
|
||||
width: 80px;
|
||||
height: 30px;
|
||||
width: 84px;
|
||||
}
|
||||
.circle {
|
||||
width: 80px;
|
||||
@ -519,67 +560,70 @@ class HuiEnergyDistrubutionCard extends LitElement implements LovelaceCard {
|
||||
height: 100%;
|
||||
}
|
||||
.low-carbon line {
|
||||
stroke: #0f9d58;
|
||||
stroke: var(--energy-non-fossil-color);
|
||||
}
|
||||
.low-carbon .circle {
|
||||
border-color: #0f9d58;
|
||||
border-color: var(--energy-non-fossil-color);
|
||||
}
|
||||
.low-carbon ha-svg-icon {
|
||||
color: #0f9d58;
|
||||
color: var(--energy-non-fossil-color);
|
||||
}
|
||||
circle.low-carbon {
|
||||
stroke: var(--energy-non-fossil-color);
|
||||
fill: var(--energy-non-fossil-color);
|
||||
}
|
||||
.solar .circle {
|
||||
border-color: #ff9800;
|
||||
border-color: var(--energy-solar-color);
|
||||
}
|
||||
circle.solar,
|
||||
path.solar {
|
||||
stroke: #ff9800;
|
||||
stroke: var(--energy-solar-color);
|
||||
}
|
||||
circle.solar {
|
||||
stroke-width: 4;
|
||||
fill: #ff9800;
|
||||
}
|
||||
circle.low-carbon {
|
||||
stroke: #0f9d58;
|
||||
fill: #0f9d58;
|
||||
fill: var(--energy-solar-color);
|
||||
}
|
||||
path.return,
|
||||
circle.return {
|
||||
stroke: #673ab7;
|
||||
stroke: var(--energy-grid-return-color);
|
||||
}
|
||||
circle.return {
|
||||
stroke-width: 4;
|
||||
fill: #673ab7;
|
||||
fill: var(--energy-grid-return-color);
|
||||
}
|
||||
.return {
|
||||
color: #673ab7;
|
||||
color: var(--energy-grid-return-color);
|
||||
}
|
||||
.grid .circle {
|
||||
border-color: #126a9a;
|
||||
border-color: var(--energy-grid-consumption-color);
|
||||
}
|
||||
.consumption {
|
||||
color: #126a9a;
|
||||
color: var(--energy-grid-consumption-color);
|
||||
}
|
||||
circle.grid,
|
||||
path.grid {
|
||||
stroke: #126a9a;
|
||||
stroke: var(--energy-grid-consumption-color);
|
||||
}
|
||||
circle.grid {
|
||||
stroke-width: 4;
|
||||
fill: #126a9a;
|
||||
fill: var(--energy-grid-consumption-color);
|
||||
}
|
||||
.home .circle {
|
||||
border: none;
|
||||
}
|
||||
.home .circle.border {
|
||||
border-width: 0;
|
||||
border-color: var(--primary-color);
|
||||
}
|
||||
.home .circle.border {
|
||||
border-width: 2px;
|
||||
}
|
||||
.circle svg circle {
|
||||
animation: rotate-in 0.2s ease-in;
|
||||
animation: rotate-in 0.6s ease-in;
|
||||
transition: stroke-dashoffset 0.4s, stroke-dasharray 0.4s;
|
||||
fill: none;
|
||||
}
|
||||
@keyframes rotate-in {
|
||||
from {
|
||||
stroke-dashoffset: 0;
|
||||
stroke-dashoffset: 238.76104;
|
||||
stroke-dasharray: 238.76104;
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
@ -1,7 +1,6 @@
|
||||
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import { styleMap } from "lit/directives/style-map";
|
||||
import { round } from "../../../../common/number/round";
|
||||
import "../../../../components/ha-card";
|
||||
import "../../../../components/ha-gauge";
|
||||
import { energySourcesByType } from "../../../../data/energy";
|
||||
@ -63,10 +62,14 @@ class HuiEnergySolarGaugeCard extends LitElement implements LovelaceCard {
|
||||
|
||||
let value: number | undefined;
|
||||
|
||||
if (productionReturnedToGrid !== null && totalSolarProduction !== null) {
|
||||
const cosumedSolar = totalSolarProduction - productionReturnedToGrid;
|
||||
value = round((cosumedSolar / totalSolarProduction) * 100);
|
||||
if (productionReturnedToGrid !== null && totalSolarProduction) {
|
||||
const cosumedSolar = Math.min(
|
||||
0,
|
||||
totalSolarProduction - productionReturnedToGrid
|
||||
);
|
||||
value = (cosumedSolar / totalSolarProduction) * 100;
|
||||
}
|
||||
|
||||
return html`
|
||||
<ha-card>
|
||||
${value !== undefined
|
||||
@ -81,7 +84,9 @@ class HuiEnergySolarGaugeCard extends LitElement implements LovelaceCard {
|
||||
})}
|
||||
></ha-gauge>
|
||||
<div class="name">Self consumed solar energy</div>`
|
||||
: html`Self consumed solar energy couldn't be calculated`}
|
||||
: totalSolarProduction === 0
|
||||
? "You have not produced any solar energy"
|
||||
: "Self consumed solar energy couldn't be calculated"}
|
||||
</ha-card>
|
||||
`;
|
||||
}
|
||||
|
@ -31,8 +31,10 @@ import { computeStateName } from "../../../../common/entity/compute_state_name";
|
||||
import "../../../../components/chart/ha-chart-base";
|
||||
import "../../../../components/ha-switch";
|
||||
import "../../../../components/ha-formfield";
|
||||
|
||||
const SOLAR_COLOR = "#FF9800";
|
||||
import {
|
||||
formatNumber,
|
||||
numberFormatToLocale,
|
||||
} from "../../../../common/string/format_number";
|
||||
|
||||
@customElement("hui-energy-solar-graph-card")
|
||||
export class HuiEnergySolarGraphCard
|
||||
@ -119,7 +121,10 @@ export class HuiEnergySolarGraphCard
|
||||
}
|
||||
|
||||
return html`
|
||||
<ha-card .header="${this._config.title}">
|
||||
<ha-card>
|
||||
${this._config.title
|
||||
? html`<h1 class="card-header">${this._config.title}</h1>`
|
||||
: ""}
|
||||
<div
|
||||
class="content ${classMap({
|
||||
"has-header": !!this._config.title,
|
||||
@ -166,7 +171,7 @@ export class HuiEnergySolarGraphCard
|
||||
: {},
|
||||
},
|
||||
time: {
|
||||
tooltipFormat: "datetimeseconds",
|
||||
tooltipFormat: "datetime",
|
||||
},
|
||||
offset: true,
|
||||
},
|
||||
@ -186,7 +191,10 @@ export class HuiEnergySolarGraphCard
|
||||
mode: "nearest",
|
||||
callbacks: {
|
||||
label: (context) =>
|
||||
`${context.dataset.label}: ${context.parsed.y} kWh`,
|
||||
`${context.dataset.label}: ${formatNumber(
|
||||
context.parsed.y,
|
||||
this.hass.locale
|
||||
)} kWh`,
|
||||
},
|
||||
},
|
||||
filler: {
|
||||
@ -212,6 +220,8 @@ export class HuiEnergySolarGraphCard
|
||||
hitRadius: 5,
|
||||
},
|
||||
},
|
||||
// @ts-expect-error
|
||||
locale: numberFormatToLocale(this.hass.locale),
|
||||
};
|
||||
}
|
||||
|
||||
@ -219,6 +229,7 @@ export class HuiEnergySolarGraphCard
|
||||
if (this._fetching) {
|
||||
return;
|
||||
}
|
||||
|
||||
const startDate = new Date();
|
||||
startDate.setHours(0, 0, 0, 0);
|
||||
startDate.setTime(startDate.getTime() - 1000 * 60 * 60); // subtract 1 hour to get a startpoint
|
||||
@ -273,20 +284,25 @@ export class HuiEnergySolarGraphCard
|
||||
endTime = new Date();
|
||||
}
|
||||
|
||||
const computedStyles = getComputedStyle(this);
|
||||
const solarColor = computedStyles
|
||||
.getPropertyValue("--energy-solar-color")
|
||||
.trim();
|
||||
|
||||
solarSources.forEach((source, idx) => {
|
||||
const data: ChartDataset<"bar" | "line">[] = [];
|
||||
const entity = this.hass.states[source.stat_energy_from];
|
||||
|
||||
const borderColor =
|
||||
idx > 0
|
||||
? rgb2hex(lab2rgb(labDarken(rgb2lab(hex2rgb(SOLAR_COLOR)), idx)))
|
||||
: SOLAR_COLOR;
|
||||
? rgb2hex(lab2rgb(labDarken(rgb2lab(hex2rgb(solarColor)), idx)))
|
||||
: solarColor;
|
||||
|
||||
data.push({
|
||||
label: `Production ${
|
||||
entity ? computeStateName(entity) : source.stat_energy_from
|
||||
}`,
|
||||
borderColor: borderColor,
|
||||
borderColor,
|
||||
backgroundColor: borderColor + "7F",
|
||||
data: [],
|
||||
});
|
||||
@ -307,7 +323,7 @@ export class HuiEnergySolarGraphCard
|
||||
if (prevStart === point.start) {
|
||||
continue;
|
||||
}
|
||||
const value = Math.round((point.sum - prevValue) * 100) / 100;
|
||||
const value = point.sum - prevValue;
|
||||
const date = new Date(point.start);
|
||||
data[0].data.push({
|
||||
x: date.getTime(),
|
||||
@ -347,7 +363,9 @@ export class HuiEnergySolarGraphCard
|
||||
}`,
|
||||
fill: false,
|
||||
stepped: false,
|
||||
borderColor: "#000",
|
||||
borderColor: computedStyles.getPropertyValue(
|
||||
"--primary-text-color"
|
||||
),
|
||||
borderDash: [7, 5],
|
||||
pointRadius: 0,
|
||||
data: [],
|
||||
@ -386,6 +404,9 @@ export class HuiEnergySolarGraphCard
|
||||
ha-card {
|
||||
height: 100%;
|
||||
}
|
||||
.card-header {
|
||||
padding-bottom: 0;
|
||||
}
|
||||
.content {
|
||||
padding: 16px;
|
||||
}
|
||||
|
@ -0,0 +1,426 @@
|
||||
// @ts-ignore
|
||||
import dataTableStyles from "@material/data-table/dist/mdc.data-table.min.css";
|
||||
import {
|
||||
css,
|
||||
CSSResultGroup,
|
||||
html,
|
||||
LitElement,
|
||||
TemplateResult,
|
||||
unsafeCSS,
|
||||
} from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import { styleMap } from "lit/directives/style-map";
|
||||
import {
|
||||
rgb2hex,
|
||||
lab2rgb,
|
||||
rgb2lab,
|
||||
hex2rgb,
|
||||
} from "../../../../common/color/convert-color";
|
||||
import { labDarken } from "../../../../common/color/lab";
|
||||
import { computeStateName } from "../../../../common/entity/compute_state_name";
|
||||
import { formatNumber } from "../../../../common/string/format_number";
|
||||
import "../../../../components/chart/statistics-chart";
|
||||
import "../../../../components/ha-card";
|
||||
import {
|
||||
EnergyInfo,
|
||||
energySourcesByType,
|
||||
getEnergyInfo,
|
||||
} from "../../../../data/energy";
|
||||
import {
|
||||
calculateStatisticSumGrowth,
|
||||
fetchStatistics,
|
||||
Statistics,
|
||||
} from "../../../../data/history";
|
||||
import { HomeAssistant } from "../../../../types";
|
||||
import { LovelaceCard } from "../../types";
|
||||
import { EnergySourcesTableCardConfig } from "../types";
|
||||
|
||||
@customElement("hui-energy-sources-table-card")
|
||||
export class HuiEnergySourcesTableCard
|
||||
extends LitElement
|
||||
implements LovelaceCard
|
||||
{
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
|
||||
@state() private _config?: EnergySourcesTableCardConfig;
|
||||
|
||||
@state() private _stats?: Statistics;
|
||||
|
||||
@state() private _energyInfo?: EnergyInfo;
|
||||
|
||||
public getCardSize(): Promise<number> | number {
|
||||
return 3;
|
||||
}
|
||||
|
||||
public setConfig(config: EnergySourcesTableCardConfig): void {
|
||||
this._config = config;
|
||||
}
|
||||
|
||||
public willUpdate() {
|
||||
if (!this.hasUpdated) {
|
||||
this._getEnergyInfo().then(() => this._getStatistics());
|
||||
}
|
||||
}
|
||||
|
||||
protected render(): TemplateResult {
|
||||
if (!this.hass || !this._config) {
|
||||
return html``;
|
||||
}
|
||||
|
||||
if (!this._stats) {
|
||||
return html`Loading...`;
|
||||
}
|
||||
|
||||
let totalGrid = 0;
|
||||
let totalSolar = 0;
|
||||
let totalCost = 0;
|
||||
|
||||
const types = energySourcesByType(this._config.prefs);
|
||||
|
||||
const computedStyles = getComputedStyle(this);
|
||||
const solarColor = computedStyles
|
||||
.getPropertyValue("--energy-solar-color")
|
||||
.trim();
|
||||
const returnColor = computedStyles
|
||||
.getPropertyValue("--energy-grid-return-color")
|
||||
.trim();
|
||||
const consumptionColor = computedStyles
|
||||
.getPropertyValue("--energy-grid-consumption-color")
|
||||
.trim();
|
||||
|
||||
const showCosts =
|
||||
types.grid?.[0].flow_from.some(
|
||||
(flow) =>
|
||||
flow.stat_cost || flow.entity_energy_price || flow.number_energy_price
|
||||
) ||
|
||||
types.grid?.[0].flow_to.some(
|
||||
(flow) =>
|
||||
flow.stat_compensation ||
|
||||
flow.entity_energy_price ||
|
||||
flow.number_energy_price
|
||||
);
|
||||
|
||||
return html` <ha-card>
|
||||
${this._config.title
|
||||
? html`<h1 class="card-header">${this._config.title}</h1>`
|
||||
: ""}
|
||||
<div class="mdc-data-table">
|
||||
<div class="mdc-data-table__table-container">
|
||||
<table class="mdc-data-table__table" aria-label="Dessert calories">
|
||||
<thead>
|
||||
<tr class="mdc-data-table__header-row">
|
||||
<th class="mdc-data-table__header-cell"></th>
|
||||
<th
|
||||
class="mdc-data-table__header-cell"
|
||||
role="columnheader"
|
||||
scope="col"
|
||||
>
|
||||
Source
|
||||
</th>
|
||||
<th
|
||||
class="mdc-data-table__header-cell mdc-data-table__header-cell--numeric"
|
||||
role="columnheader"
|
||||
scope="col"
|
||||
>
|
||||
Energy
|
||||
</th>
|
||||
${showCosts
|
||||
? html` <th
|
||||
class="mdc-data-table__header-cell mdc-data-table__header-cell--numeric"
|
||||
role="columnheader"
|
||||
scope="col"
|
||||
>
|
||||
Cost
|
||||
</th>`
|
||||
: ""}
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="mdc-data-table__content">
|
||||
${types.solar?.map((source, idx) => {
|
||||
const entity = this.hass.states[source.stat_energy_from];
|
||||
const energy =
|
||||
calculateStatisticSumGrowth(
|
||||
this._stats![source.stat_energy_from]
|
||||
) || 0;
|
||||
totalSolar += energy;
|
||||
const color =
|
||||
idx > 0
|
||||
? rgb2hex(
|
||||
lab2rgb(labDarken(rgb2lab(hex2rgb(solarColor)), idx))
|
||||
)
|
||||
: solarColor;
|
||||
return html`<tr class="mdc-data-table__row">
|
||||
<td class="mdc-data-table__cell cell-bullet">
|
||||
<div
|
||||
class="bullet"
|
||||
style=${styleMap({
|
||||
borderColor: color,
|
||||
backgroundColor: color + "7F",
|
||||
})}
|
||||
></div>
|
||||
</td>
|
||||
<th class="mdc-data-table__cell" scope="row">
|
||||
${entity
|
||||
? computeStateName(entity)
|
||||
: source.stat_energy_from}
|
||||
</th>
|
||||
<td
|
||||
class="mdc-data-table__cell mdc-data-table__cell--numeric"
|
||||
>
|
||||
${formatNumber(energy, this.hass.locale)} kWh
|
||||
</td>
|
||||
${showCosts
|
||||
? html`<td class="mdc-data-table__cell"></td>`
|
||||
: ""}
|
||||
</tr>`;
|
||||
})}
|
||||
${types.solar
|
||||
? html`<tr class="mdc-data-table__row total">
|
||||
<td class="mdc-data-table__cell"></td>
|
||||
<th class="mdc-data-table__cell" scope="row">
|
||||
Solar total
|
||||
</th>
|
||||
<td
|
||||
class="mdc-data-table__cell mdc-data-table__cell--numeric"
|
||||
>
|
||||
${formatNumber(totalSolar, this.hass.locale)} kWh
|
||||
</td>
|
||||
${showCosts
|
||||
? html`<td class="mdc-data-table__cell"></td>`
|
||||
: ""}
|
||||
</tr>`
|
||||
: ""}
|
||||
${types.grid?.map(
|
||||
(source) => html`${source.flow_from.map((flow, idx) => {
|
||||
const entity = this.hass.states[flow.stat_energy_from];
|
||||
const energy =
|
||||
calculateStatisticSumGrowth(
|
||||
this._stats![flow.stat_energy_from]
|
||||
) || 0;
|
||||
totalGrid += energy;
|
||||
const cost_stat =
|
||||
flow.stat_cost ||
|
||||
this._energyInfo!.cost_sensors[flow.stat_energy_from];
|
||||
const cost = cost_stat
|
||||
? calculateStatisticSumGrowth(this._stats![cost_stat])
|
||||
: null;
|
||||
if (cost !== null) {
|
||||
totalCost += cost;
|
||||
}
|
||||
const color =
|
||||
idx > 0
|
||||
? rgb2hex(
|
||||
lab2rgb(
|
||||
labDarken(rgb2lab(hex2rgb(consumptionColor)), idx)
|
||||
)
|
||||
)
|
||||
: consumptionColor;
|
||||
return html`<tr class="mdc-data-table__row">
|
||||
<td class="mdc-data-table__cell cell-bullet">
|
||||
<div
|
||||
class="bullet"
|
||||
style=${styleMap({
|
||||
borderColor: color,
|
||||
backgroundColor: color + "7F",
|
||||
})}
|
||||
></div>
|
||||
</td>
|
||||
<th class="mdc-data-table__cell" scope="row">
|
||||
${entity
|
||||
? computeStateName(entity)
|
||||
: flow.stat_energy_from}
|
||||
</th>
|
||||
<td
|
||||
class="mdc-data-table__cell mdc-data-table__cell--numeric"
|
||||
>
|
||||
${formatNumber(energy, this.hass.locale)} kWh
|
||||
</td>
|
||||
${showCosts
|
||||
? html` <td
|
||||
class="mdc-data-table__cell mdc-data-table__cell--numeric"
|
||||
>
|
||||
${cost !== null
|
||||
? formatNumber(cost, this.hass.locale, {
|
||||
style: "currency",
|
||||
currency: this.hass.config.currency!,
|
||||
})
|
||||
: ""}
|
||||
</td>`
|
||||
: ""}
|
||||
</tr>`;
|
||||
})}
|
||||
${source.flow_to.map((flow, idx) => {
|
||||
const entity = this.hass.states[flow.stat_energy_to];
|
||||
const energy =
|
||||
(calculateStatisticSumGrowth(
|
||||
this._stats![flow.stat_energy_to]
|
||||
) || 0) * -1;
|
||||
totalGrid += energy;
|
||||
const cost_stat =
|
||||
flow.stat_compensation ||
|
||||
this._energyInfo!.cost_sensors[flow.stat_energy_to];
|
||||
const cost = cost_stat
|
||||
? calculateStatisticSumGrowth(this._stats![cost_stat])
|
||||
: null;
|
||||
if (cost !== null) {
|
||||
totalCost += cost;
|
||||
}
|
||||
const color =
|
||||
idx > 0
|
||||
? rgb2hex(
|
||||
lab2rgb(labDarken(rgb2lab(hex2rgb(returnColor)), idx))
|
||||
)
|
||||
: returnColor;
|
||||
return html`<tr class="mdc-data-table__row">
|
||||
<td class="mdc-data-table__cell cell-bullet">
|
||||
<div
|
||||
class="bullet"
|
||||
style=${styleMap({
|
||||
borderColor: color,
|
||||
backgroundColor: color + "7F",
|
||||
})}
|
||||
></div>
|
||||
</td>
|
||||
<th class="mdc-data-table__cell" scope="row">
|
||||
${entity ? computeStateName(entity) : flow.stat_energy_to}
|
||||
</th>
|
||||
<td
|
||||
class="mdc-data-table__cell mdc-data-table__cell--numeric"
|
||||
>
|
||||
${formatNumber(energy, this.hass.locale)} kWh
|
||||
</td>
|
||||
${showCosts
|
||||
? html` <td
|
||||
class="mdc-data-table__cell mdc-data-table__cell--numeric"
|
||||
>
|
||||
${cost !== null
|
||||
? formatNumber(cost, this.hass.locale, {
|
||||
style: "currency",
|
||||
currency: this.hass.config.currency!,
|
||||
})
|
||||
: ""}
|
||||
</td>`
|
||||
: ""}
|
||||
</tr>`;
|
||||
})}`
|
||||
)}
|
||||
${types.grid
|
||||
? html` <tr class="mdc-data-table__row total">
|
||||
<td class="mdc-data-table__cell"></td>
|
||||
<th class="mdc-data-table__cell" scope="row">Grid total</th>
|
||||
<td
|
||||
class="mdc-data-table__cell mdc-data-table__cell--numeric"
|
||||
>
|
||||
${formatNumber(totalGrid, this.hass.locale)} kWh
|
||||
</td>
|
||||
${showCosts
|
||||
? html`<td
|
||||
class="mdc-data-table__cell mdc-data-table__cell--numeric"
|
||||
>
|
||||
${formatNumber(totalCost, this.hass.locale, {
|
||||
style: "currency",
|
||||
currency: this.hass.config.currency!,
|
||||
})}
|
||||
</td>`
|
||||
: ""}
|
||||
</tr>`
|
||||
: ""}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</ha-card>`;
|
||||
}
|
||||
|
||||
private async _getEnergyInfo() {
|
||||
this._energyInfo = await getEnergyInfo(this.hass);
|
||||
}
|
||||
|
||||
private async _getStatistics(): Promise<void> {
|
||||
const startDate = new Date();
|
||||
startDate.setHours(0, 0, 0, 0);
|
||||
startDate.setTime(startDate.getTime() - 1000 * 60 * 60); // subtract 1 hour to get a startpoint
|
||||
|
||||
const statistics: string[] = Object.values(this._energyInfo!.cost_sensors);
|
||||
const prefs = this._config!.prefs;
|
||||
for (const source of prefs.energy_sources) {
|
||||
if (source.type === "solar") {
|
||||
statistics.push(source.stat_energy_from);
|
||||
} else {
|
||||
// grid source
|
||||
for (const flowFrom of source.flow_from) {
|
||||
statistics.push(flowFrom.stat_energy_from);
|
||||
if (flowFrom.stat_cost) {
|
||||
statistics.push(flowFrom.stat_cost);
|
||||
}
|
||||
}
|
||||
for (const flowTo of source.flow_to) {
|
||||
statistics.push(flowTo.stat_energy_to);
|
||||
if (flowTo.stat_compensation) {
|
||||
statistics.push(flowTo.stat_compensation);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
this._stats = await fetchStatistics(
|
||||
this.hass!,
|
||||
startDate,
|
||||
undefined,
|
||||
statistics
|
||||
);
|
||||
}
|
||||
|
||||
static get styles(): CSSResultGroup {
|
||||
return css`
|
||||
${unsafeCSS(dataTableStyles)}
|
||||
.mdc-data-table {
|
||||
width: 100%;
|
||||
border: 0;
|
||||
}
|
||||
.mdc-data-table__header-cell,
|
||||
.mdc-data-table__cell {
|
||||
color: var(--primary-text-color);
|
||||
border-bottom-color: var(--divider-color);
|
||||
}
|
||||
.mdc-data-table__row:not(.mdc-data-table__row--selected):hover {
|
||||
background-color: rgba(var(--rgb-primary-text-color), 0.04);
|
||||
}
|
||||
.total {
|
||||
--mdc-typography-body2-font-weight: 500;
|
||||
}
|
||||
.total .mdc-data-table__cell {
|
||||
border-top: 1px solid var(--divider-color);
|
||||
}
|
||||
ha-card {
|
||||
height: 100%;
|
||||
}
|
||||
.card-header {
|
||||
padding-bottom: 0;
|
||||
}
|
||||
.content {
|
||||
padding: 16px;
|
||||
}
|
||||
.has-header {
|
||||
padding-top: 0;
|
||||
}
|
||||
.cell-bullet {
|
||||
width: 32px;
|
||||
padding-right: 0;
|
||||
}
|
||||
.bullet {
|
||||
border-width: 1px;
|
||||
border-style: solid;
|
||||
border-radius: 4px;
|
||||
height: 16px;
|
||||
width: 32px;
|
||||
}
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"hui-energy-sources-table-card": HuiEnergySourcesTableCard;
|
||||
}
|
||||
}
|
@ -9,39 +9,34 @@ import {
|
||||
} from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import { classMap } from "lit/directives/class-map";
|
||||
import { styleMap } from "lit/directives/style-map";
|
||||
import {
|
||||
hex2rgb,
|
||||
lab2rgb,
|
||||
rgb2hex,
|
||||
rgb2lab,
|
||||
} from "../../../../common/color/convert-color";
|
||||
import { hexBlend } from "../../../../common/color/hex";
|
||||
import { labDarken } from "../../../../common/color/lab";
|
||||
import { computeStateName } from "../../../../common/entity/compute_state_name";
|
||||
import { round } from "../../../../common/number/round";
|
||||
import { formatNumber } from "../../../../common/string/format_number";
|
||||
import {
|
||||
formatNumber,
|
||||
numberFormatToLocale,
|
||||
} from "../../../../common/string/format_number";
|
||||
import "../../../../components/chart/ha-chart-base";
|
||||
import "../../../../components/ha-card";
|
||||
import { fetchStatistics, Statistics } from "../../../../data/history";
|
||||
import { HomeAssistant } from "../../../../types";
|
||||
import { LovelaceCard } from "../../types";
|
||||
import { EnergySummaryGraphCardConfig } from "../types";
|
||||
import { EnergyUsageGraphCardConfig } from "../types";
|
||||
|
||||
const NEGATIVE = ["to_grid"];
|
||||
const COLORS = {
|
||||
to_grid: { border: "#673ab7", background: "#b39bdb" },
|
||||
from_grid: { border: "#126A9A", background: "#8ab5cd" },
|
||||
used_solar: { border: "#FF9800", background: "#fecc8e" },
|
||||
};
|
||||
|
||||
@customElement("hui-energy-summary-graph-card")
|
||||
export class HuiEnergySummaryGraphCard
|
||||
@customElement("hui-energy-usage-graph-card")
|
||||
export class HuiEnergyUsageGraphCard
|
||||
extends LitElement
|
||||
implements LovelaceCard
|
||||
{
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
|
||||
@state() private _config?: EnergySummaryGraphCardConfig;
|
||||
@state() private _config?: EnergyUsageGraphCardConfig;
|
||||
|
||||
@state() private _data?: Statistics;
|
||||
|
||||
@ -81,7 +76,7 @@ export class HuiEnergySummaryGraphCard
|
||||
return 3;
|
||||
}
|
||||
|
||||
public setConfig(config: EnergySummaryGraphCardConfig): void {
|
||||
public setConfig(config: EnergyUsageGraphCardConfig): void {
|
||||
this._config = config;
|
||||
}
|
||||
|
||||
@ -95,7 +90,7 @@ export class HuiEnergySummaryGraphCard
|
||||
}
|
||||
|
||||
const oldConfig = changedProps.get("_config") as
|
||||
| EnergySummaryGraphCardConfig
|
||||
| EnergyUsageGraphCardConfig
|
||||
| undefined;
|
||||
|
||||
if (oldConfig !== this._config) {
|
||||
@ -116,42 +111,14 @@ export class HuiEnergySummaryGraphCard
|
||||
|
||||
return html`
|
||||
<ha-card>
|
||||
<h1 class="card-header">${this._config.title}</h1>
|
||||
${this._config.title
|
||||
? html`<h1 class="card-header">${this._config.title}</h1>`
|
||||
: ""}
|
||||
<div
|
||||
class="content ${classMap({
|
||||
"has-header": !!this._config.title,
|
||||
})}"
|
||||
>
|
||||
<div class="chartLegend">
|
||||
<ul>
|
||||
${this._chartData.datasets.map(
|
||||
(dataset) => html`<li>
|
||||
<div>
|
||||
<div
|
||||
class="bullet"
|
||||
style=${styleMap({
|
||||
backgroundColor: dataset.backgroundColor as string,
|
||||
borderColor: dataset.borderColor as string,
|
||||
})}
|
||||
></div>
|
||||
<span class="label">${dataset.label}</span>
|
||||
</div>
|
||||
<span class="value"
|
||||
>${formatNumber(
|
||||
Math.abs(
|
||||
dataset.data.reduce(
|
||||
(total, point) => total + (point as any).y,
|
||||
0
|
||||
) as number
|
||||
),
|
||||
this.hass.locale
|
||||
)}
|
||||
kWh</span
|
||||
>
|
||||
</li>`
|
||||
)}
|
||||
</ul>
|
||||
</div>
|
||||
<ha-chart-base
|
||||
.data=${this._chartData}
|
||||
.options=${this._chartOptions}
|
||||
@ -193,7 +160,7 @@ export class HuiEnergySummaryGraphCard
|
||||
: {},
|
||||
},
|
||||
time: {
|
||||
tooltipFormat: "datetimeseconds",
|
||||
tooltipFormat: "datetime",
|
||||
},
|
||||
offset: true,
|
||||
},
|
||||
@ -206,7 +173,8 @@ export class HuiEnergySummaryGraphCard
|
||||
},
|
||||
ticks: {
|
||||
beginAtZero: true,
|
||||
callback: (value) => Math.abs(round(value)),
|
||||
callback: (value) =>
|
||||
formatNumber(Math.abs(value), this.hass.locale),
|
||||
},
|
||||
},
|
||||
},
|
||||
@ -218,7 +186,10 @@ export class HuiEnergySummaryGraphCard
|
||||
filter: (val) => val.formattedValue !== "0",
|
||||
callbacks: {
|
||||
label: (context) =>
|
||||
`${context.dataset.label}: ${Math.abs(context.parsed.y)} kWh`,
|
||||
`${context.dataset.label}: ${formatNumber(
|
||||
Math.abs(context.parsed.y),
|
||||
this.hass.locale
|
||||
)} kWh`,
|
||||
footer: (contexts) => {
|
||||
let totalConsumed = 0;
|
||||
let totalReturned = 0;
|
||||
@ -233,10 +204,16 @@ export class HuiEnergySummaryGraphCard
|
||||
}
|
||||
return [
|
||||
totalConsumed
|
||||
? `Total consumed: ${totalConsumed.toFixed(2)} kWh`
|
||||
? `Total consumed: ${formatNumber(
|
||||
totalConsumed,
|
||||
this.hass.locale
|
||||
)} kWh`
|
||||
: "",
|
||||
totalReturned
|
||||
? `Total returned: ${totalReturned.toFixed(2)} kWh`
|
||||
? `Total returned: ${formatNumber(
|
||||
totalReturned,
|
||||
this.hass.locale
|
||||
)} kWh`
|
||||
: "",
|
||||
].filter(Boolean);
|
||||
},
|
||||
@ -261,6 +238,8 @@ export class HuiEnergySummaryGraphCard
|
||||
hitRadius: 5,
|
||||
},
|
||||
},
|
||||
// @ts-expect-error
|
||||
locale: numberFormatToLocale(this.hass.locale),
|
||||
};
|
||||
}
|
||||
|
||||
@ -344,6 +323,23 @@ export class HuiEnergySummaryGraphCard
|
||||
} = {};
|
||||
const summedData: { [key: string]: { [start: string]: number } } = {};
|
||||
|
||||
const computedStyles = getComputedStyle(this);
|
||||
const colors = {
|
||||
to_grid: computedStyles
|
||||
.getPropertyValue("--energy-grid-return-color")
|
||||
.trim(),
|
||||
from_grid: computedStyles
|
||||
.getPropertyValue("--energy-grid-consumption-color")
|
||||
.trim(),
|
||||
used_solar: computedStyles
|
||||
.getPropertyValue("--energy-solar-color")
|
||||
.trim(),
|
||||
};
|
||||
|
||||
const backgroundColor = computedStyles
|
||||
.getPropertyValue("--card-background-color")
|
||||
.trim();
|
||||
|
||||
Object.entries(statistics).forEach(([key, statIds]) => {
|
||||
const sum = ["solar", "to_grid"].includes(key);
|
||||
const add = key !== "solar";
|
||||
@ -407,12 +403,13 @@ export class HuiEnergySummaryGraphCard
|
||||
const uniqueKeys = Array.from(new Set(allKeys));
|
||||
|
||||
Object.entries(combinedData).forEach(([type, sources]) => {
|
||||
const negative = NEGATIVE.includes(type);
|
||||
|
||||
Object.entries(sources).forEach(([statId, source], idx) => {
|
||||
const data: ChartDataset<"bar">[] = [];
|
||||
const entity = this.hass.states[statId];
|
||||
const color = COLORS[type];
|
||||
const borderColor =
|
||||
idx > 0
|
||||
? rgb2hex(lab2rgb(labDarken(rgb2lab(hex2rgb(colors[type])), idx)))
|
||||
: colors[type];
|
||||
|
||||
data.push({
|
||||
label:
|
||||
@ -421,28 +418,20 @@ export class HuiEnergySummaryGraphCard
|
||||
: entity
|
||||
? computeStateName(entity)
|
||||
: statId,
|
||||
borderColor:
|
||||
idx > 0
|
||||
? rgb2hex(lab2rgb(labDarken(rgb2lab(hex2rgb(color.border)), idx)))
|
||||
: color.border,
|
||||
backgroundColor:
|
||||
idx > 0
|
||||
? rgb2hex(
|
||||
lab2rgb(labDarken(rgb2lab(hex2rgb(color.background)), idx))
|
||||
)
|
||||
: color.background,
|
||||
borderColor,
|
||||
backgroundColor: hexBlend(borderColor, backgroundColor, 50),
|
||||
stack: "stack",
|
||||
data: [],
|
||||
});
|
||||
|
||||
// Process chart data.
|
||||
for (const key of uniqueKeys) {
|
||||
const value = key in source ? Math.round(source[key] * 100) / 100 : 0;
|
||||
const value = source[key] || 0;
|
||||
const date = new Date(key);
|
||||
// @ts-expect-error
|
||||
data[0].data.push({
|
||||
x: date.getTime(),
|
||||
y: value && negative ? -1 * value : value,
|
||||
y: value && type === "to_grid" ? -1 * value : value,
|
||||
});
|
||||
}
|
||||
|
||||
@ -470,43 +459,12 @@ export class HuiEnergySummaryGraphCard
|
||||
.has-header {
|
||||
padding-top: 0;
|
||||
}
|
||||
.chartLegend ul {
|
||||
padding-left: 20px;
|
||||
}
|
||||
.chartLegend li {
|
||||
padding: 2px 8px;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
box-sizing: border-box;
|
||||
color: var(--secondary-text-color);
|
||||
}
|
||||
.chartLegend li > div {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
.chartLegend .bullet {
|
||||
border-width: 1px;
|
||||
border-style: solid;
|
||||
border-radius: 4px;
|
||||
display: inline-block;
|
||||
height: 16px;
|
||||
margin-right: 6px;
|
||||
width: 32px;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
.value {
|
||||
font-weight: 300;
|
||||
}
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"hui-energy-summary-graph-card": HuiEnergySummaryGraphCard;
|
||||
"hui-energy-usage-graph-card": HuiEnergyUsageGraphCard;
|
||||
}
|
||||
}
|
@ -101,7 +101,7 @@ export interface EnergyDistributionCardConfig extends LovelaceCardConfig {
|
||||
title?: string;
|
||||
prefs: EnergyPreferences;
|
||||
}
|
||||
export interface EnergySummaryGraphCardConfig extends LovelaceCardConfig {
|
||||
export interface EnergyUsageGraphCardConfig extends LovelaceCardConfig {
|
||||
type: "energy-summary-graph";
|
||||
title?: string;
|
||||
prefs: EnergyPreferences;
|
||||
@ -119,6 +119,12 @@ export interface EnergyDevicesGraphCardConfig extends LovelaceCardConfig {
|
||||
prefs: EnergyPreferences;
|
||||
}
|
||||
|
||||
export interface EnergySourcesTableCardConfig extends LovelaceCardConfig {
|
||||
type: "energy-sources-table";
|
||||
title?: string;
|
||||
prefs: EnergyPreferences;
|
||||
}
|
||||
|
||||
export interface EnergySolarGaugeCardConfig extends LovelaceCardConfig {
|
||||
type: "energy-solar-consumed-gauge";
|
||||
title?: string;
|
||||
|
@ -35,14 +35,14 @@ const LAZY_LOAD_TYPES = {
|
||||
"alarm-panel": () => import("../cards/hui-alarm-panel-card"),
|
||||
error: () => import("../cards/hui-error-card"),
|
||||
"empty-state": () => import("../cards/hui-empty-state-card"),
|
||||
"energy-summary-graph": () =>
|
||||
import("../cards/energy/hui-energy-summary-graph-card"),
|
||||
"energy-usage-graph": () =>
|
||||
import("../cards/energy/hui-energy-usage-graph-card"),
|
||||
"energy-solar-graph": () =>
|
||||
import("../cards/energy/hui-energy-solar-graph-card"),
|
||||
"energy-devices-graph": () =>
|
||||
import("../cards/energy/hui-energy-devices-graph-card"),
|
||||
"energy-costs-table": () =>
|
||||
import("../cards/energy/hui-energy-costs-table-card"),
|
||||
"energy-sources-table": () =>
|
||||
import("../cards/energy/hui-energy-sources-table-card"),
|
||||
"energy-distribution": () =>
|
||||
import("../cards/energy/hui-energy-distribution-card"),
|
||||
"energy-solar-consumed-gauge": () =>
|
||||
|
@ -82,6 +82,14 @@ documentContainer.innerHTML = `<custom-style>
|
||||
--state-climate-dry-color: #efbd07;
|
||||
--state-climate-idle-color: #8a8a8a;
|
||||
|
||||
/* energy */
|
||||
--energy-grid-consumption-color: #126a9a;
|
||||
--energy-grid-return-color: #673ab7;
|
||||
--energy-solar-color: #ff9800;
|
||||
--energy-non-fossil-color: #0f9d58;
|
||||
|
||||
--rgb-energy-solar-color: 255, 152, 0;
|
||||
|
||||
/*
|
||||
Paper-styles color.html dependency is stripped on build.
|
||||
When a default paper-style color is used, it needs to be copied
|
||||
|
@ -31,6 +31,7 @@ export const darkStyles = {
|
||||
"codemirror-property": "#C792EA",
|
||||
"codemirror-qualifier": "#DECB6B",
|
||||
"codemirror-type": "#DECB6B",
|
||||
"energy-grid-return-color": "#b39bdb",
|
||||
};
|
||||
|
||||
export const derivedStyles = {
|
||||
|
Loading…
x
Reference in New Issue
Block a user