Support displaying relative change in statistics graph cards (#13837)

* Support displaying relative change in statistics graph cards

* Address review comments

* Add option to display state

* Drop absolute sum option

* Drop period if invalid

* prevent fetching stats twice

* Change stat_types to supported ones for statistics

Co-authored-by: Bram Kragten <mail@bramkragten.nl>
This commit is contained in:
Erik Montnemery 2022-09-28 14:51:41 +02:00 committed by GitHub
parent f9d119d33d
commit 71c43058ea
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 54 additions and 22 deletions

View File

@ -30,6 +30,15 @@ import {
import type { HomeAssistant } from "../../types"; import type { HomeAssistant } from "../../types";
import "./ha-chart-base"; import "./ha-chart-base";
export type ExtendedStatisticType = StatisticType | "state";
export const statTypeMap: Record<ExtendedStatisticType, StatisticType> = {
mean: "mean",
min: "min",
max: "max",
sum: "sum",
state: "sum",
};
@customElement("statistics-chart") @customElement("statistics-chart")
class StatisticsChart extends LitElement { class StatisticsChart extends LitElement {
@property({ attribute: false }) public hass!: HomeAssistant; @property({ attribute: false }) public hass!: HomeAssistant;
@ -42,7 +51,7 @@ class StatisticsChart extends LitElement {
@property({ attribute: false }) public endTime?: Date; @property({ attribute: false }) public endTime?: Date;
@property({ type: Array }) public statTypes: Array<StatisticType> = [ @property({ type: Array }) public statTypes: Array<ExtendedStatisticType> = [
"sum", "sum",
"min", "min",
"mean", "mean",
@ -307,7 +316,7 @@ class StatisticsChart extends LitElement {
: this.statTypes; : this.statTypes;
sortedTypes.forEach((type) => { sortedTypes.forEach((type) => {
if (statisticsHaveType(stats, type)) { if (statisticsHaveType(stats, statTypeMap[type])) {
const band = drawBands && (type === "min" || type === "max"); const band = drawBands && (type === "min" || type === "max");
statTypes.push(type); statTypes.push(type);
statDataSets.push({ statDataSets.push({
@ -335,7 +344,6 @@ class StatisticsChart extends LitElement {
let prevDate: Date | null = null; let prevDate: Date | null = null;
// Process chart data. // Process chart data.
let initVal: number | null = null;
let prevSum: number | null = null; let prevSum: number | null = null;
stats.forEach((stat) => { stats.forEach((stat) => {
const date = new Date(stat.start); const date = new Date(stat.start);
@ -347,11 +355,11 @@ class StatisticsChart extends LitElement {
statTypes.forEach((type) => { statTypes.forEach((type) => {
let val: number | null; let val: number | null;
if (type === "sum") { if (type === "sum") {
if (initVal === null) { if (prevSum === null) {
initVal = val = stat.state || 0; val = 0;
prevSum = stat.sum; prevSum = stat.sum;
} else { } else {
val = initVal + ((stat.sum || 0) - prevSum!); val = (stat.sum || 0) - prevSum;
} }
} else { } else {
val = stat[type]; val = stat[type];

View File

@ -1,7 +1,7 @@
import { computeStateName } from "../common/entity/compute_state_name"; import { computeStateName } from "../common/entity/compute_state_name";
import { HomeAssistant } from "../types"; import { HomeAssistant } from "../types";
export type StatisticType = "sum" | "min" | "max" | "mean"; export type StatisticType = "state" | "sum" | "min" | "max" | "mean";
export interface Statistics { export interface Statistics {
[statisticId: string]: StatisticValue[]; [statisticId: string]: StatisticValue[];

View File

@ -38,8 +38,6 @@ export class HuiStatisticsGraphCard extends LitElement implements LovelaceCard {
private _names: Record<string, string> = {}; private _names: Record<string, string> = {};
private _fetching = false;
private _interval?: number; private _interval?: number;
public disconnectedCallback() { public disconnectedCallback() {
@ -92,7 +90,10 @@ export class HuiStatisticsGraphCard extends LitElement implements LovelaceCard {
if (typeof config.stat_types === "string") { if (typeof config.stat_types === "string") {
this._config = { ...config, stat_types: [config.stat_types] }; this._config = { ...config, stat_types: [config.stat_types] };
} else if (!config.stat_types) { } else if (!config.stat_types) {
this._config = { ...config, stat_types: ["sum", "min", "max", "mean"] }; this._config = {
...config,
stat_types: ["state", "sum", "min", "max", "mean"],
};
} else { } else {
this._config = config; this._config = config;
} }
@ -156,15 +157,11 @@ export class HuiStatisticsGraphCard extends LitElement implements LovelaceCard {
} }
private async _getStatistics(): Promise<void> { private async _getStatistics(): Promise<void> {
if (this._fetching) {
return;
}
const startDate = new Date(); const startDate = new Date();
startDate.setTime( startDate.setTime(
startDate.getTime() - startDate.getTime() -
1000 * 60 * 60 * (24 * (this._config!.days_to_show || 30) + 1) 1000 * 60 * 60 * (24 * (this._config!.days_to_show || 30) + 1)
); );
this._fetching = true;
try { try {
this._statistics = await fetchStatistics( this._statistics = await fetchStatistics(
this.hass!, this.hass!,
@ -173,8 +170,8 @@ export class HuiStatisticsGraphCard extends LitElement implements LovelaceCard {
this._entities, this._entities,
this._config!.period this._config!.period
); );
} finally { } catch (err) {
this._fetching = false; this._statistics = undefined;
} }
} }

View File

@ -36,8 +36,11 @@ import {
statisticsMetaHasType, statisticsMetaHasType,
} from "../../../../data/recorder"; } from "../../../../data/recorder";
import { deepEqual } from "../../../../common/util/deep-equal"; import { deepEqual } from "../../../../common/util/deep-equal";
import { statTypeMap } from "../../../../components/chart/statistics-chart";
import { ensureArray } from "../../../../common/ensure-array";
const statTypeStruct = union([ const statTypeStruct = union([
literal("state"),
literal("sum"), literal("sum"),
literal("min"), literal("min"),
literal("max"), literal("max"),
@ -64,7 +67,7 @@ const cardConfigStruct = assign(
); );
const periods = ["5minute", "hour", "day", "month"] as const; const periods = ["5minute", "hour", "day", "month"] as const;
const stat_types = ["mean", "min", "max", "sum"] as const; const stat_types = ["mean", "min", "max", "sum", "state"] as const;
@customElement("hui-statistics-graph-card-editor") @customElement("hui-statistics-graph-card-editor")
export class HuiStatisticsGraphCardEditor export class HuiStatisticsGraphCardEditor
@ -156,7 +159,7 @@ export class HuiStatisticsGraphCardEditor
disabled: disabled:
!metaDatas || !metaDatas ||
!metaDatas?.every((metaData) => !metaDatas?.every((metaData) =>
statisticsMetaHasType(metaData, stat_type) statisticsMetaHasType(metaData, statTypeMap[stat_type])
), ),
})), })),
}, },
@ -190,7 +193,11 @@ export class HuiStatisticsGraphCardEditor
? Array.isArray(this._config!.stat_types) ? Array.isArray(this._config!.stat_types)
? this._config!.stat_types ? this._config!.stat_types
: [this._config!.stat_types] : [this._config!.stat_types]
: stat_types; : stat_types.filter((stat_type) =>
this._metaDatas?.every((metaData) =>
statisticsMetaHasType(metaData, statTypeMap[stat_type])
)
);
const data = { const data = {
chart_type: "line", chart_type: "line",
period: "hour", period: "hour",
@ -230,9 +237,27 @@ export class HuiStatisticsGraphCardEditor
fireEvent(this, "config-changed", { config: ev.detail.value }); fireEvent(this, "config-changed", { config: ev.detail.value });
} }
private _entitiesChanged(ev: CustomEvent): void { private async _entitiesChanged(ev: CustomEvent): Promise<void> {
const config = { ...this._config!, entities: ev.detail.value };
if (
config.entities?.some((statistic_id) => statistic_id.includes(":")) &&
config.period === "5minute"
) {
delete config.period;
}
if (config.stat_types && config.entities.length) {
const metadata = await getStatisticMetadata(this.hass!, config.entities);
config.stat_types = ensureArray(config.stat_types).filter((stat_type) =>
metadata.every((metaData) =>
statisticsMetaHasType(metaData, statTypeMap[stat_type])
)
);
if (!config.stat_types.length) {
delete config.stat_types;
}
}
fireEvent(this, "config-changed", { fireEvent(this, "config-changed", {
config: { ...this._config!, entities: ev.detail.value }, config,
}); });
} }

View File

@ -517,6 +517,7 @@
"min": "min", "min": "min",
"max": "max", "max": "max",
"mean": "mean", "mean": "mean",
"state": "state",
"sum": "sum" "sum": "sum"
} }
}, },
@ -3969,7 +3970,8 @@
"mean": "Mean", "mean": "Mean",
"min": "Min", "min": "Min",
"max": "Max", "max": "Max",
"sum": "Sum" "state": "State",
"sum": "Sum (change during period)"
}, },
"chart_type": "Chart type", "chart_type": "Chart type",
"periods": { "periods": {