mirror of
				https://github.com/home-assistant/frontend.git
				synced 2025-10-30 05:59:42 +00:00 
			
		
		
		
	Compare commits
	
		
			9 Commits
		
	
	
		
			power
			...
			power-char
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
|   | 8b3b7e701a | ||
|   | 299c08c0ea | ||
|   | 2fd33069c1 | ||
|   | 8dde4c9a21 | ||
|   | 7a2826a580 | ||
|   | c32e5a049a | ||
|   | f294c1bae6 | ||
|   | 4aadd7ec71 | ||
|   | f68d885dd8 | 
| @@ -360,6 +360,35 @@ export const getReferencedStatisticIds = ( | |||||||
|   return statIDs; |   return statIDs; | ||||||
| }; | }; | ||||||
|  |  | ||||||
|  | export const getReferencedStatisticIdsPower = ( | ||||||
|  |   prefs: EnergyPreferences | ||||||
|  | ): string[] => { | ||||||
|  |   const statIDs: (string | undefined)[] = []; | ||||||
|  |  | ||||||
|  |   for (const source of prefs.energy_sources) { | ||||||
|  |     if (source.type === "gas" || source.type === "water") { | ||||||
|  |       continue; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     if (source.type === "solar") { | ||||||
|  |       statIDs.push(source.stat_power); | ||||||
|  |       continue; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     if (source.type === "battery") { | ||||||
|  |       statIDs.push(source.stat_power); | ||||||
|  |       continue; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     if (source.power) { | ||||||
|  |       statIDs.push(...source.power.map((p) => p.stat_power)); | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  |   statIDs.push(...prefs.device_consumption.map((d) => d.stat_power)); | ||||||
|  |  | ||||||
|  |   return statIDs.filter(Boolean) as string[]; | ||||||
|  | }; | ||||||
|  |  | ||||||
| export const enum CompareMode { | export const enum CompareMode { | ||||||
|   NONE = "", |   NONE = "", | ||||||
|   PREVIOUS = "previous", |   PREVIOUS = "previous", | ||||||
| @@ -407,9 +436,10 @@ const getEnergyData = async ( | |||||||
|     "gas", |     "gas", | ||||||
|     "device", |     "device", | ||||||
|   ]); |   ]); | ||||||
|  |   const powerStatIds = getReferencedStatisticIdsPower(prefs); | ||||||
|   const waterStatIds = getReferencedStatisticIds(prefs, info, ["water"]); |   const waterStatIds = getReferencedStatisticIds(prefs, info, ["water"]); | ||||||
|  |  | ||||||
|   const allStatIDs = [...energyStatIds, ...waterStatIds]; |   const allStatIDs = [...energyStatIds, ...waterStatIds, ...powerStatIds]; | ||||||
|  |  | ||||||
|   const dayDifference = differenceInDays(end || new Date(), start); |   const dayDifference = differenceInDays(end || new Date(), start); | ||||||
|   const period = |   const period = | ||||||
| @@ -420,6 +450,8 @@ const getEnergyData = async ( | |||||||
|       : dayDifference > 2 |       : dayDifference > 2 | ||||||
|         ? "day" |         ? "day" | ||||||
|         : "hour"; |         : "hour"; | ||||||
|  |   const finePeriod = | ||||||
|  |     dayDifference > 64 ? "day" : dayDifference > 8 ? "hour" : "5minute"; | ||||||
|  |  | ||||||
|   const statsMetadata: Record<string, StatisticsMetaData> = {}; |   const statsMetadata: Record<string, StatisticsMetaData> = {}; | ||||||
|   const statsMetadataArray = allStatIDs.length |   const statsMetadataArray = allStatIDs.length | ||||||
| @@ -441,6 +473,9 @@ const getEnergyData = async ( | |||||||
|       ? (gasUnit as (typeof VOLUME_UNITS)[number]) |       ? (gasUnit as (typeof VOLUME_UNITS)[number]) | ||||||
|       : undefined, |       : undefined, | ||||||
|   }; |   }; | ||||||
|  |   const powerUnits: StatisticsUnitConfiguration = { | ||||||
|  |     power: "kW", | ||||||
|  |   }; | ||||||
|   const waterUnit = getEnergyWaterUnit(hass, prefs, statsMetadata); |   const waterUnit = getEnergyWaterUnit(hass, prefs, statsMetadata); | ||||||
|   const waterUnits: StatisticsUnitConfiguration = { |   const waterUnits: StatisticsUnitConfiguration = { | ||||||
|     volume: waterUnit, |     volume: waterUnit, | ||||||
| @@ -451,6 +486,12 @@ const getEnergyData = async ( | |||||||
|         "change", |         "change", | ||||||
|       ]) |       ]) | ||||||
|     : {}; |     : {}; | ||||||
|  |   const _powerStats: Statistics | Promise<Statistics> = powerStatIds.length | ||||||
|  |     ? fetchStatistics(hass!, start, end, powerStatIds, finePeriod, powerUnits, [ | ||||||
|  |         "mean", | ||||||
|  |       ]) | ||||||
|  |     : {}; | ||||||
|  |  | ||||||
|   const _waterStats: Statistics | Promise<Statistics> = waterStatIds.length |   const _waterStats: Statistics | Promise<Statistics> = waterStatIds.length | ||||||
|     ? fetchStatistics(hass!, start, end, waterStatIds, period, waterUnits, [ |     ? fetchStatistics(hass!, start, end, waterStatIds, period, waterUnits, [ | ||||||
|         "change", |         "change", | ||||||
| @@ -557,6 +598,7 @@ const getEnergyData = async ( | |||||||
|  |  | ||||||
|   const [ |   const [ | ||||||
|     energyStats, |     energyStats, | ||||||
|  |     powerStats, | ||||||
|     waterStats, |     waterStats, | ||||||
|     energyStatsCompare, |     energyStatsCompare, | ||||||
|     waterStatsCompare, |     waterStatsCompare, | ||||||
| @@ -564,13 +606,14 @@ const getEnergyData = async ( | |||||||
|     fossilEnergyConsumptionCompare, |     fossilEnergyConsumptionCompare, | ||||||
|   ] = await Promise.all([ |   ] = await Promise.all([ | ||||||
|     _energyStats, |     _energyStats, | ||||||
|  |     _powerStats, | ||||||
|     _waterStats, |     _waterStats, | ||||||
|     _energyStatsCompare, |     _energyStatsCompare, | ||||||
|     _waterStatsCompare, |     _waterStatsCompare, | ||||||
|     _fossilEnergyConsumption, |     _fossilEnergyConsumption, | ||||||
|     _fossilEnergyConsumptionCompare, |     _fossilEnergyConsumptionCompare, | ||||||
|   ]); |   ]); | ||||||
|   const stats = { ...energyStats, ...waterStats }; |   const stats = { ...energyStats, ...waterStats, ...powerStats }; | ||||||
|   if (compare) { |   if (compare) { | ||||||
|     statsCompare = { ...energyStatsCompare, ...waterStatsCompare }; |     statsCompare = { ...energyStatsCompare, ...waterStatsCompare }; | ||||||
|   } |   } | ||||||
|   | |||||||
| @@ -16,6 +16,7 @@ import { | |||||||
| import type { | import type { | ||||||
|   BarSeriesOption, |   BarSeriesOption, | ||||||
|   CallbackDataParams, |   CallbackDataParams, | ||||||
|  |   LineSeriesOption, | ||||||
|   TopLevelFormatterParams, |   TopLevelFormatterParams, | ||||||
| } from "echarts/types/dist/shared"; | } from "echarts/types/dist/shared"; | ||||||
| import type { FrontendLocaleData } from "../../../../../data/translation"; | import type { FrontendLocaleData } from "../../../../../data/translation"; | ||||||
| @@ -170,11 +171,10 @@ function formatTooltip( | |||||||
|       compare |       compare | ||||||
|         ? `${(showCompareYear ? formatDateShort : formatDateVeryShort)(date, locale, config)}: ` |         ? `${(showCompareYear ? formatDateShort : formatDateVeryShort)(date, locale, config)}: ` | ||||||
|         : "" |         : "" | ||||||
|     }${formatTime(date, locale, config)} – ${formatTime( |     }${formatTime(date, locale, config)}`; | ||||||
|       addHours(date, 1), |     if (params[0].componentSubType === "bar") { | ||||||
|       locale, |       period += ` – ${formatTime(addHours(date, 1), locale, config)}`; | ||||||
|       config |     } | ||||||
|     )}`; |  | ||||||
|   } |   } | ||||||
|   const title = `<h4 style="text-align: center; margin: 0;">${period}</h4>`; |   const title = `<h4 style="text-align: center; margin: 0;">${period}</h4>`; | ||||||
|  |  | ||||||
| @@ -281,6 +281,35 @@ export function fillDataGapsAndRoundCaps(datasets: BarSeriesOption[]) { | |||||||
|   }); |   }); | ||||||
| } | } | ||||||
|  |  | ||||||
|  | export function fillLineGaps(datasets: LineSeriesOption[]) { | ||||||
|  |   const buckets = Array.from( | ||||||
|  |     new Set( | ||||||
|  |       datasets | ||||||
|  |         .map((dataset) => | ||||||
|  |           dataset.data!.map((datapoint) => Number(datapoint![0])) | ||||||
|  |         ) | ||||||
|  |         .flat() | ||||||
|  |     ) | ||||||
|  |   ).sort((a, b) => a - b); | ||||||
|  |   buckets.forEach((bucket, index) => { | ||||||
|  |     for (let i = datasets.length - 1; i >= 0; i--) { | ||||||
|  |       const dataPoint = datasets[i].data![index]; | ||||||
|  |       const item: any = | ||||||
|  |         dataPoint && typeof dataPoint === "object" && "value" in dataPoint | ||||||
|  |           ? dataPoint | ||||||
|  |           : { value: dataPoint }; | ||||||
|  |       const x = item.value?.[0]; | ||||||
|  |       if (x === undefined) { | ||||||
|  |         continue; | ||||||
|  |       } | ||||||
|  |       if (Number(x) !== bucket) { | ||||||
|  |         datasets[i].data?.splice(index, 0, [bucket, 0]); | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  |   }); | ||||||
|  |   return datasets; | ||||||
|  | } | ||||||
|  |  | ||||||
| export function getCompareTransform(start: Date, compareStart?: Date) { | export function getCompareTransform(start: Date, compareStart?: Date) { | ||||||
|   if (!compareStart) { |   if (!compareStart) { | ||||||
|     return (ts: Date) => ts; |     return (ts: Date) => ts; | ||||||
|   | |||||||
							
								
								
									
										305
									
								
								src/panels/lovelace/cards/energy/hui-power-sources-graph-card.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										305
									
								
								src/panels/lovelace/cards/energy/hui-power-sources-graph-card.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,305 @@ | |||||||
|  | import { endOfToday, isToday, startOfToday } from "date-fns"; | ||||||
|  | import type { HassConfig, UnsubscribeFunc } from "home-assistant-js-websocket"; | ||||||
|  | import type { PropertyValues } from "lit"; | ||||||
|  | import { css, html, LitElement, nothing } from "lit"; | ||||||
|  | import { customElement, property, state } from "lit/decorators"; | ||||||
|  | import { classMap } from "lit/directives/class-map"; | ||||||
|  | import memoizeOne from "memoize-one"; | ||||||
|  | import type { LineSeriesOption } from "echarts/charts"; | ||||||
|  | import { graphic } from "echarts"; | ||||||
|  | import "../../../../components/chart/ha-chart-base"; | ||||||
|  | import "../../../../components/ha-card"; | ||||||
|  | import type { EnergyData } from "../../../../data/energy"; | ||||||
|  | import { getEnergyDataCollection } from "../../../../data/energy"; | ||||||
|  | import type { StatisticValue } from "../../../../data/recorder"; | ||||||
|  | import type { FrontendLocaleData } from "../../../../data/translation"; | ||||||
|  | import { SubscribeMixin } from "../../../../mixins/subscribe-mixin"; | ||||||
|  | import type { HomeAssistant } from "../../../../types"; | ||||||
|  | import type { LovelaceCard } from "../../types"; | ||||||
|  | import type { PowerSourcesGraphCardConfig } from "../types"; | ||||||
|  | import { hasConfigChanged } from "../../common/has-changed"; | ||||||
|  | import { getCommonOptions, fillLineGaps } from "./common/energy-chart-options"; | ||||||
|  | import type { ECOption } from "../../../../resources/echarts"; | ||||||
|  | import { hex2rgb } from "../../../../common/color/convert-color"; | ||||||
|  |  | ||||||
|  | @customElement("hui-power-sources-graph-card") | ||||||
|  | export class HuiPowerSourcesGraphCard | ||||||
|  |   extends SubscribeMixin(LitElement) | ||||||
|  |   implements LovelaceCard | ||||||
|  | { | ||||||
|  |   @property({ attribute: false }) public hass!: HomeAssistant; | ||||||
|  |  | ||||||
|  |   @state() private _config?: PowerSourcesGraphCardConfig; | ||||||
|  |  | ||||||
|  |   @state() private _chartData: LineSeriesOption[] = []; | ||||||
|  |  | ||||||
|  |   @state() private _start = startOfToday(); | ||||||
|  |  | ||||||
|  |   @state() private _end = endOfToday(); | ||||||
|  |  | ||||||
|  |   @state() private _compareStart?: Date; | ||||||
|  |  | ||||||
|  |   @state() private _compareEnd?: Date; | ||||||
|  |  | ||||||
|  |   protected hassSubscribeRequiredHostProps = ["_config"]; | ||||||
|  |  | ||||||
|  |   public hassSubscribe(): UnsubscribeFunc[] { | ||||||
|  |     return [ | ||||||
|  |       getEnergyDataCollection(this.hass, { | ||||||
|  |         key: this._config?.collection_key, | ||||||
|  |       }).subscribe((data) => this._getStatistics(data)), | ||||||
|  |     ]; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   public getCardSize(): Promise<number> | number { | ||||||
|  |     return 3; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   public setConfig(config: PowerSourcesGraphCardConfig): void { | ||||||
|  |     this._config = config; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   protected shouldUpdate(changedProps: PropertyValues): boolean { | ||||||
|  |     return ( | ||||||
|  |       hasConfigChanged(this, changedProps) || | ||||||
|  |       changedProps.size > 1 || | ||||||
|  |       !changedProps.has("hass") | ||||||
|  |     ); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   protected render() { | ||||||
|  |     if (!this.hass || !this._config) { | ||||||
|  |       return nothing; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     return html` | ||||||
|  |       <ha-card> | ||||||
|  |         ${this._config.title | ||||||
|  |           ? html`<h1 class="card-header">${this._config.title}</h1>` | ||||||
|  |           : ""} | ||||||
|  |         <div | ||||||
|  |           class="content ${classMap({ | ||||||
|  |             "has-header": !!this._config.title, | ||||||
|  |           })}" | ||||||
|  |         > | ||||||
|  |           <ha-chart-base | ||||||
|  |             .hass=${this.hass} | ||||||
|  |             .data=${this._chartData} | ||||||
|  |             .options=${this._createOptions( | ||||||
|  |               this._start, | ||||||
|  |               this._end, | ||||||
|  |               this.hass.locale, | ||||||
|  |               this.hass.config, | ||||||
|  |               this._compareStart, | ||||||
|  |               this._compareEnd | ||||||
|  |             )} | ||||||
|  |           ></ha-chart-base> | ||||||
|  |           ${!this._chartData.some((dataset) => dataset.data!.length) | ||||||
|  |             ? html`<div class="no-data"> | ||||||
|  |                 ${isToday(this._start) | ||||||
|  |                   ? this.hass.localize("ui.panel.lovelace.cards.energy.no_data") | ||||||
|  |                   : this.hass.localize( | ||||||
|  |                       "ui.panel.lovelace.cards.energy.no_data_period" | ||||||
|  |                     )} | ||||||
|  |               </div>` | ||||||
|  |             : nothing} | ||||||
|  |         </div> | ||||||
|  |       </ha-card> | ||||||
|  |     `; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   private _createOptions = memoizeOne( | ||||||
|  |     ( | ||||||
|  |       start: Date, | ||||||
|  |       end: Date, | ||||||
|  |       locale: FrontendLocaleData, | ||||||
|  |       config: HassConfig, | ||||||
|  |       compareStart?: Date, | ||||||
|  |       compareEnd?: Date | ||||||
|  |     ): ECOption => | ||||||
|  |       getCommonOptions( | ||||||
|  |         start, | ||||||
|  |         end, | ||||||
|  |         locale, | ||||||
|  |         config, | ||||||
|  |         "kW", | ||||||
|  |         compareStart, | ||||||
|  |         compareEnd | ||||||
|  |       ) | ||||||
|  |   ); | ||||||
|  |  | ||||||
|  |   private async _getStatistics(energyData: EnergyData): Promise<void> { | ||||||
|  |     const datasets: LineSeriesOption[] = []; | ||||||
|  |  | ||||||
|  |     const statIds = { | ||||||
|  |       solar: { | ||||||
|  |         stats: [] as string[], | ||||||
|  |         color: "--energy-solar-color", | ||||||
|  |         name: this.hass.localize( | ||||||
|  |           "ui.panel.lovelace.cards.energy.power_graph.solar" | ||||||
|  |         ), | ||||||
|  |       }, | ||||||
|  |       grid: { | ||||||
|  |         stats: [] as string[], | ||||||
|  |         color: "--energy-grid-consumption-color", | ||||||
|  |         name: this.hass.localize( | ||||||
|  |           "ui.panel.lovelace.cards.energy.power_graph.grid" | ||||||
|  |         ), | ||||||
|  |       }, | ||||||
|  |       battery: { | ||||||
|  |         stats: [] as string[], | ||||||
|  |         color: "--energy-battery-out-color", | ||||||
|  |         name: this.hass.localize( | ||||||
|  |           "ui.panel.lovelace.cards.energy.power_graph.battery" | ||||||
|  |         ), | ||||||
|  |       }, | ||||||
|  |     }; | ||||||
|  |  | ||||||
|  |     const computedStyles = getComputedStyle(this); | ||||||
|  |  | ||||||
|  |     for (const source of energyData.prefs.energy_sources) { | ||||||
|  |       if (source.type === "solar") { | ||||||
|  |         if (source.stat_power) { | ||||||
|  |           statIds.solar.stats.push(source.stat_power); | ||||||
|  |         } | ||||||
|  |         continue; | ||||||
|  |       } | ||||||
|  |  | ||||||
|  |       if (source.type === "battery") { | ||||||
|  |         if (source.stat_power) { | ||||||
|  |           statIds.battery.stats.push(source.stat_power); | ||||||
|  |         } | ||||||
|  |         continue; | ||||||
|  |       } | ||||||
|  |  | ||||||
|  |       if (source.type === "grid" && source.power) { | ||||||
|  |         statIds.grid.stats.push(...source.power.map((p) => p.stat_power)); | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  |     const commonSeriesOptions: LineSeriesOption = { | ||||||
|  |       type: "line", | ||||||
|  |       smooth: 0.4, | ||||||
|  |       smoothMonotone: "x", | ||||||
|  |       lineStyle: { | ||||||
|  |         width: 1, | ||||||
|  |       }, | ||||||
|  |     }; | ||||||
|  |  | ||||||
|  |     Object.keys(statIds).forEach((key, keyIndex) => { | ||||||
|  |       if (statIds[key].stats.length) { | ||||||
|  |         const colorHex = computedStyles.getPropertyValue(statIds[key].color); | ||||||
|  |         const rgb = hex2rgb(colorHex); | ||||||
|  |         const { positive, negative } = this._processData( | ||||||
|  |           statIds[key].stats.map((id: string) => energyData.stats[id] ?? []) | ||||||
|  |         ); | ||||||
|  |         datasets.push({ | ||||||
|  |           ...commonSeriesOptions, | ||||||
|  |           id: key, | ||||||
|  |           name: statIds[key].name, | ||||||
|  |           color: colorHex, | ||||||
|  |           stack: "positive", | ||||||
|  |           areaStyle: { | ||||||
|  |             color: new graphic.LinearGradient(0, 0, 0, 1, [ | ||||||
|  |               { | ||||||
|  |                 offset: 0, | ||||||
|  |                 color: `rgba(${rgb[0]}, ${rgb[1]}, ${rgb[2]}, 0.75)`, | ||||||
|  |               }, | ||||||
|  |               { | ||||||
|  |                 offset: 1, | ||||||
|  |                 color: `rgba(${rgb[0]}, ${rgb[1]}, ${rgb[2]}, 0.25)`, | ||||||
|  |               }, | ||||||
|  |             ]), | ||||||
|  |           }, | ||||||
|  |           data: positive, | ||||||
|  |           z: 3 - keyIndex, // draw in reverse order so 0 value lines are overwritten | ||||||
|  |         }); | ||||||
|  |         if (key !== "solar") { | ||||||
|  |           datasets.push({ | ||||||
|  |             ...commonSeriesOptions, | ||||||
|  |             id: `${key}-negative`, | ||||||
|  |             name: statIds[key].name, | ||||||
|  |             color: colorHex, | ||||||
|  |             stack: "negative", | ||||||
|  |             areaStyle: { | ||||||
|  |               color: new graphic.LinearGradient(0, 1, 0, 0, [ | ||||||
|  |                 { | ||||||
|  |                   offset: 0, | ||||||
|  |                   color: `rgba(${rgb[0]}, ${rgb[1]}, ${rgb[2]}, 0.75)`, | ||||||
|  |                 }, | ||||||
|  |                 { | ||||||
|  |                   offset: 1, | ||||||
|  |                   color: `rgba(${rgb[0]}, ${rgb[1]}, ${rgb[2]}, 0.25)`, | ||||||
|  |                 }, | ||||||
|  |               ]), | ||||||
|  |             }, | ||||||
|  |             data: negative, | ||||||
|  |             z: 4 - keyIndex, // draw in reverse order but above positive series | ||||||
|  |           }); | ||||||
|  |         } | ||||||
|  |       } | ||||||
|  |     }); | ||||||
|  |  | ||||||
|  |     this._start = energyData.start; | ||||||
|  |     this._end = energyData.end || endOfToday(); | ||||||
|  |  | ||||||
|  |     this._chartData = fillLineGaps(datasets); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   private _processData(stats: StatisticValue[][]) { | ||||||
|  |     const data: Record<number, number[]> = {}; | ||||||
|  |     stats.forEach((statSet) => { | ||||||
|  |       statSet.forEach((point) => { | ||||||
|  |         if (point.mean == null) { | ||||||
|  |           return; | ||||||
|  |         } | ||||||
|  |         const x = (point.start + point.end) / 2; | ||||||
|  |         data[x] = [...(data[x] ?? []), point.mean]; | ||||||
|  |       }); | ||||||
|  |     }); | ||||||
|  |     const positive: [number, number][] = []; | ||||||
|  |     const negative: [number, number][] = []; | ||||||
|  |     Object.entries(data).forEach(([x, y]) => { | ||||||
|  |       const ts = Number(x); | ||||||
|  |       const meanY = y.reduce((a, b) => a + b, 0) / y.length; | ||||||
|  |       positive.push([ts, Math.max(0, meanY)]); | ||||||
|  |       negative.push([ts, Math.min(0, meanY)]); | ||||||
|  |     }); | ||||||
|  |     return { positive, negative }; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   static styles = css` | ||||||
|  |     ha-card { | ||||||
|  |       height: 100%; | ||||||
|  |     } | ||||||
|  |     .card-header { | ||||||
|  |       padding-bottom: 0; | ||||||
|  |     } | ||||||
|  |     .content { | ||||||
|  |       padding: var(--ha-space-4); | ||||||
|  |     } | ||||||
|  |     .has-header { | ||||||
|  |       padding-top: 0; | ||||||
|  |     } | ||||||
|  |     .no-data { | ||||||
|  |       position: absolute; | ||||||
|  |       height: 100%; | ||||||
|  |       top: 0; | ||||||
|  |       left: 0; | ||||||
|  |       right: 0; | ||||||
|  |       display: flex; | ||||||
|  |       justify-content: center; | ||||||
|  |       align-items: center; | ||||||
|  |       padding: 20%; | ||||||
|  |       margin-left: var(--ha-space-8); | ||||||
|  |       margin-inline-start: var(--ha-space-8); | ||||||
|  |       margin-inline-end: initial; | ||||||
|  |       box-sizing: border-box; | ||||||
|  |     } | ||||||
|  |   `; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | declare global { | ||||||
|  |   interface HTMLElementTagNameMap { | ||||||
|  |     "hui-power-sources-graph-card": HuiPowerSourcesGraphCard; | ||||||
|  |   } | ||||||
|  | } | ||||||
| @@ -227,6 +227,11 @@ export interface EnergySankeyCardConfig extends EnergyCardBaseConfig { | |||||||
|   group_by_area?: boolean; |   group_by_area?: boolean; | ||||||
| } | } | ||||||
|  |  | ||||||
|  | export interface PowerSourcesGraphCardConfig extends EnergyCardBaseConfig { | ||||||
|  |   type: "power-sources-graph"; | ||||||
|  |   title?: string; | ||||||
|  | } | ||||||
|  |  | ||||||
| export interface EntityFilterCardConfig extends LovelaceCardConfig { | export interface EntityFilterCardConfig extends LovelaceCardConfig { | ||||||
|   type: "entity-filter"; |   type: "entity-filter"; | ||||||
|   entities: (EntityFilterEntityConfig | string)[]; |   entities: (EntityFilterEntityConfig | string)[]; | ||||||
|   | |||||||
| @@ -66,6 +66,8 @@ const LAZY_LOAD_TYPES = { | |||||||
|   "energy-usage-graph": () => |   "energy-usage-graph": () => | ||||||
|     import("../cards/energy/hui-energy-usage-graph-card"), |     import("../cards/energy/hui-energy-usage-graph-card"), | ||||||
|   "energy-sankey": () => import("../cards/energy/hui-energy-sankey-card"), |   "energy-sankey": () => import("../cards/energy/hui-energy-sankey-card"), | ||||||
|  |   "power-sources-graph": () => | ||||||
|  |     import("../cards/energy/hui-power-sources-graph-card"), | ||||||
|   "entity-filter": () => import("../cards/hui-entity-filter-card"), |   "entity-filter": () => import("../cards/hui-entity-filter-card"), | ||||||
|   error: () => import("../cards/hui-error-card"), |   error: () => import("../cards/hui-error-card"), | ||||||
|   "home-summary": () => import("../cards/hui-home-summary-card"), |   "home-summary": () => import("../cards/hui-home-summary-card"), | ||||||
|   | |||||||
| @@ -22,6 +22,7 @@ const NON_STANDARD_URLS = { | |||||||
|   "energy-devices-graph": "energy/#devices-energy-graph", |   "energy-devices-graph": "energy/#devices-energy-graph", | ||||||
|   "energy-devices-detail-graph": "energy/#detail-devices-energy-graph", |   "energy-devices-detail-graph": "energy/#detail-devices-energy-graph", | ||||||
|   "energy-sankey": "energy/#sankey-energy-graph", |   "energy-sankey": "energy/#sankey-energy-graph", | ||||||
|  |   "power-sources-graph": "energy/#power-sources-graph", | ||||||
| }; | }; | ||||||
|  |  | ||||||
| export const getCardDocumentationURL = ( | export const getCardDocumentationURL = ( | ||||||
|   | |||||||
| @@ -7053,6 +7053,11 @@ | |||||||
|               "card_indicates_energy_used": "This card indicates how much of the electricity consumed by your home was generated using non-fossil fuels like solar, wind, and nuclear. The higher, the better!", |               "card_indicates_energy_used": "This card indicates how much of the electricity consumed by your home was generated using non-fossil fuels like solar, wind, and nuclear. The higher, the better!", | ||||||
|               "low_carbon_energy_consumed": "Low-carbon electricity consumed", |               "low_carbon_energy_consumed": "Low-carbon electricity consumed", | ||||||
|               "low_carbon_energy_not_calculated": "Consumed low-carbon electricity couldn't be calculated" |               "low_carbon_energy_not_calculated": "Consumed low-carbon electricity couldn't be calculated" | ||||||
|  |             }, | ||||||
|  |             "power_graph": { | ||||||
|  |               "grid": "Grid", | ||||||
|  |               "solar": "Solar", | ||||||
|  |               "battery": "Battery" | ||||||
|             } |             } | ||||||
|           }, |           }, | ||||||
|           "heading": { |           "heading": { | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user