mirror of
https://github.com/home-assistant/frontend.git
synced 2025-07-24 09:46:36 +00:00
Convert units in statistics graph card (#13970)
Co-authored-by: Bram Kragten <mail@bramkragten.nl>
This commit is contained in:
parent
1c03dc9b77
commit
1cb44ce857
@ -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 = {
|
||||
|
@ -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,
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
|
@ -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[];
|
||||
|
@ -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}`
|
||||
);
|
||||
|
@ -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",
|
||||
|
Loading…
x
Reference in New Issue
Block a user