Convert units in statistics graph card (#13970)

Co-authored-by: Bram Kragten <mail@bramkragten.nl>
This commit is contained in:
Erik Montnemery 2022-11-11 14:21:53 +01:00 committed by GitHub
parent 1c03dc9b77
commit 1cb44ce857
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 155 additions and 48 deletions

View File

@ -26,6 +26,7 @@ import {
getStatisticMetadata,
Statistics,
statisticsHaveType,
StatisticsMetaData,
StatisticType,
} from "../../data/recorder";
import type { HomeAssistant } from "../../types";
@ -46,6 +47,11 @@ class StatisticsChart extends LitElement {
@property({ attribute: false }) public statisticsData!: Statistics;
@property({ attribute: false }) public metadata?: Record<
string,
StatisticsMetaData
>;
@property() public names: boolean | Record<string, string> = false;
@property() public unit?: string;
@ -76,7 +82,7 @@ class StatisticsChart extends LitElement {
}
public willUpdate(changedProps: PropertyValues) {
if (!this.hasUpdated) {
if (!this.hasUpdated || changedProps.has("unit")) {
this._createOptions();
}
if (changedProps.has("statisticsData") || changedProps.has("statTypes")) {
@ -120,7 +126,7 @@ class StatisticsChart extends LitElement {
`;
}
private _createOptions() {
private _createOptions(unit?: string) {
this._chartOptions = {
parsing: false,
animation: false,
@ -154,8 +160,8 @@ class StatisticsChart extends LitElement {
maxTicksLimit: 7,
},
title: {
display: this.unit,
text: this.unit,
display: unit || this.unit,
text: unit || this.unit,
},
},
},
@ -220,9 +226,9 @@ class StatisticsChart extends LitElement {
return;
}
const statisticsMetaData = await this._getStatisticsMetaData(
Object.keys(this.statisticsData)
);
const statisticsMetaData =
this.metadata ||
(await this._getStatisticsMetaData(Object.keys(this.statisticsData)));
let colorIndex = 0;
const statisticsData = Object.values(this.statisticsData);
@ -263,6 +269,7 @@ class StatisticsChart extends LitElement {
if (unit === undefined) {
unit = getDisplayUnit(this.hass, firstStat.statistic_id, meta);
} else if (
unit !== null &&
unit !== getDisplayUnit(this.hass, firstStat.statistic_id, meta)
) {
// Clear unit if not all statistics have same unit
@ -386,17 +393,8 @@ class StatisticsChart extends LitElement {
Array.prototype.push.apply(totalDataSets, statDataSets);
});
if (unit !== null) {
this._chartOptions = {
...this._chartOptions,
scales: {
...this._chartOptions!.scales,
y: {
...(this._chartOptions!.scales!.y as Record<string, unknown>),
title: { display: unit, text: unit },
},
},
};
if (unit) {
this._createOptions(unit);
}
this._chartData = {

View File

@ -85,7 +85,7 @@ export class HaForm extends LitElement implements HaFormElement {
.selector=${item.selector}
.value=${getValue(this.data, item)}
.label=${this._computeLabel(item, this.data)}
.disabled=${this.disabled}
.disabled=${this.disabled || item.disabled}
.helper=${this._computeHelper(item)}
.required=${item.required || false}
.context=${this._generateContext(item)}
@ -95,7 +95,7 @@ export class HaForm extends LitElement implements HaFormElement {
data: getValue(this.data, item),
label: this._computeLabel(item, this.data),
helper: this._computeHelper(item),
disabled: this.disabled,
disabled: this.disabled || item.disabled,
hass: this.hass,
computeLabel: this.computeLabel,
computeHelper: this.computeHelper,

View File

@ -20,6 +20,7 @@ export interface HaFormBaseSchema {
// This value is applied if no data is submitted for this field
default?: HaFormData;
required?: boolean;
disabled?: boolean;
description?: {
suffix?: string;
// This value will be set initially when form is loaded

View File

@ -8,14 +8,20 @@ import {
} from "lit";
import { customElement, property, state } from "lit/decorators";
import { classMap } from "lit/directives/class-map";
import "../../../components/ha-card";
import "../../../components/chart/statistics-chart";
import "../../../components/ha-card";
import {
fetchStatistics,
getDisplayUnit,
getStatisticMetadata,
Statistics,
StatisticsMetaData,
} from "../../../data/recorder";
import { HomeAssistant } from "../../../types";
import { hasConfigOrEntitiesChanged } from "../common/has-changed";
import { processConfigEntities } from "../common/process-config-entities";
import { LovelaceCard } from "../types";
import { StatisticsGraphCardConfig } from "./types";
import { fetchStatistics, Statistics } from "../../../data/recorder";
@customElement("hui-statistics-graph-card")
export class HuiStatisticsGraphCard extends LitElement implements LovelaceCard {
@ -30,9 +36,13 @@ export class HuiStatisticsGraphCard extends LitElement implements LovelaceCard {
@property({ attribute: false }) public hass?: HomeAssistant;
@state() private _config?: StatisticsGraphCardConfig;
@state() private _statistics?: Statistics;
@state() private _config?: StatisticsGraphCardConfig;
@state() private _metadata?: Record<string, StatisticsMetaData>;
@state() private _unit?: string;
private _entities: string[] = [];
@ -108,7 +118,10 @@ export class HuiStatisticsGraphCard extends LitElement implements LovelaceCard {
public willUpdate(changedProps: PropertyValues) {
super.willUpdate(changedProps);
if (!this._config || !changedProps.has("_config")) {
if (
!this._config ||
(!changedProps.has("_config") && !changedProps.has("_metadata"))
) {
return;
}
@ -117,9 +130,19 @@ export class HuiStatisticsGraphCard extends LitElement implements LovelaceCard {
| undefined;
if (
oldConfig?.entities !== this._config.entities ||
oldConfig?.days_to_show !== this._config.days_to_show ||
oldConfig?.period !== this._config.period
changedProps.has("_config") &&
oldConfig?.entities !== this._config.entities
) {
this._getStatisticsMetaData(this._entities);
}
if (
changedProps.has("_metadata") ||
(changedProps.has("_config") &&
(oldConfig?.entities !== this._config.entities ||
oldConfig?.days_to_show !== this._config.days_to_show ||
oldConfig?.period !== this._config.period ||
oldConfig?.unit !== this._config.unit))
) {
this._getStatistics();
// statistics are created every hour
@ -147,9 +170,11 @@ export class HuiStatisticsGraphCard extends LitElement implements LovelaceCard {
.hass=${this.hass}
.isLoadingData=${!this._statistics}
.statisticsData=${this._statistics}
.metadata=${this._metadata}
.chartType=${this._config.chart_type || "line"}
.statTypes=${this._config.stat_types!}
.names=${this._names}
.unit=${this._unit}
></statistics-chart>
</div>
</ha-card>
@ -160,6 +185,18 @@ export class HuiStatisticsGraphCard extends LitElement implements LovelaceCard {
return (this._config?.period === "5minute" ? 5 : 60) * 1000 * 60;
}
private async _getStatisticsMetaData(statisticIds: string[] | undefined) {
const statsMetadataArray = await getStatisticMetadata(
this.hass!,
statisticIds
);
const statisticsMetaData = {};
statsMetadataArray.forEach((x) => {
statisticsMetaData[x.statistic_id] = x;
});
this._metadata = statisticsMetaData;
}
private async _getStatistics(): Promise<void> {
const startDate = new Date();
startDate.setTime(
@ -167,12 +204,34 @@ export class HuiStatisticsGraphCard extends LitElement implements LovelaceCard {
1000 * 60 * 60 * (24 * (this._config!.days_to_show || 30) + 1)
);
try {
let unitClass;
if (this._config!.unit && this._metadata) {
const metadata = Object.values(this._metadata).find(
(metaData) =>
getDisplayUnit(this.hass!, metaData?.statistic_id, metaData) ===
this._config!.unit
);
if (metadata) {
unitClass = metadata.unit_class;
this._unit = this._config!.unit;
}
}
if (!unitClass && this._metadata) {
const metadata = this._metadata[this._entities[0]];
unitClass = metadata?.unit_class;
this._unit = unitClass
? getDisplayUnit(this.hass!, metadata.statistic_id, metadata) ||
undefined
: undefined;
}
const unitconfig = unitClass ? { [unitClass]: this._unit } : undefined;
this._statistics = await fetchStatistics(
this.hass!,
startDate,
undefined,
this._entities,
this._config!.period
this._config!.period,
unitconfig
);
} catch (err) {
this._statistics = undefined;

View File

@ -304,6 +304,7 @@ export interface HistoryGraphCardConfig extends LovelaceCardConfig {
export interface StatisticsGraphCardConfig extends LovelaceCardConfig {
title?: string;
entities: Array<EntityConfig | string>;
unit?: string;
days_to_show?: number;
period?: "5minute" | "hour" | "day" | "month";
stat_types?: StatisticType | StatisticType[];

View File

@ -1,4 +1,3 @@
import "../../../../components/ha-form/ha-form";
import {
css,
CSSResultGroup,
@ -21,24 +20,26 @@ import {
union,
} from "superstruct";
import { fireEvent } from "../../../../common/dom/fire_event";
import { ensureArray } from "../../../../common/ensure-array";
import type { LocalizeFunc } from "../../../../common/translations/localize";
import { deepEqual } from "../../../../common/util/deep-equal";
import { statTypeMap } from "../../../../components/chart/statistics-chart";
import "../../../../components/entity/ha-statistics-picker";
import type { SchemaUnion } from "../../../../components/ha-form/types";
import "../../../../components/ha-form/ha-form";
import type { HaFormSchema } from "../../../../components/ha-form/types";
import {
getDisplayUnit,
getStatisticMetadata,
isExternalStatistic,
StatisticsMetaData,
statisticsMetaHasType,
} from "../../../../data/recorder";
import type { HomeAssistant } from "../../../../types";
import type { StatisticsGraphCardConfig } from "../../cards/types";
import { processConfigEntities } from "../../common/process-config-entities";
import type { LovelaceCardEditor } from "../../types";
import { baseLovelaceCardConfig } from "../structs/base-card-struct";
import { entitiesConfigStruct } from "../structs/entities-struct";
import {
getStatisticMetadata,
isExternalStatistic,
StatisticsMetaData,
statisticsMetaHasType,
} from "../../../../data/recorder";
import { deepEqual } from "../../../../common/util/deep-equal";
import { statTypeMap } from "../../../../components/chart/statistics-chart";
import { ensureArray } from "../../../../common/ensure-array";
const statTypeStruct = union([
literal("state"),
@ -65,6 +66,7 @@ const cardConfigStruct = assign(
),
chart_type: optional(union([literal("bar"), literal("line")])),
stat_types: optional(union([array(statTypeStruct), statTypeStruct])),
unit: optional(string()),
})
);
@ -114,8 +116,19 @@ export class HuiStatisticsGraphCardEditor
localize: LocalizeFunc,
statisticIds: string[] | undefined,
metaDatas: StatisticsMetaData[] | undefined
) =>
[
) => {
const units = new Set<string>();
metaDatas?.forEach((metaData) => {
const unit = getDisplayUnit(
this.hass!,
metaData.statistic_id,
metaData
);
if (unit) {
units.add(unit);
}
});
const schema: HaFormSchema[] = [
{ name: "title", selector: { text: {} } },
{
name: "",
@ -159,7 +172,7 @@ export class HuiStatisticsGraphCardEditor
),
disabled:
!metaDatas ||
!metaDatas?.every((metaData) =>
!metaDatas.every((metaData) =>
statisticsMetaHasType(metaData, statTypeMap[stat_type])
),
})),
@ -177,7 +190,25 @@ export class HuiStatisticsGraphCardEditor
},
],
},
] as const
];
if (units.size > 1) {
(schema[1] as any).schema.push({
name: "unit",
required: false,
selector: {
select: {
options: Array.from(units).map((unit) => ({
value: unit,
label: unit,
})),
},
},
});
}
return schema;
}
);
protected render(): TemplateResult {
@ -207,6 +238,9 @@ export class HuiStatisticsGraphCardEditor
stat_types: configured_stat_types,
};
const unitClass = this._metaDatas?.[0]?.unit_class;
const statisticsUnit = unitClass
? undefined
: this._metaDatas?.[0]?.statistics_unit_of_measurement;
return html`
<ha-form
@ -224,6 +258,7 @@ export class HuiStatisticsGraphCardEditor
.pickedStatisticLabel=${this.hass!.localize(
"ui.panel.lovelace.editor.card.statistics-graph.picked_statistic"
)}
.includeStatisticsUnitOfMeasurement=${statisticsUnit}
.includeUnitClass=${unitClass}
.ignoreRestrictionsOnFirstStatistic=${true}
.value=${this._configEntities}
@ -248,10 +283,13 @@ export class HuiStatisticsGraphCardEditor
) {
delete config.period;
}
const metadata =
config.stat_types || config.unit
? await getStatisticMetadata(this.hass!, config.entities)
: undefined;
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) =>
metadata!.every((metaData) =>
statisticsMetaHasType(metaData, statTypeMap[stat_type])
)
);
@ -259,18 +297,27 @@ export class HuiStatisticsGraphCardEditor
delete config.stat_types;
}
}
if (
config.unit &&
!metadata!.some(
(metaData) =>
getDisplayUnit(this.hass!, metaData?.statistic_id, metaData) ===
config.unit
)
) {
delete config.unit;
}
fireEvent(this, "config-changed", {
config,
});
}
private _computeLabelCallback = (
schema: SchemaUnion<ReturnType<typeof this._schema>>
) => {
private _computeLabelCallback = (schema) => {
switch (schema.name) {
case "chart_type":
case "stat_types":
case "period":
case "unit":
return this.hass!.localize(
`ui.panel.lovelace.editor.card.statistics-graph.${schema.name}`
);

View File

@ -4052,6 +4052,7 @@
"name": "Statistics Graph",
"description": "The Statistics Graph card allows you to display a graph of the statistics for each of the entities listed.",
"period": "Period",
"unit": "Unit",
"stat_types": "Show stat types",
"stat_type_labels": {
"mean": "Mean",