mirror of
https://github.com/home-assistant/frontend.git
synced 2025-04-25 05:47:20 +00:00
Add option to compare energy graphs with previous period (#12723)
Co-authored-by: Paulus Schoutsen <balloob@gmail.com>
This commit is contained in:
parent
a0a7ce014f
commit
6ab19d66d5
@ -1,5 +1,8 @@
|
||||
import {
|
||||
addDays,
|
||||
addHours,
|
||||
addMilliseconds,
|
||||
addMonths,
|
||||
differenceInDays,
|
||||
endOfToday,
|
||||
endOfYesterday,
|
||||
@ -14,9 +17,9 @@ import { ConfigEntry, getConfigEntries } from "./config_entries";
|
||||
import { subscribeEntityRegistry } from "./entity_registry";
|
||||
import {
|
||||
fetchStatistics,
|
||||
getStatisticMetadata,
|
||||
Statistics,
|
||||
StatisticsMetaData,
|
||||
getStatisticMetadata,
|
||||
} from "./history";
|
||||
|
||||
const energyCollectionKeys: (string | undefined)[] = [];
|
||||
@ -232,19 +235,24 @@ export const energySourcesByType = (prefs: EnergyPreferences) =>
|
||||
export interface EnergyData {
|
||||
start: Date;
|
||||
end?: Date;
|
||||
startCompare?: Date;
|
||||
endCompare?: Date;
|
||||
prefs: EnergyPreferences;
|
||||
info: EnergyInfo;
|
||||
stats: Statistics;
|
||||
statsCompare: Statistics;
|
||||
co2SignalConfigEntry?: ConfigEntry;
|
||||
co2SignalEntity?: string;
|
||||
fossilEnergyConsumption?: FossilEnergyConsumption;
|
||||
fossilEnergyConsumptionCompare?: FossilEnergyConsumption;
|
||||
}
|
||||
|
||||
const getEnergyData = async (
|
||||
hass: HomeAssistant,
|
||||
prefs: EnergyPreferences,
|
||||
start: Date,
|
||||
end?: Date
|
||||
end?: Date,
|
||||
compare?: boolean
|
||||
): Promise<EnergyData> => {
|
||||
const [configEntries, entityRegistryEntries, info] = await Promise.all([
|
||||
getConfigEntries(hass, { domain: "co2signal" }),
|
||||
@ -350,6 +358,8 @@ const getEnergyData = async (
|
||||
}
|
||||
|
||||
const dayDifference = differenceInDays(end || new Date(), start);
|
||||
const period =
|
||||
dayDifference > 35 ? "month" : dayDifference > 2 ? "day" : "hour";
|
||||
|
||||
// Subtract 1 hour from start to get starting point data
|
||||
const startMinHour = addHours(start, -1);
|
||||
@ -359,10 +369,34 @@ const getEnergyData = async (
|
||||
startMinHour,
|
||||
end,
|
||||
statIDs,
|
||||
dayDifference > 35 ? "month" : dayDifference > 2 ? "day" : "hour"
|
||||
period
|
||||
);
|
||||
|
||||
let statsCompare;
|
||||
let startCompare;
|
||||
let endCompare;
|
||||
if (compare) {
|
||||
if (dayDifference > 27 && dayDifference < 32) {
|
||||
// When comparing a month, we want to start at the begining of the month
|
||||
startCompare = addMonths(start, -1);
|
||||
} else {
|
||||
startCompare = addDays(start, (dayDifference + 1) * -1);
|
||||
}
|
||||
|
||||
const compareStartMinHour = addHours(startCompare, -1);
|
||||
endCompare = addMilliseconds(start, -1);
|
||||
|
||||
statsCompare = await fetchStatistics(
|
||||
hass!,
|
||||
compareStartMinHour,
|
||||
endCompare,
|
||||
statIDs,
|
||||
period
|
||||
);
|
||||
}
|
||||
|
||||
let fossilEnergyConsumption: FossilEnergyConsumption | undefined;
|
||||
let fossilEnergyConsumptionCompare: FossilEnergyConsumption | undefined;
|
||||
|
||||
if (co2SignalEntity !== undefined) {
|
||||
fossilEnergyConsumption = await getFossilEnergyConsumption(
|
||||
@ -373,6 +407,16 @@ const getEnergyData = async (
|
||||
end,
|
||||
dayDifference > 35 ? "month" : dayDifference > 2 ? "day" : "hour"
|
||||
);
|
||||
if (compare) {
|
||||
fossilEnergyConsumptionCompare = await getFossilEnergyConsumption(
|
||||
hass!,
|
||||
startCompare,
|
||||
consumptionStatIDs,
|
||||
co2SignalEntity,
|
||||
endCompare,
|
||||
dayDifference > 35 ? "month" : dayDifference > 2 ? "day" : "hour"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Object.values(stats).forEach((stat) => {
|
||||
@ -388,15 +432,19 @@ const getEnergyData = async (
|
||||
}
|
||||
});
|
||||
|
||||
const data = {
|
||||
const data: EnergyData = {
|
||||
start,
|
||||
end,
|
||||
startCompare,
|
||||
endCompare,
|
||||
info,
|
||||
prefs,
|
||||
stats,
|
||||
statsCompare,
|
||||
co2SignalConfigEntry,
|
||||
co2SignalEntity,
|
||||
fossilEnergyConsumption,
|
||||
fossilEnergyConsumptionCompare,
|
||||
};
|
||||
|
||||
return data;
|
||||
@ -405,9 +453,11 @@ const getEnergyData = async (
|
||||
export interface EnergyCollection extends Collection<EnergyData> {
|
||||
start: Date;
|
||||
end?: Date;
|
||||
compare?: boolean;
|
||||
prefs?: EnergyPreferences;
|
||||
clearPrefs(): void;
|
||||
setPeriod(newStart: Date, newEnd?: Date): void;
|
||||
setCompare(compare: boolean): void;
|
||||
_refreshTimeout?: number;
|
||||
_updatePeriodTimeout?: number;
|
||||
_active: number;
|
||||
@ -478,7 +528,8 @@ export const getEnergyDataCollection = (
|
||||
hass,
|
||||
collection.prefs,
|
||||
collection.start,
|
||||
collection.end
|
||||
collection.end,
|
||||
collection.compare
|
||||
);
|
||||
}
|
||||
) as EnergyCollection;
|
||||
@ -534,6 +585,9 @@ export const getEnergyDataCollection = (
|
||||
collection._updatePeriodTimeout = undefined;
|
||||
}
|
||||
};
|
||||
collection.setCompare = (compare: boolean) => {
|
||||
collection.compare = compare;
|
||||
};
|
||||
return collection;
|
||||
};
|
||||
|
||||
|
@ -1,7 +1,5 @@
|
||||
import "@polymer/app-layout/app-header/app-header";
|
||||
import "@polymer/app-layout/app-toolbar/app-toolbar";
|
||||
import "@material/mwc-tab";
|
||||
import "@material/mwc-tab-bar";
|
||||
import {
|
||||
css,
|
||||
CSSResultGroup,
|
||||
@ -12,14 +10,13 @@ import {
|
||||
} from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import "../../components/ha-menu-button";
|
||||
import "../../layouts/ha-app-layout";
|
||||
|
||||
import { haStyle } from "../../resources/styles";
|
||||
import "../lovelace/views/hui-view";
|
||||
import { HomeAssistant } from "../../types";
|
||||
import { Lovelace } from "../lovelace/types";
|
||||
import { LovelaceConfig } from "../../data/lovelace";
|
||||
import "../../layouts/ha-app-layout";
|
||||
import { haStyle } from "../../resources/styles";
|
||||
import { HomeAssistant } from "../../types";
|
||||
import "../lovelace/components/hui-energy-period-selector";
|
||||
import { Lovelace } from "../lovelace/types";
|
||||
import "../lovelace/views/hui-view";
|
||||
|
||||
const LOVELACE_CONFIG: LovelaceConfig = {
|
||||
views: [
|
||||
|
@ -60,6 +60,11 @@ export class EnergyStrategy {
|
||||
});
|
||||
}
|
||||
|
||||
view.cards!.push({
|
||||
type: "energy-compare",
|
||||
collection_key: "energy_dashboard",
|
||||
});
|
||||
|
||||
// Only include if we have a grid source.
|
||||
if (hasGrid) {
|
||||
view.cards!.push({
|
||||
|
106
src/panels/lovelace/cards/energy/hui-energy-compare-card.ts
Normal file
106
src/panels/lovelace/cards/energy/hui-energy-compare-card.ts
Normal file
@ -0,0 +1,106 @@
|
||||
import { differenceInDays, endOfDay } from "date-fns";
|
||||
import { UnsubscribeFunc } from "home-assistant-js-websocket";
|
||||
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import { formatDate } from "../../../../common/datetime/format_date";
|
||||
import { EnergyData, getEnergyDataCollection } from "../../../../data/energy";
|
||||
import { SubscribeMixin } from "../../../../mixins/subscribe-mixin";
|
||||
import { HomeAssistant } from "../../../../types";
|
||||
import { LovelaceCard } from "../../types";
|
||||
import { EnergyCardBaseConfig } from "../types";
|
||||
|
||||
@customElement("hui-energy-compare-card")
|
||||
export class HuiEnergyCompareCard
|
||||
extends SubscribeMixin(LitElement)
|
||||
implements LovelaceCard
|
||||
{
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
|
||||
@state() private _config?: EnergyCardBaseConfig;
|
||||
|
||||
@state() private _start?: Date;
|
||||
|
||||
@state() private _end?: Date;
|
||||
|
||||
@state() private _startCompare?: Date;
|
||||
|
||||
@state() private _endCompare?: Date;
|
||||
|
||||
public getCardSize(): Promise<number> | number {
|
||||
return 1;
|
||||
}
|
||||
|
||||
public setConfig(config: EnergyCardBaseConfig): void {
|
||||
this._config = config;
|
||||
}
|
||||
|
||||
protected hassSubscribeRequiredHostProps = ["_config"];
|
||||
|
||||
public hassSubscribe(): UnsubscribeFunc[] {
|
||||
return [
|
||||
getEnergyDataCollection(this.hass, {
|
||||
key: this._config!.collection_key,
|
||||
}).subscribe((data) => this._update(data)),
|
||||
];
|
||||
}
|
||||
|
||||
protected render(): TemplateResult {
|
||||
if (!this._startCompare || !this._endCompare) {
|
||||
return html``;
|
||||
}
|
||||
|
||||
const dayDifference = differenceInDays(
|
||||
this._endCompare,
|
||||
this._startCompare
|
||||
);
|
||||
|
||||
return html`
|
||||
<ha-alert dismissable @alert-dismissed-clicked=${this._stopCompare}>
|
||||
You are comparing the period
|
||||
<b
|
||||
>${formatDate(this._start!, this.hass.locale)}${dayDifference > 0
|
||||
? ` -
|
||||
${formatDate(this._end || endOfDay(new Date()), this.hass.locale)}`
|
||||
: ""}</b
|
||||
>
|
||||
with period
|
||||
<b
|
||||
>${formatDate(this._startCompare, this.hass.locale)}${dayDifference >
|
||||
0
|
||||
? ` -
|
||||
${formatDate(this._endCompare, this.hass.locale)}`
|
||||
: ""}</b
|
||||
>
|
||||
</ha-alert>
|
||||
`;
|
||||
}
|
||||
|
||||
private _update(data: EnergyData): void {
|
||||
this._start = data.start;
|
||||
this._end = data.end;
|
||||
this._startCompare = data.startCompare;
|
||||
this._endCompare = data.endCompare;
|
||||
}
|
||||
|
||||
private _stopCompare(): void {
|
||||
const energyCollection = getEnergyDataCollection(this.hass, {
|
||||
key: this._config!.collection_key,
|
||||
});
|
||||
energyCollection.setCompare(false);
|
||||
energyCollection.refresh();
|
||||
}
|
||||
|
||||
static get styles(): CSSResultGroup {
|
||||
return css`
|
||||
mwc-button {
|
||||
width: max-content;
|
||||
}
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"hui-energy-compare-card": HuiEnergyCompareCard;
|
||||
}
|
||||
}
|
@ -1,8 +1,8 @@
|
||||
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
|
||||
import { html, LitElement, TemplateResult } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import { HomeAssistant } from "../../../../types";
|
||||
import { LovelaceCard } from "../../types";
|
||||
import { EnergyDevicesGraphCardConfig } from "../types";
|
||||
import { EnergyCardBaseConfig } from "../types";
|
||||
import "../../components/hui-energy-period-selector";
|
||||
|
||||
@customElement("hui-energy-date-selection-card")
|
||||
@ -12,13 +12,13 @@ export class HuiEnergyDateSelectionCard
|
||||
{
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
|
||||
@state() private _config?: EnergyDevicesGraphCardConfig;
|
||||
@state() private _config?: EnergyCardBaseConfig;
|
||||
|
||||
public getCardSize(): Promise<number> | number {
|
||||
return 1;
|
||||
}
|
||||
|
||||
public setConfig(config: EnergyDevicesGraphCardConfig): void {
|
||||
public setConfig(config: EnergyCardBaseConfig): void {
|
||||
this._config = config;
|
||||
}
|
||||
|
||||
@ -34,10 +34,6 @@ export class HuiEnergyDateSelectionCard
|
||||
></hui-energy-period-selector>
|
||||
`;
|
||||
}
|
||||
|
||||
static get styles(): CSSResultGroup {
|
||||
return css``;
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
|
@ -1,9 +1,3 @@
|
||||
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import { UnsubscribeFunc } from "home-assistant-js-websocket";
|
||||
import memoizeOne from "memoize-one";
|
||||
import { classMap } from "lit/directives/class-map";
|
||||
import "../../../../components/ha-card";
|
||||
import {
|
||||
ChartData,
|
||||
ChartDataset,
|
||||
@ -13,13 +7,16 @@ import {
|
||||
import {
|
||||
addHours,
|
||||
differenceInDays,
|
||||
differenceInHours,
|
||||
endOfToday,
|
||||
isToday,
|
||||
startOfToday,
|
||||
} from "date-fns/esm";
|
||||
import { HomeAssistant } from "../../../../types";
|
||||
import { LovelaceCard } from "../../types";
|
||||
import { EnergyGasGraphCardConfig } from "../types";
|
||||
} from "date-fns";
|
||||
import { UnsubscribeFunc } from "home-assistant-js-websocket";
|
||||
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import { classMap } from "lit/directives/class-map";
|
||||
import memoizeOne from "memoize-one";
|
||||
import {
|
||||
hex2rgb,
|
||||
lab2rgb,
|
||||
@ -27,21 +24,27 @@ import {
|
||||
rgb2lab,
|
||||
} from "../../../../common/color/convert-color";
|
||||
import { labBrighten, labDarken } from "../../../../common/color/lab";
|
||||
import {
|
||||
EnergyData,
|
||||
getEnergyDataCollection,
|
||||
getEnergyGasUnit,
|
||||
GasSourceTypeEnergyPreference,
|
||||
} from "../../../../data/energy";
|
||||
import { formatDateShort } from "../../../../common/datetime/format_date";
|
||||
import { formatTime } from "../../../../common/datetime/format_time";
|
||||
import { computeStateName } from "../../../../common/entity/compute_state_name";
|
||||
import "../../../../components/chart/ha-chart-base";
|
||||
import {
|
||||
formatNumber,
|
||||
numberFormatToLocale,
|
||||
} from "../../../../common/number/format_number";
|
||||
import { SubscribeMixin } from "../../../../mixins/subscribe-mixin";
|
||||
import "../../../../components/chart/ha-chart-base";
|
||||
import "../../../../components/ha-card";
|
||||
import {
|
||||
EnergyData,
|
||||
GasSourceTypeEnergyPreference,
|
||||
getEnergyDataCollection,
|
||||
getEnergyGasUnit,
|
||||
} from "../../../../data/energy";
|
||||
import { Statistics } from "../../../../data/history";
|
||||
import { FrontendLocaleData } from "../../../../data/translation";
|
||||
import { formatTime } from "../../../../common/datetime/format_time";
|
||||
import { SubscribeMixin } from "../../../../mixins/subscribe-mixin";
|
||||
import { HomeAssistant } from "../../../../types";
|
||||
import { LovelaceCard } from "../../types";
|
||||
import { EnergyGasGraphCardConfig } from "../types";
|
||||
|
||||
@customElement("hui-energy-gas-graph-card")
|
||||
export class HuiEnergyGasGraphCard
|
||||
@ -60,6 +63,10 @@ export class HuiEnergyGasGraphCard
|
||||
|
||||
@state() private _end = endOfToday();
|
||||
|
||||
@state() private _compareStart?: Date;
|
||||
|
||||
@state() private _compareEnd?: Date;
|
||||
|
||||
@state() private _unit?: string;
|
||||
|
||||
protected hassSubscribeRequiredHostProps = ["_config"];
|
||||
@ -101,7 +108,9 @@ export class HuiEnergyGasGraphCard
|
||||
this._start,
|
||||
this._end,
|
||||
this.hass.locale,
|
||||
this._unit
|
||||
this._unit,
|
||||
this._compareStart,
|
||||
this._compareEnd
|
||||
)}
|
||||
chart-type="bar"
|
||||
></ha-chart-base>
|
||||
@ -124,10 +133,24 @@ export class HuiEnergyGasGraphCard
|
||||
start: Date,
|
||||
end: Date,
|
||||
locale: FrontendLocaleData,
|
||||
unit?: string
|
||||
unit?: string,
|
||||
compareStart?: Date,
|
||||
compareEnd?: Date
|
||||
): ChartOptions => {
|
||||
const dayDifference = differenceInDays(end, start);
|
||||
return {
|
||||
const compare = compareStart !== undefined && compareEnd !== undefined;
|
||||
if (compare) {
|
||||
const difference = differenceInHours(end, start);
|
||||
const differenceCompare = differenceInHours(compareEnd!, compareStart!);
|
||||
// If the compare period doesn't match the main period, adjust them to match
|
||||
if (differenceCompare > difference) {
|
||||
end = addHours(end, differenceCompare - difference);
|
||||
} else if (difference > differenceCompare) {
|
||||
compareEnd = addHours(compareEnd!, difference - differenceCompare);
|
||||
}
|
||||
}
|
||||
|
||||
const options: ChartOptions = {
|
||||
parsing: false,
|
||||
animation: false,
|
||||
scales: {
|
||||
@ -193,7 +216,9 @@ export class HuiEnergyGasGraphCard
|
||||
return datasets[0].label;
|
||||
}
|
||||
const date = new Date(datasets[0].parsed.x);
|
||||
return `${formatTime(date, locale)} – ${formatTime(
|
||||
return `${
|
||||
compare ? `${formatDateShort(date, locale)}: ` : ""
|
||||
}${formatTime(date, locale)} – ${formatTime(
|
||||
addHours(date, 1),
|
||||
locale
|
||||
)}`;
|
||||
@ -227,6 +252,15 @@ export class HuiEnergyGasGraphCard
|
||||
// @ts-expect-error
|
||||
locale: numberFormatToLocale(locale),
|
||||
};
|
||||
if (compare) {
|
||||
options.scales!.xAxisCompare = {
|
||||
...(options.scales!.x as Record<string, any>),
|
||||
suggestedMin: compareStart!.getTime(),
|
||||
suggestedMax: compareEnd!.getTime(),
|
||||
display: false,
|
||||
};
|
||||
}
|
||||
return options;
|
||||
}
|
||||
);
|
||||
|
||||
@ -238,15 +272,58 @@ export class HuiEnergyGasGraphCard
|
||||
|
||||
this._unit = getEnergyGasUnit(this.hass, energyData.prefs) || "m³";
|
||||
|
||||
const datasets: ChartDataset<"bar">[] = [];
|
||||
const datasets: ChartDataset<"bar", ScatterDataPoint[]>[] = [];
|
||||
|
||||
const computedStyles = getComputedStyle(this);
|
||||
const gasColor = computedStyles
|
||||
.getPropertyValue("--energy-gas-color")
|
||||
.trim();
|
||||
|
||||
datasets.push(
|
||||
...this._processDataSet(energyData.stats, gasSources, gasColor)
|
||||
);
|
||||
|
||||
if (energyData.statsCompare) {
|
||||
// Add empty dataset to align the bars
|
||||
datasets.push({
|
||||
order: 0,
|
||||
data: [],
|
||||
});
|
||||
datasets.push({
|
||||
order: 999,
|
||||
data: [],
|
||||
xAxisID: "xAxisCompare",
|
||||
});
|
||||
|
||||
datasets.push(
|
||||
...this._processDataSet(
|
||||
energyData.statsCompare,
|
||||
gasSources,
|
||||
gasColor,
|
||||
true
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
this._start = energyData.start;
|
||||
this._end = energyData.end || endOfToday();
|
||||
|
||||
this._compareStart = energyData.startCompare;
|
||||
this._compareEnd = energyData.endCompare;
|
||||
|
||||
this._chartData = {
|
||||
datasets,
|
||||
};
|
||||
}
|
||||
|
||||
private _processDataSet(
|
||||
statistics: Statistics,
|
||||
gasSources: GasSourceTypeEnergyPreference[],
|
||||
gasColor: string,
|
||||
compare = false
|
||||
) {
|
||||
const data: ChartDataset<"bar", ScatterDataPoint[]>[] = [];
|
||||
gasSources.forEach((source, idx) => {
|
||||
const data: ChartDataset<"bar" | "line">[] = [];
|
||||
const entity = this.hass.states[source.stat_energy_from];
|
||||
|
||||
const modifiedColor =
|
||||
@ -265,8 +342,8 @@ export class HuiEnergyGasGraphCard
|
||||
const gasConsumptionData: ScatterDataPoint[] = [];
|
||||
|
||||
// Process gas consumption data.
|
||||
if (source.stat_energy_from in energyData.stats) {
|
||||
const stats = energyData.stats[source.stat_energy_from];
|
||||
if (source.stat_energy_from in statistics) {
|
||||
const stats = statistics[source.stat_energy_from];
|
||||
|
||||
for (const point of stats) {
|
||||
if (point.sum === null) {
|
||||
@ -290,26 +367,17 @@ export class HuiEnergyGasGraphCard
|
||||
}
|
||||
}
|
||||
|
||||
if (gasConsumptionData.length) {
|
||||
data.push({
|
||||
label: entity ? computeStateName(entity) : source.stat_energy_from,
|
||||
borderColor,
|
||||
backgroundColor: borderColor + "7F",
|
||||
data: gasConsumptionData,
|
||||
stack: "gas",
|
||||
});
|
||||
}
|
||||
|
||||
// Concat two arrays
|
||||
Array.prototype.push.apply(datasets, data);
|
||||
data.push({
|
||||
label: entity ? computeStateName(entity) : source.stat_energy_from,
|
||||
borderColor: compare ? borderColor + "7F" : borderColor,
|
||||
backgroundColor: compare ? borderColor + "32" : borderColor + "7F",
|
||||
data: gasConsumptionData,
|
||||
order: 1,
|
||||
stack: "gas",
|
||||
xAxisID: compare ? "xAxisCompare" : undefined,
|
||||
});
|
||||
});
|
||||
|
||||
this._start = energyData.start;
|
||||
this._end = energyData.end || endOfToday();
|
||||
|
||||
this._chartData = {
|
||||
datasets,
|
||||
};
|
||||
return data;
|
||||
}
|
||||
|
||||
static get styles(): CSSResultGroup {
|
||||
|
@ -7,6 +7,7 @@ import {
|
||||
import {
|
||||
addHours,
|
||||
differenceInDays,
|
||||
differenceInHours,
|
||||
endOfToday,
|
||||
isToday,
|
||||
startOfToday,
|
||||
@ -23,6 +24,7 @@ import {
|
||||
rgb2lab,
|
||||
} from "../../../../common/color/convert-color";
|
||||
import { labBrighten, labDarken } from "../../../../common/color/lab";
|
||||
import { formatDateShort } from "../../../../common/datetime/format_date";
|
||||
import { formatTime } from "../../../../common/datetime/format_time";
|
||||
import { computeStateName } from "../../../../common/entity/compute_state_name";
|
||||
import {
|
||||
@ -38,6 +40,7 @@ import {
|
||||
getEnergySolarForecasts,
|
||||
SolarSourceTypeEnergyPreference,
|
||||
} from "../../../../data/energy";
|
||||
import { Statistics } from "../../../../data/history";
|
||||
import { FrontendLocaleData } from "../../../../data/translation";
|
||||
import { SubscribeMixin } from "../../../../mixins/subscribe-mixin";
|
||||
import { HomeAssistant } from "../../../../types";
|
||||
@ -61,6 +64,10 @@ export class HuiEnergySolarGraphCard
|
||||
|
||||
@state() private _end = endOfToday();
|
||||
|
||||
@state() private _compareStart?: Date;
|
||||
|
||||
@state() private _compareEnd?: Date;
|
||||
|
||||
protected hassSubscribeRequiredHostProps = ["_config"];
|
||||
|
||||
public hassSubscribe(): UnsubscribeFunc[] {
|
||||
@ -99,7 +106,9 @@ export class HuiEnergySolarGraphCard
|
||||
.options=${this._createOptions(
|
||||
this._start,
|
||||
this._end,
|
||||
this.hass.locale
|
||||
this.hass.locale,
|
||||
this._compareStart,
|
||||
this._compareEnd
|
||||
)}
|
||||
chart-type="bar"
|
||||
></ha-chart-base>
|
||||
@ -118,9 +127,27 @@ export class HuiEnergySolarGraphCard
|
||||
}
|
||||
|
||||
private _createOptions = memoizeOne(
|
||||
(start: Date, end: Date, locale: FrontendLocaleData): ChartOptions => {
|
||||
(
|
||||
start: Date,
|
||||
end: Date,
|
||||
locale: FrontendLocaleData,
|
||||
compareStart?: Date,
|
||||
compareEnd?: Date
|
||||
): ChartOptions => {
|
||||
const dayDifference = differenceInDays(end, start);
|
||||
return {
|
||||
const compare = compareStart !== undefined && compareEnd !== undefined;
|
||||
if (compare) {
|
||||
const difference = differenceInHours(end, start);
|
||||
const differenceCompare = differenceInHours(compareEnd!, compareStart!);
|
||||
// If the compare period doesn't match the main period, adjust them to match
|
||||
if (differenceCompare > difference) {
|
||||
end = addHours(end, differenceCompare - difference);
|
||||
} else if (difference > differenceCompare) {
|
||||
compareEnd = addHours(compareEnd!, difference - differenceCompare);
|
||||
}
|
||||
}
|
||||
|
||||
const options: ChartOptions = {
|
||||
parsing: false,
|
||||
animation: false,
|
||||
scales: {
|
||||
@ -163,7 +190,6 @@ export class HuiEnergySolarGraphCard
|
||||
? "day"
|
||||
: "hour",
|
||||
},
|
||||
offset: true,
|
||||
},
|
||||
y: {
|
||||
stacked: true,
|
||||
@ -186,7 +212,9 @@ export class HuiEnergySolarGraphCard
|
||||
return datasets[0].label;
|
||||
}
|
||||
const date = new Date(datasets[0].parsed.x);
|
||||
return `${formatTime(date, locale)} – ${formatTime(
|
||||
return `${
|
||||
compare ? `${formatDateShort(date, locale)}: ` : ""
|
||||
}${formatTime(date, locale)} – ${formatTime(
|
||||
addHours(date, 1),
|
||||
locale
|
||||
)}`;
|
||||
@ -224,6 +252,15 @@ export class HuiEnergySolarGraphCard
|
||||
// @ts-expect-error
|
||||
locale: numberFormatToLocale(locale),
|
||||
};
|
||||
if (compare) {
|
||||
options.scales!.xAxisCompare = {
|
||||
...(options.scales!.x as Record<string, any>),
|
||||
suggestedMin: compareStart!.getTime(),
|
||||
suggestedMax: compareEnd!.getTime(),
|
||||
display: false,
|
||||
};
|
||||
}
|
||||
return options;
|
||||
}
|
||||
);
|
||||
|
||||
@ -244,20 +281,71 @@ export class HuiEnergySolarGraphCard
|
||||
}
|
||||
}
|
||||
|
||||
const datasets: ChartDataset<"bar">[] = [];
|
||||
const datasets: ChartDataset<"bar" | "line">[] = [];
|
||||
|
||||
const computedStyles = getComputedStyle(this);
|
||||
const solarColor = computedStyles
|
||||
.getPropertyValue("--energy-solar-color")
|
||||
.trim();
|
||||
|
||||
const dayDifference = differenceInDays(
|
||||
energyData.end || new Date(),
|
||||
energyData.start
|
||||
datasets.push(
|
||||
...this._processDataSet(energyData.stats, solarSources, solarColor)
|
||||
);
|
||||
|
||||
if (energyData.statsCompare) {
|
||||
// Add empty dataset to align the bars
|
||||
datasets.push({
|
||||
order: 0,
|
||||
data: [],
|
||||
});
|
||||
datasets.push({
|
||||
order: 999,
|
||||
data: [],
|
||||
xAxisID: "xAxisCompare",
|
||||
});
|
||||
|
||||
datasets.push(
|
||||
...this._processDataSet(
|
||||
energyData.statsCompare,
|
||||
solarSources,
|
||||
solarColor,
|
||||
true
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
if (forecasts) {
|
||||
datasets.push(
|
||||
...this._processForecast(
|
||||
forecasts,
|
||||
solarSources,
|
||||
computedStyles.getPropertyValue("--primary-text-color"),
|
||||
energyData.start,
|
||||
energyData.end
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
this._start = energyData.start;
|
||||
this._end = energyData.end || endOfToday();
|
||||
|
||||
this._compareStart = energyData.startCompare;
|
||||
this._compareEnd = energyData.endCompare;
|
||||
|
||||
this._chartData = {
|
||||
datasets,
|
||||
};
|
||||
}
|
||||
|
||||
private _processDataSet(
|
||||
statistics: Statistics,
|
||||
solarSources: SolarSourceTypeEnergyPreference[],
|
||||
solarColor: string,
|
||||
compare = false
|
||||
) {
|
||||
const data: ChartDataset<"bar", ScatterDataPoint[]>[] = [];
|
||||
|
||||
solarSources.forEach((source, idx) => {
|
||||
const data: ChartDataset<"bar" | "line">[] = [];
|
||||
const entity = this.hass.states[source.stat_energy_from];
|
||||
|
||||
const modifiedColor =
|
||||
@ -276,8 +364,8 @@ export class HuiEnergySolarGraphCard
|
||||
const solarProductionData: ScatterDataPoint[] = [];
|
||||
|
||||
// Process solar production data.
|
||||
if (source.stat_energy_from in energyData.stats) {
|
||||
const stats = energyData.stats[source.stat_energy_from];
|
||||
if (source.stat_energy_from in statistics) {
|
||||
const stats = statistics[source.stat_energy_from];
|
||||
|
||||
for (const point of stats) {
|
||||
if (point.sum === null) {
|
||||
@ -301,23 +389,41 @@ export class HuiEnergySolarGraphCard
|
||||
}
|
||||
}
|
||||
|
||||
if (solarProductionData.length) {
|
||||
data.push({
|
||||
label: this.hass.localize(
|
||||
"ui.panel.lovelace.cards.energy.energy_solar_graph.production",
|
||||
{
|
||||
name: entity ? computeStateName(entity) : source.stat_energy_from,
|
||||
}
|
||||
),
|
||||
borderColor,
|
||||
backgroundColor: borderColor + "7F",
|
||||
data: solarProductionData,
|
||||
stack: "solar",
|
||||
});
|
||||
}
|
||||
data.push({
|
||||
label: this.hass.localize(
|
||||
"ui.panel.lovelace.cards.energy.energy_solar_graph.production",
|
||||
{
|
||||
name: entity ? computeStateName(entity) : source.stat_energy_from,
|
||||
}
|
||||
),
|
||||
borderColor: compare ? borderColor + "7F" : borderColor,
|
||||
backgroundColor: compare ? borderColor + "32" : borderColor + "7F",
|
||||
data: solarProductionData,
|
||||
order: 1,
|
||||
stack: "solar",
|
||||
xAxisID: compare ? "xAxisCompare" : undefined,
|
||||
});
|
||||
});
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
private _processForecast(
|
||||
forecasts: EnergySolarForecasts,
|
||||
solarSources: SolarSourceTypeEnergyPreference[],
|
||||
borderColor: string,
|
||||
start: Date,
|
||||
end?: Date
|
||||
) {
|
||||
const data: ChartDataset<"line">[] = [];
|
||||
|
||||
const dayDifference = differenceInDays(end || new Date(), start);
|
||||
|
||||
// Process solar forecast data.
|
||||
solarSources.forEach((source) => {
|
||||
if (source.config_entry_solar_forecast) {
|
||||
const entity = this.hass.states[source.stat_energy_from];
|
||||
|
||||
// Process solar forecast data.
|
||||
if (forecasts && source.config_entry_solar_forecast) {
|
||||
const forecastsData: Record<string, number> | undefined = {};
|
||||
source.config_entry_solar_forecast.forEach((configEntryId) => {
|
||||
if (!forecasts![configEntryId]) {
|
||||
@ -326,10 +432,7 @@ export class HuiEnergySolarGraphCard
|
||||
Object.entries(forecasts![configEntryId].wh_hours).forEach(
|
||||
([date, value]) => {
|
||||
const dateObj = new Date(date);
|
||||
if (
|
||||
dateObj < energyData.start ||
|
||||
(energyData.end && dateObj > energyData.end)
|
||||
) {
|
||||
if (dateObj < start || (end && dateObj > end)) {
|
||||
return;
|
||||
}
|
||||
if (dayDifference > 35) {
|
||||
@ -372,9 +475,7 @@ export class HuiEnergySolarGraphCard
|
||||
),
|
||||
fill: false,
|
||||
stepped: false,
|
||||
borderColor: computedStyles.getPropertyValue(
|
||||
"--primary-text-color"
|
||||
),
|
||||
borderColor,
|
||||
borderDash: [7, 5],
|
||||
pointRadius: 0,
|
||||
data: solarForecastData,
|
||||
@ -382,17 +483,9 @@ export class HuiEnergySolarGraphCard
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Concat two arrays
|
||||
Array.prototype.push.apply(datasets, data);
|
||||
});
|
||||
|
||||
this._start = energyData.start;
|
||||
this._end = energyData.end || endOfToday();
|
||||
|
||||
this._chartData = {
|
||||
datasets,
|
||||
};
|
||||
return data;
|
||||
}
|
||||
|
||||
static get styles(): CSSResultGroup {
|
||||
|
@ -1,7 +1,13 @@
|
||||
import { ChartData, ChartDataset, ChartOptions } from "chart.js";
|
||||
import {
|
||||
ChartData,
|
||||
ChartDataset,
|
||||
ChartOptions,
|
||||
ScatterDataPoint,
|
||||
} from "chart.js";
|
||||
import {
|
||||
addHours,
|
||||
differenceInDays,
|
||||
differenceInHours,
|
||||
endOfToday,
|
||||
isToday,
|
||||
startOfToday,
|
||||
@ -18,6 +24,7 @@ import {
|
||||
rgb2lab,
|
||||
} from "../../../../common/color/convert-color";
|
||||
import { labBrighten, labDarken } from "../../../../common/color/lab";
|
||||
import { formatDateShort } from "../../../../common/datetime/format_date";
|
||||
import { formatTime } from "../../../../common/datetime/format_time";
|
||||
import { computeStateName } from "../../../../common/entity/compute_state_name";
|
||||
import {
|
||||
@ -27,6 +34,7 @@ import {
|
||||
import "../../../../components/chart/ha-chart-base";
|
||||
import "../../../../components/ha-card";
|
||||
import { EnergyData, getEnergyDataCollection } from "../../../../data/energy";
|
||||
import { Statistics } from "../../../../data/history";
|
||||
import { FrontendLocaleData } from "../../../../data/translation";
|
||||
import { SubscribeMixin } from "../../../../mixins/subscribe-mixin";
|
||||
import { HomeAssistant } from "../../../../types";
|
||||
@ -50,6 +58,10 @@ export class HuiEnergyUsageGraphCard
|
||||
|
||||
@state() private _end = endOfToday();
|
||||
|
||||
@state() private _compareStart?: Date;
|
||||
|
||||
@state() private _compareEnd?: Date;
|
||||
|
||||
protected hassSubscribeRequiredHostProps = ["_config"];
|
||||
|
||||
public hassSubscribe(): UnsubscribeFunc[] {
|
||||
@ -88,7 +100,9 @@ export class HuiEnergyUsageGraphCard
|
||||
.options=${this._createOptions(
|
||||
this._start,
|
||||
this._end,
|
||||
this.hass.locale
|
||||
this.hass.locale,
|
||||
this._compareStart,
|
||||
this._compareEnd
|
||||
)}
|
||||
chart-type="bar"
|
||||
></ha-chart-base>
|
||||
@ -107,9 +121,27 @@ export class HuiEnergyUsageGraphCard
|
||||
}
|
||||
|
||||
private _createOptions = memoizeOne(
|
||||
(start: Date, end: Date, locale: FrontendLocaleData): ChartOptions => {
|
||||
(
|
||||
start: Date,
|
||||
end: Date,
|
||||
locale: FrontendLocaleData,
|
||||
compareStart?: Date,
|
||||
compareEnd?: Date
|
||||
): ChartOptions => {
|
||||
const dayDifference = differenceInDays(end, start);
|
||||
return {
|
||||
const compare = compareStart !== undefined && compareEnd !== undefined;
|
||||
if (compare) {
|
||||
const difference = differenceInHours(end, start);
|
||||
const differenceCompare = differenceInHours(compareEnd!, compareStart!);
|
||||
// If the compare period doesn't match the main period, adjust them to match
|
||||
if (differenceCompare > difference) {
|
||||
end = addHours(end, differenceCompare - difference);
|
||||
} else if (difference > differenceCompare) {
|
||||
compareEnd = addHours(compareEnd!, difference - differenceCompare);
|
||||
}
|
||||
}
|
||||
|
||||
const options: ChartOptions = {
|
||||
parsing: false,
|
||||
animation: false,
|
||||
scales: {
|
||||
@ -152,7 +184,6 @@ export class HuiEnergyUsageGraphCard
|
||||
? "day"
|
||||
: "hour",
|
||||
},
|
||||
offset: true,
|
||||
},
|
||||
y: {
|
||||
stacked: true,
|
||||
@ -179,7 +210,9 @@ export class HuiEnergyUsageGraphCard
|
||||
return datasets[0].label;
|
||||
}
|
||||
const date = new Date(datasets[0].parsed.x);
|
||||
return `${formatTime(date, locale)} – ${formatTime(
|
||||
return `${
|
||||
compare ? `${formatDateShort(date, locale)}: ` : ""
|
||||
}${formatTime(date, locale)} – ${formatTime(
|
||||
addHours(date, 1),
|
||||
locale
|
||||
)}`;
|
||||
@ -240,13 +273,22 @@ export class HuiEnergyUsageGraphCard
|
||||
// @ts-expect-error
|
||||
locale: numberFormatToLocale(locale),
|
||||
};
|
||||
if (compare) {
|
||||
options.scales!.xAxisCompare = {
|
||||
...(options.scales!.x as Record<string, any>),
|
||||
suggestedMin: compareStart!.getTime(),
|
||||
suggestedMax: compareEnd!.getTime(),
|
||||
display: false,
|
||||
};
|
||||
}
|
||||
return options;
|
||||
}
|
||||
);
|
||||
|
||||
private async _getStatistics(energyData: EnergyData): Promise<void> {
|
||||
const datasets: ChartDataset<"bar">[] = [];
|
||||
const datasets: ChartDataset<"bar", ScatterDataPoint[]>[] = [];
|
||||
|
||||
const statistics: {
|
||||
const statIds: {
|
||||
to_grid?: string[];
|
||||
from_grid?: string[];
|
||||
solar?: string[];
|
||||
@ -256,21 +298,21 @@ export class HuiEnergyUsageGraphCard
|
||||
|
||||
for (const source of energyData.prefs.energy_sources) {
|
||||
if (source.type === "solar") {
|
||||
if (statistics.solar) {
|
||||
statistics.solar.push(source.stat_energy_from);
|
||||
if (statIds.solar) {
|
||||
statIds.solar.push(source.stat_energy_from);
|
||||
} else {
|
||||
statistics.solar = [source.stat_energy_from];
|
||||
statIds.solar = [source.stat_energy_from];
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
if (source.type === "battery") {
|
||||
if (statistics.to_battery) {
|
||||
statistics.to_battery.push(source.stat_energy_to);
|
||||
statistics.from_battery!.push(source.stat_energy_from);
|
||||
if (statIds.to_battery) {
|
||||
statIds.to_battery.push(source.stat_energy_to);
|
||||
statIds.from_battery!.push(source.stat_energy_from);
|
||||
} else {
|
||||
statistics.to_battery = [source.stat_energy_to];
|
||||
statistics.from_battery = [source.stat_energy_from];
|
||||
statIds.to_battery = [source.stat_energy_to];
|
||||
statIds.from_battery = [source.stat_energy_from];
|
||||
}
|
||||
continue;
|
||||
}
|
||||
@ -281,41 +323,21 @@ export class HuiEnergyUsageGraphCard
|
||||
|
||||
// grid source
|
||||
for (const flowFrom of source.flow_from) {
|
||||
if (statistics.from_grid) {
|
||||
statistics.from_grid.push(flowFrom.stat_energy_from);
|
||||
if (statIds.from_grid) {
|
||||
statIds.from_grid.push(flowFrom.stat_energy_from);
|
||||
} else {
|
||||
statistics.from_grid = [flowFrom.stat_energy_from];
|
||||
statIds.from_grid = [flowFrom.stat_energy_from];
|
||||
}
|
||||
}
|
||||
for (const flowTo of source.flow_to) {
|
||||
if (statistics.to_grid) {
|
||||
statistics.to_grid.push(flowTo.stat_energy_to);
|
||||
if (statIds.to_grid) {
|
||||
statIds.to_grid.push(flowTo.stat_energy_to);
|
||||
} else {
|
||||
statistics.to_grid = [flowTo.stat_energy_to];
|
||||
statIds.to_grid = [flowTo.stat_energy_to];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
this._start = energyData.start;
|
||||
this._end = energyData.end || endOfToday();
|
||||
|
||||
const combinedData: {
|
||||
to_grid?: { [statId: string]: { [start: string]: number } };
|
||||
to_battery?: { [statId: string]: { [start: string]: number } };
|
||||
from_grid?: { [statId: string]: { [start: string]: number } };
|
||||
used_grid?: { [statId: string]: { [start: string]: number } };
|
||||
used_solar?: { [statId: string]: { [start: string]: number } };
|
||||
used_battery?: { [statId: string]: { [start: string]: number } };
|
||||
} = {};
|
||||
|
||||
const summedData: {
|
||||
to_grid?: { [start: string]: number };
|
||||
from_grid?: { [start: string]: number };
|
||||
to_battery?: { [start: string]: number };
|
||||
from_battery?: { [start: string]: number };
|
||||
solar?: { [start: string]: number };
|
||||
} = {};
|
||||
|
||||
const computedStyles = getComputedStyle(this);
|
||||
const colors = {
|
||||
to_grid: computedStyles
|
||||
@ -349,7 +371,88 @@ export class HuiEnergyUsageGraphCard
|
||||
),
|
||||
};
|
||||
|
||||
Object.entries(statistics).forEach(([key, statIds]) => {
|
||||
this._start = energyData.start;
|
||||
this._end = energyData.end || endOfToday();
|
||||
|
||||
this._compareStart = energyData.startCompare;
|
||||
this._compareEnd = energyData.endCompare;
|
||||
|
||||
datasets.push(
|
||||
...this._processDataSet(energyData.stats, statIds, colors, labels, false)
|
||||
);
|
||||
|
||||
if (energyData.statsCompare) {
|
||||
// Add empty dataset to align the bars
|
||||
datasets.push({
|
||||
order: 0,
|
||||
data: [],
|
||||
});
|
||||
datasets.push({
|
||||
order: 999,
|
||||
data: [],
|
||||
xAxisID: "xAxisCompare",
|
||||
});
|
||||
|
||||
datasets.push(
|
||||
...this._processDataSet(
|
||||
energyData.statsCompare,
|
||||
statIds,
|
||||
colors,
|
||||
labels,
|
||||
true
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
this._chartData = {
|
||||
datasets,
|
||||
};
|
||||
}
|
||||
|
||||
private _processDataSet(
|
||||
statistics: Statistics,
|
||||
statIdsByCat: {
|
||||
to_grid?: string[] | undefined;
|
||||
from_grid?: string[] | undefined;
|
||||
solar?: string[] | undefined;
|
||||
to_battery?: string[] | undefined;
|
||||
from_battery?: string[] | undefined;
|
||||
},
|
||||
colors: {
|
||||
to_grid: string;
|
||||
to_battery: string;
|
||||
from_grid: string;
|
||||
used_grid: string;
|
||||
used_solar: string;
|
||||
used_battery: string;
|
||||
},
|
||||
labels: {
|
||||
used_grid: string;
|
||||
used_solar: string;
|
||||
used_battery: string;
|
||||
},
|
||||
compare = false
|
||||
) {
|
||||
const data: ChartDataset<"bar", ScatterDataPoint[]>[] = [];
|
||||
|
||||
const combinedData: {
|
||||
to_grid?: { [statId: string]: { [start: string]: number } };
|
||||
to_battery?: { [statId: string]: { [start: string]: number } };
|
||||
from_grid?: { [statId: string]: { [start: string]: number } };
|
||||
used_grid?: { [statId: string]: { [start: string]: number } };
|
||||
used_solar?: { [statId: string]: { [start: string]: number } };
|
||||
used_battery?: { [statId: string]: { [start: string]: number } };
|
||||
} = {};
|
||||
|
||||
const summedData: {
|
||||
to_grid?: { [start: string]: number };
|
||||
from_grid?: { [start: string]: number };
|
||||
to_battery?: { [start: string]: number };
|
||||
from_battery?: { [start: string]: number };
|
||||
solar?: { [start: string]: number };
|
||||
} = {};
|
||||
|
||||
Object.entries(statIdsByCat).forEach(([key, statIds]) => {
|
||||
const sum = [
|
||||
"solar",
|
||||
"to_grid",
|
||||
@ -361,7 +464,7 @@ export class HuiEnergyUsageGraphCard
|
||||
const totalStats: { [start: string]: number } = {};
|
||||
const sets: { [statId: string]: { [start: string]: number } } = {};
|
||||
statIds!.forEach((id) => {
|
||||
const stats = energyData.stats[id];
|
||||
const stats = statistics[id];
|
||||
if (!stats) {
|
||||
return;
|
||||
}
|
||||
@ -477,7 +580,6 @@ export class HuiEnergyUsageGraphCard
|
||||
|
||||
Object.entries(combinedData).forEach(([type, sources]) => {
|
||||
Object.entries(sources).forEach(([statId, source], idx) => {
|
||||
const data: ChartDataset<"bar">[] = [];
|
||||
const entity = this.hass.states[statId];
|
||||
|
||||
const modifiedColor =
|
||||
@ -490,6 +592,20 @@ export class HuiEnergyUsageGraphCard
|
||||
? rgb2hex(lab2rgb(modifiedColor))
|
||||
: colors[type];
|
||||
|
||||
const points: ScatterDataPoint[] = [];
|
||||
// Process chart data.
|
||||
for (const key of uniqueKeys) {
|
||||
const value = source[key] || 0;
|
||||
const date = new Date(key);
|
||||
points.push({
|
||||
x: date.getTime(),
|
||||
y:
|
||||
value && ["to_grid", "to_battery"].includes(type)
|
||||
? -1 * value
|
||||
: value,
|
||||
});
|
||||
}
|
||||
|
||||
data.push({
|
||||
label:
|
||||
type in labels
|
||||
@ -499,38 +615,19 @@ export class HuiEnergyUsageGraphCard
|
||||
: statId,
|
||||
order:
|
||||
type === "used_solar"
|
||||
? 0
|
||||
? 1
|
||||
: type === "to_battery"
|
||||
? Object.keys(combinedData).length
|
||||
: idx + 1,
|
||||
borderColor,
|
||||
backgroundColor: borderColor + "7F",
|
||||
: idx + 2,
|
||||
borderColor: compare ? borderColor + "7F" : borderColor,
|
||||
backgroundColor: compare ? borderColor + "32" : borderColor + "7F",
|
||||
stack: "stack",
|
||||
data: [],
|
||||
data: points,
|
||||
xAxisID: compare ? "xAxisCompare" : undefined,
|
||||
});
|
||||
|
||||
// Process chart data.
|
||||
for (const key of uniqueKeys) {
|
||||
const value = source[key] || 0;
|
||||
const date = new Date(key);
|
||||
// @ts-expect-error
|
||||
data[0].data.push({
|
||||
x: date.getTime(),
|
||||
y:
|
||||
value && ["to_grid", "to_battery"].includes(type)
|
||||
? -1 * value
|
||||
: value,
|
||||
});
|
||||
}
|
||||
|
||||
// Concat two arrays
|
||||
Array.prototype.push.apply(datasets, data);
|
||||
});
|
||||
});
|
||||
|
||||
this._chartData = {
|
||||
datasets,
|
||||
};
|
||||
return data;
|
||||
}
|
||||
|
||||
static get styles(): CSSResultGroup {
|
||||
|
@ -97,6 +97,10 @@ export interface ButtonCardConfig extends LovelaceCardConfig {
|
||||
show_state?: boolean;
|
||||
}
|
||||
|
||||
export interface EnergyCardBaseConfig extends LovelaceCardConfig {
|
||||
collection_key?: string;
|
||||
}
|
||||
|
||||
export interface EnergySummaryCardConfig extends LovelaceCardConfig {
|
||||
type: "energy-summary";
|
||||
title?: string;
|
||||
|
@ -46,6 +46,8 @@ export class HuiEnergyPeriodSelector extends SubscribeMixin(LitElement) {
|
||||
|
||||
@state() private _period?: "day" | "week" | "month" | "year";
|
||||
|
||||
@state() private _compare? = false;
|
||||
|
||||
public connectedCallback() {
|
||||
super.connectedCallback();
|
||||
toggleAttribute(this, "narrow", this.offsetWidth < 600);
|
||||
@ -134,6 +136,14 @@ export class HuiEnergyPeriodSelector extends SubscribeMixin(LitElement) {
|
||||
dense
|
||||
@value-changed=${this._handleView}
|
||||
></ha-button-toggle-group>
|
||||
<mwc-button
|
||||
class="compare ${this._compare ? "active" : ""}"
|
||||
@click=${this._toggleCompare}
|
||||
dense
|
||||
outlined
|
||||
>
|
||||
Compare data
|
||||
</mwc-button>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
@ -216,6 +226,7 @@ export class HuiEnergyPeriodSelector extends SubscribeMixin(LitElement) {
|
||||
}
|
||||
|
||||
private _updateDates(energyData: EnergyData): void {
|
||||
this._compare = energyData.startCompare !== undefined;
|
||||
this._startDate = energyData.start;
|
||||
this._endDate = energyData.end || endOfToday();
|
||||
const dayDifference = differenceInDays(this._endDate, this._startDate);
|
||||
@ -231,6 +242,15 @@ export class HuiEnergyPeriodSelector extends SubscribeMixin(LitElement) {
|
||||
: undefined;
|
||||
}
|
||||
|
||||
private _toggleCompare() {
|
||||
this._compare = !this._compare;
|
||||
const energyCollection = getEnergyDataCollection(this.hass, {
|
||||
key: "energy_dashboard",
|
||||
});
|
||||
energyCollection.setCompare(this._compare);
|
||||
energyCollection.refresh();
|
||||
}
|
||||
|
||||
static get styles(): CSSResultGroup {
|
||||
return css`
|
||||
.row {
|
||||
@ -251,12 +271,37 @@ export class HuiEnergyPeriodSelector extends SubscribeMixin(LitElement) {
|
||||
}
|
||||
.period {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
justify-content: flex-end;
|
||||
align-items: flex-end;
|
||||
}
|
||||
mwc-button.active::before {
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
position: absolute;
|
||||
background-color: currentColor;
|
||||
opacity: 0;
|
||||
pointer-events: none;
|
||||
content: "";
|
||||
transition: opacity 15ms linear, background-color 15ms linear;
|
||||
opacity: var(--mdc-icon-button-ripple-opacity, 0.12);
|
||||
}
|
||||
.compare {
|
||||
position: relative;
|
||||
margin-left: 8px;
|
||||
width: max-content;
|
||||
}
|
||||
:host([narrow]) .compare {
|
||||
margin-left: auto;
|
||||
margin-top: 8px;
|
||||
}
|
||||
:host {
|
||||
--mdc-button-outline-color: currentColor;
|
||||
--primary-color: currentColor;
|
||||
--mdc-theme-primary: currentColor;
|
||||
--mdc-theme-on-primary: currentColor;
|
||||
--mdc-button-disabled-outline-color: var(--disabled-text-color);
|
||||
--mdc-button-disabled-ink-color: var(--disabled-text-color);
|
||||
--mdc-icon-button-ripple-opacity: 0.2;
|
||||
|
@ -35,6 +35,7 @@ const LAZY_LOAD_TYPES = {
|
||||
calendar: () => import("../cards/hui-calendar-card"),
|
||||
conditional: () => import("../cards/hui-conditional-card"),
|
||||
"empty-state": () => import("../cards/hui-empty-state-card"),
|
||||
"energy-compare": () => import("../cards/energy/hui-energy-compare-card"),
|
||||
"energy-carbon-consumed-gauge": () =>
|
||||
import("../cards/energy/hui-energy-carbon-consumed-gauge-card"),
|
||||
"energy-date-selection": () =>
|
||||
|
Loading…
x
Reference in New Issue
Block a user