mirror of
https://github.com/home-assistant/frontend.git
synced 2025-07-24 09:46:36 +00:00
Improve statistics graph editor (#13630)
This commit is contained in:
parent
bc62e9372b
commit
bb0529ecd2
@ -3,6 +3,7 @@ import { html, LitElement, PropertyValues, TemplateResult } from "lit";
|
||||
import { ComboBoxLitRenderer } from "@vaadin/combo-box/lit";
|
||||
import { customElement, property, query, state } from "lit/decorators";
|
||||
import memoizeOne from "memoize-one";
|
||||
import { ensureArray } from "../../common/ensure-array";
|
||||
import { fireEvent } from "../../common/dom/fire_event";
|
||||
import { computeStateName } from "../../common/entity/compute_state_name";
|
||||
import { stringCompare } from "../../common/string/compare";
|
||||
@ -39,22 +40,20 @@ export class HaStatisticPicker extends LitElement {
|
||||
type: Array,
|
||||
attribute: "include-statistics-unit-of-measurement",
|
||||
})
|
||||
public includeStatisticsUnitOfMeasurement?: string[];
|
||||
public includeStatisticsUnitOfMeasurement?: string | string[];
|
||||
|
||||
/**
|
||||
* Show only statistics displayed with these units of measurements.
|
||||
* @type {Array}
|
||||
* @attr include-display-unit-of-measurement
|
||||
*/
|
||||
@property({ type: Array, attribute: "include-display-unit-of-measurement" })
|
||||
public includeDisplayUnitOfMeasurement?: string[];
|
||||
@property({ attribute: "include-display-unit-of-measurement" })
|
||||
public includeDisplayUnitOfMeasurement?: string | string[];
|
||||
|
||||
/**
|
||||
* Show only statistics with these device classes.
|
||||
* @type {Array}
|
||||
* @attr include-device-classes
|
||||
*/
|
||||
@property({ type: Array, attribute: "include-device-classes" })
|
||||
@property({ attribute: "include-device-classes" })
|
||||
public includeDeviceClasses?: string[];
|
||||
|
||||
/**
|
||||
@ -97,8 +96,8 @@ export class HaStatisticPicker extends LitElement {
|
||||
private _getStatistics = memoizeOne(
|
||||
(
|
||||
statisticIds: StatisticsMetaData[],
|
||||
includeStatisticsUnitOfMeasurement?: string[],
|
||||
includeDisplayUnitOfMeasurement?: string[],
|
||||
includeStatisticsUnitOfMeasurement?: string | string[],
|
||||
includeDisplayUnitOfMeasurement?: string | string[],
|
||||
includeDeviceClasses?: string[],
|
||||
entitiesOnly?: boolean
|
||||
): Array<{ id: string; name: string; state?: HassEntity }> => {
|
||||
@ -114,17 +113,15 @@ export class HaStatisticPicker extends LitElement {
|
||||
}
|
||||
|
||||
if (includeStatisticsUnitOfMeasurement) {
|
||||
const includeUnits = ensureArray(includeStatisticsUnitOfMeasurement);
|
||||
statisticIds = statisticIds.filter((meta) =>
|
||||
includeStatisticsUnitOfMeasurement.includes(
|
||||
meta.statistics_unit_of_measurement
|
||||
)
|
||||
includeUnits.includes(meta.statistics_unit_of_measurement)
|
||||
);
|
||||
}
|
||||
if (includeDisplayUnitOfMeasurement) {
|
||||
const includeUnits = ensureArray(includeDisplayUnitOfMeasurement);
|
||||
statisticIds = statisticIds.filter((meta) =>
|
||||
includeDisplayUnitOfMeasurement.includes(
|
||||
meta.display_unit_of_measurement
|
||||
)
|
||||
includeUnits.includes(meta.display_unit_of_measurement)
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -22,11 +22,52 @@ class HaStatisticsPicker extends LitElement {
|
||||
@property({ attribute: "pick-statistic-label" })
|
||||
public pickStatisticLabel?: string;
|
||||
|
||||
/**
|
||||
* Show only statistics natively stored with these units of measurements.
|
||||
* @attr include-statistics-unit-of-measurement
|
||||
*/
|
||||
@property({
|
||||
attribute: "include-statistics-unit-of-measurement",
|
||||
})
|
||||
public includeStatisticsUnitOfMeasurement?: string[] | string;
|
||||
|
||||
/**
|
||||
* Show only statistics displayed with these units of measurements.
|
||||
* @attr include-display-unit-of-measurement
|
||||
*/
|
||||
@property({ attribute: "include-display-unit-of-measurement" })
|
||||
public includeDisplayUnitOfMeasurement?: string[] | string;
|
||||
|
||||
/**
|
||||
* Ignore filtering of statistics type and units when only a single statistic is selected.
|
||||
* @type {boolean}
|
||||
* @attr ignore-restrictions-on-first-statistic
|
||||
*/
|
||||
@property({
|
||||
type: Boolean,
|
||||
attribute: "ignore-restrictions-on-first-statistic",
|
||||
})
|
||||
public ignoreRestrictionsOnFirstStatistic = false;
|
||||
|
||||
protected render(): TemplateResult {
|
||||
if (!this.hass) {
|
||||
return html``;
|
||||
}
|
||||
|
||||
const ignoreRestriction =
|
||||
this.ignoreRestrictionsOnFirstStatistic &&
|
||||
this._currentStatistics.length <= 1;
|
||||
|
||||
const includeDisplayUnitCurrent = ignoreRestriction
|
||||
? undefined
|
||||
: this.includeDisplayUnitOfMeasurement;
|
||||
const includeStatisticsUnitCurrent = ignoreRestriction
|
||||
? undefined
|
||||
: this.includeStatisticsUnitOfMeasurement;
|
||||
const includeStatisticTypesCurrent = ignoreRestriction
|
||||
? undefined
|
||||
: this.statisticTypes;
|
||||
|
||||
return html`
|
||||
${this._currentStatistics.map(
|
||||
(statisticId) => html`
|
||||
@ -34,8 +75,10 @@ class HaStatisticsPicker extends LitElement {
|
||||
<ha-statistic-picker
|
||||
.curValue=${statisticId}
|
||||
.hass=${this.hass}
|
||||
.includeDisplayUnitOfMeasurement=${includeDisplayUnitCurrent}
|
||||
.includeStatisticsUnitOfMeasurement=${includeStatisticsUnitCurrent}
|
||||
.value=${statisticId}
|
||||
.statisticTypes=${this.statisticTypes}
|
||||
.statisticTypes=${includeStatisticTypesCurrent}
|
||||
.statisticIds=${this.statisticIds}
|
||||
.label=${this.pickedStatisticLabel}
|
||||
@value-changed=${this._statisticChanged}
|
||||
@ -46,6 +89,10 @@ class HaStatisticsPicker extends LitElement {
|
||||
<div>
|
||||
<ha-statistic-picker
|
||||
.hass=${this.hass}
|
||||
.includeDisplayUnitOfMeasurement=${this
|
||||
.includeDisplayUnitOfMeasurement}
|
||||
.includeStatisticsUnitOfMeasurement=${this
|
||||
.includeStatisticsUnitOfMeasurement}
|
||||
.statisticTypes=${this.statisticTypes}
|
||||
.statisticIds=${this.statisticIds}
|
||||
.label=${this.pickStatisticLabel}
|
||||
|
@ -184,6 +184,22 @@ export const statisticsHaveType = (
|
||||
type: StatisticType
|
||||
) => stats.some((stat) => stat[type] !== null);
|
||||
|
||||
const mean_stat_types: readonly StatisticType[] = ["mean", "min", "max"];
|
||||
const sum_stat_types: readonly StatisticType[] = ["sum"];
|
||||
|
||||
export const statisticsMetaHasType = (
|
||||
metadata: StatisticsMetaData,
|
||||
type: StatisticType
|
||||
) => {
|
||||
if (mean_stat_types.includes(type) && metadata.has_mean) {
|
||||
return true;
|
||||
}
|
||||
if (sum_stat_types.includes(type) && metadata.has_sum) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
export const adjustStatisticsSum = (
|
||||
hass: HomeAssistant,
|
||||
statistic_id: string,
|
||||
|
@ -1,5 +1,12 @@
|
||||
import "../../../../components/ha-form/ha-form";
|
||||
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
|
||||
import {
|
||||
css,
|
||||
CSSResultGroup,
|
||||
html,
|
||||
LitElement,
|
||||
PropertyValues,
|
||||
TemplateResult,
|
||||
} from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import memoizeOne from "memoize-one";
|
||||
import {
|
||||
@ -23,6 +30,12 @@ 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,
|
||||
StatisticsMetaData,
|
||||
statisticsMetaHasType,
|
||||
} from "../../../../data/recorder";
|
||||
import { deepEqual } from "../../../../common/util/deep-equal";
|
||||
|
||||
const statTypeStruct = union([
|
||||
literal("sum"),
|
||||
@ -51,6 +64,13 @@ const cardConfigStruct = assign(
|
||||
);
|
||||
|
||||
const periods = ["5minute", "hour", "day", "month"] as const;
|
||||
const stat_types = ["mean", "min", "max", "sum"] as const;
|
||||
const stat_type_labels = {
|
||||
mean: "Mean",
|
||||
min: "Min",
|
||||
max: "Max",
|
||||
sum: "Sum",
|
||||
} as const;
|
||||
|
||||
@customElement("hui-statistics-graph-card-editor")
|
||||
export class HuiStatisticsGraphCardEditor
|
||||
@ -63,6 +83,8 @@ export class HuiStatisticsGraphCardEditor
|
||||
|
||||
@state() private _configEntities?: string[];
|
||||
|
||||
@state() private _metaDatas?: StatisticsMetaData[];
|
||||
|
||||
public setConfig(config: StatisticsGraphCardConfig): void {
|
||||
assert(config, cardConfigStruct);
|
||||
this._config = config;
|
||||
@ -71,8 +93,29 @@ export class HuiStatisticsGraphCardEditor
|
||||
: [];
|
||||
}
|
||||
|
||||
private _getStatisticsMetaData = async (statisticIds?: string[]) => {
|
||||
this._metaDatas = await getStatisticMetadata(
|
||||
this.hass!,
|
||||
statisticIds || []
|
||||
);
|
||||
};
|
||||
|
||||
public willUpdate(changedProps: PropertyValues) {
|
||||
if (
|
||||
changedProps.has("_configEntities") &&
|
||||
!deepEqual(this._configEntities, changedProps.get("_configEntities"))
|
||||
) {
|
||||
this._metaDatas = undefined;
|
||||
this._getStatisticsMetaData(this._configEntities);
|
||||
}
|
||||
}
|
||||
|
||||
private _schema = memoizeOne(
|
||||
(localize: LocalizeFunc) =>
|
||||
(
|
||||
localize: LocalizeFunc,
|
||||
statisticIds: string[] | undefined,
|
||||
metaDatas: StatisticsMetaData[] | undefined
|
||||
) =>
|
||||
[
|
||||
{ name: "title", selector: { text: {} } },
|
||||
{
|
||||
@ -89,6 +132,13 @@ export class HuiStatisticsGraphCardEditor
|
||||
label: localize(
|
||||
`ui.panel.lovelace.editor.card.statistics-graph.periods.${period}`
|
||||
),
|
||||
disabled:
|
||||
period === "5minute" &&
|
||||
// External statistics don't support 5-minute statistics.
|
||||
// External statistics is formatted as <domain>:<object_id>
|
||||
statisticIds?.some((statistic_id) =>
|
||||
statistic_id.includes(":")
|
||||
),
|
||||
})),
|
||||
},
|
||||
},
|
||||
@ -101,13 +151,20 @@ export class HuiStatisticsGraphCardEditor
|
||||
{
|
||||
name: "stat_types",
|
||||
required: true,
|
||||
type: "multi_select",
|
||||
options: [
|
||||
["mean", "Mean"],
|
||||
["min", "Min"],
|
||||
["max", "Max"],
|
||||
["sum", "Sum"],
|
||||
],
|
||||
selector: {
|
||||
select: {
|
||||
multiple: true,
|
||||
options: stat_types.map((stat_type) => ({
|
||||
value: stat_type,
|
||||
label: stat_type_labels[stat_type],
|
||||
disabled:
|
||||
!metaDatas ||
|
||||
!metaDatas?.every((metaData) =>
|
||||
statisticsMetaHasType(metaData, stat_type)
|
||||
),
|
||||
})),
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "chart_type",
|
||||
@ -128,19 +185,24 @@ export class HuiStatisticsGraphCardEditor
|
||||
return html``;
|
||||
}
|
||||
|
||||
const schema = this._schema(this.hass.localize);
|
||||
const stat_types = this._config!.stat_types
|
||||
const schema = this._schema(
|
||||
this.hass.localize,
|
||||
this._configEntities,
|
||||
this._metaDatas
|
||||
);
|
||||
const configured_stat_types = this._config!.stat_types
|
||||
? Array.isArray(this._config!.stat_types)
|
||||
? this._config!.stat_types
|
||||
: [this._config!.stat_types]
|
||||
: ["mean", "min", "max", "sum"];
|
||||
: stat_types;
|
||||
const data = {
|
||||
chart_type: "line",
|
||||
period: "hour",
|
||||
days_to_show: 30,
|
||||
...this._config,
|
||||
stat_types,
|
||||
stat_types: configured_stat_types,
|
||||
};
|
||||
const displayUnit = this._metaDatas?.[0]?.display_unit_of_measurement;
|
||||
|
||||
return html`
|
||||
<ha-form
|
||||
@ -154,6 +216,8 @@ export class HuiStatisticsGraphCardEditor
|
||||
.hass=${this.hass}
|
||||
.pickStatisticLabel=${`Add a statistic`}
|
||||
.pickedStatisticLabel=${`Statistic`}
|
||||
.includeDisplayUnitOfMeasurement=${displayUnit}
|
||||
.ignoreRestrictionsOnFirstStatistic=${true}
|
||||
.value=${this._configEntities}
|
||||
.configValue=${"entities"}
|
||||
@value-changed=${this._entitiesChanged}
|
||||
|
Loading…
x
Reference in New Issue
Block a user