mirror of
https://github.com/home-assistant/frontend.git
synced 2025-07-26 02:36:37 +00:00
fix and finish statistics card (#9658)
This commit is contained in:
parent
5234e9bce5
commit
cfad45b7c2
@ -55,7 +55,7 @@ export default class HaChartBase extends LitElement {
|
|||||||
this._setupChart();
|
this._setupChart();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (changedProps.has("type")) {
|
if (changedProps.has("chartType")) {
|
||||||
this.chart.config.type = this.chartType;
|
this.chart.config.type = this.chartType;
|
||||||
}
|
}
|
||||||
if (changedProps.has("data")) {
|
if (changedProps.has("data")) {
|
||||||
|
@ -38,8 +38,8 @@ class StatisticsChart extends LitElement {
|
|||||||
@property({ type: Array }) public statTypes: Array<StatisticType> = [
|
@property({ type: Array }) public statTypes: Array<StatisticType> = [
|
||||||
"sum",
|
"sum",
|
||||||
"min",
|
"min",
|
||||||
"max",
|
|
||||||
"mean",
|
"mean",
|
||||||
|
"max",
|
||||||
];
|
];
|
||||||
|
|
||||||
@property() public chartType: ChartType = "line";
|
@property() public chartType: ChartType = "line";
|
||||||
@ -58,7 +58,7 @@ class StatisticsChart extends LitElement {
|
|||||||
if (!this.hasUpdated) {
|
if (!this.hasUpdated) {
|
||||||
this._createOptions();
|
this._createOptions();
|
||||||
}
|
}
|
||||||
if (changedProps.has("statisticsData")) {
|
if (changedProps.has("statisticsData") || changedProps.has("statTypes")) {
|
||||||
this._generateData();
|
this._generateData();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -164,6 +164,9 @@ class StatisticsChart extends LitElement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private _generateData() {
|
private _generateData() {
|
||||||
|
if (!this.statisticsData) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
let colorIndex = 0;
|
let colorIndex = 0;
|
||||||
const statisticsData = Object.values(this.statisticsData);
|
const statisticsData = Object.values(this.statisticsData);
|
||||||
const totalDataSets: ChartDataset<"line">[] = [];
|
const totalDataSets: ChartDataset<"line">[] = [];
|
||||||
@ -231,21 +234,21 @@ class StatisticsChart extends LitElement {
|
|||||||
prevValues = dataValues;
|
prevValues = dataValues;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const color = getColorByIndex(colorIndex);
|
||||||
|
colorIndex++;
|
||||||
|
|
||||||
const addDataSet = (
|
const addDataSet = (
|
||||||
nameY: string,
|
nameY: string,
|
||||||
|
borderColor: string,
|
||||||
|
backgroundColor: string,
|
||||||
step = false,
|
step = false,
|
||||||
fill = false,
|
fill?: boolean | number | string
|
||||||
color?: string
|
|
||||||
) => {
|
) => {
|
||||||
if (!color) {
|
|
||||||
color = getColorByIndex(colorIndex);
|
|
||||||
colorIndex++;
|
|
||||||
}
|
|
||||||
statDataSets.push({
|
statDataSets.push({
|
||||||
label: nameY,
|
label: nameY,
|
||||||
fill: fill ? "origin" : false,
|
fill: fill || false,
|
||||||
borderColor: color,
|
borderColor,
|
||||||
backgroundColor: color + "7F",
|
backgroundColor: backgroundColor,
|
||||||
stepped: step ? "before" : false,
|
stepped: step ? "before" : false,
|
||||||
pointRadius: 0,
|
pointRadius: 0,
|
||||||
data: [],
|
data: [],
|
||||||
@ -254,20 +257,50 @@ class StatisticsChart extends LitElement {
|
|||||||
|
|
||||||
const statTypes: this["statTypes"] = [];
|
const statTypes: this["statTypes"] = [];
|
||||||
|
|
||||||
this.statTypes.forEach((type) => {
|
const sortedTypes = [...this.statTypes].sort((a, _b) => {
|
||||||
|
if (a === "min") {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
if (a === "max") {
|
||||||
|
return +1;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
});
|
||||||
|
|
||||||
|
const drawBands =
|
||||||
|
this.statTypes.includes("mean") && statisticsHaveType(stats, "mean");
|
||||||
|
|
||||||
|
sortedTypes.forEach((type) => {
|
||||||
if (statisticsHaveType(stats, type)) {
|
if (statisticsHaveType(stats, type)) {
|
||||||
statTypes.push(type);
|
statTypes.push(type);
|
||||||
addDataSet(
|
addDataSet(
|
||||||
`${name} (${this.hass.localize(
|
`${name} (${this.hass.localize(
|
||||||
`ui.components.statistics_charts.statistic_types.${type}`
|
`ui.components.statistics_charts.statistic_types.${type}`
|
||||||
)})`,
|
)})`,
|
||||||
false
|
drawBands && (type === "min" || type === "max")
|
||||||
|
? color + "7F"
|
||||||
|
: color,
|
||||||
|
color + "7F",
|
||||||
|
false,
|
||||||
|
drawBands
|
||||||
|
? type === "min"
|
||||||
|
? "+1"
|
||||||
|
: type === "max"
|
||||||
|
? "-1"
|
||||||
|
: false
|
||||||
|
: false
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
let prevDate: Date | null = null;
|
||||||
// Process chart data.
|
// Process chart data.
|
||||||
stats.forEach((stat) => {
|
stats.forEach((stat) => {
|
||||||
|
const date = new Date(stat.start);
|
||||||
|
if (prevDate === date) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
prevDate = date;
|
||||||
const dataValues: Array<number | null> = [];
|
const dataValues: Array<number | null> = [];
|
||||||
statTypes.forEach((type) => {
|
statTypes.forEach((type) => {
|
||||||
let val: number | null;
|
let val: number | null;
|
||||||
@ -278,7 +311,6 @@ class StatisticsChart extends LitElement {
|
|||||||
}
|
}
|
||||||
dataValues.push(val !== null ? Math.round(val * 100) / 100 : null);
|
dataValues.push(val !== null ? Math.round(val * 100) / 100 : null);
|
||||||
});
|
});
|
||||||
const date = new Date(stat.start);
|
|
||||||
pushData(date, dataValues);
|
pushData(date, dataValues);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -60,10 +60,14 @@ export class Gauge extends LitElement {
|
|||||||
protected render() {
|
protected render() {
|
||||||
return svg`
|
return svg`
|
||||||
<svg viewBox="0 0 100 50" class="gauge">
|
<svg viewBox="0 0 100 50" class="gauge">
|
||||||
<path
|
${
|
||||||
|
!this.needle || !this.levels
|
||||||
|
? svg`<path
|
||||||
class="dial"
|
class="dial"
|
||||||
d="M 10 50 A 40 40 0 0 1 90 50"
|
d="M 10 50 A 40 40 0 0 1 90 50"
|
||||||
></path>
|
></path>`
|
||||||
|
: ""
|
||||||
|
}
|
||||||
|
|
||||||
${
|
${
|
||||||
this.levels
|
this.levels
|
||||||
|
@ -206,7 +206,7 @@ class HuiGaugeCard extends LitElement implements LovelaceCard {
|
|||||||
const sections = this._config!.severity;
|
const sections = this._config!.severity;
|
||||||
|
|
||||||
if (!sections) {
|
if (!sections) {
|
||||||
return [];
|
return [{ level: 0, stroke: severityMap.normal }];
|
||||||
}
|
}
|
||||||
|
|
||||||
const sectionsArray = Object.keys(sections);
|
const sectionsArray = Object.keys(sections);
|
||||||
|
@ -114,7 +114,10 @@ export class HuiStatisticsGraphCard extends LitElement implements LovelaceCard {
|
|||||||
| StatisticsGraphCardConfig
|
| StatisticsGraphCardConfig
|
||||||
| undefined;
|
| undefined;
|
||||||
|
|
||||||
if (oldConfig?.entities !== this._config.entities) {
|
if (
|
||||||
|
oldConfig?.entities !== this._config.entities ||
|
||||||
|
oldConfig?.days_to_show !== this._config.days_to_show
|
||||||
|
) {
|
||||||
this._getStatistics();
|
this._getStatistics();
|
||||||
// statistics are created every hour
|
// statistics are created every hour
|
||||||
clearInterval(this._interval);
|
clearInterval(this._interval);
|
||||||
|
@ -1,7 +1,16 @@
|
|||||||
import "@polymer/paper-input/paper-input";
|
import "@polymer/paper-input/paper-input";
|
||||||
import { CSSResultGroup, html, LitElement, TemplateResult } from "lit";
|
import { CSSResultGroup, html, LitElement, TemplateResult } from "lit";
|
||||||
import { customElement, property, state } from "lit/decorators";
|
import { customElement, property, state } from "lit/decorators";
|
||||||
import { array, assert, number, object, optional, string } from "superstruct";
|
import {
|
||||||
|
array,
|
||||||
|
assert,
|
||||||
|
literal,
|
||||||
|
number,
|
||||||
|
object,
|
||||||
|
optional,
|
||||||
|
string,
|
||||||
|
union,
|
||||||
|
} from "superstruct";
|
||||||
import { fireEvent } from "../../../../common/dom/fire_event";
|
import { fireEvent } from "../../../../common/dom/fire_event";
|
||||||
import { HomeAssistant } from "../../../../types";
|
import { HomeAssistant } from "../../../../types";
|
||||||
import { StatisticsGraphCardConfig } from "../../cards/types";
|
import { StatisticsGraphCardConfig } from "../../cards/types";
|
||||||
@ -11,12 +20,26 @@ import { EditorTarget } from "../types";
|
|||||||
import { configElementStyle } from "./config-elements-style";
|
import { configElementStyle } from "./config-elements-style";
|
||||||
import "../../../../components/entity/ha-statistics-picker";
|
import "../../../../components/entity/ha-statistics-picker";
|
||||||
import { processConfigEntities } from "../../common/process-config-entities";
|
import { processConfigEntities } from "../../common/process-config-entities";
|
||||||
|
import "../../../../components/ha-formfield";
|
||||||
|
import "../../../../components/ha-checkbox";
|
||||||
|
import { StatisticType } from "../../../../data/history";
|
||||||
|
import "../../../../components/ha-radio";
|
||||||
|
import type { HaRadio } from "../../../../components/ha-radio";
|
||||||
|
|
||||||
|
const statTypeStruct = union([
|
||||||
|
literal("sum"),
|
||||||
|
literal("min"),
|
||||||
|
literal("max"),
|
||||||
|
literal("mean"),
|
||||||
|
]);
|
||||||
|
|
||||||
const cardConfigStruct = object({
|
const cardConfigStruct = object({
|
||||||
type: string(),
|
type: string(),
|
||||||
entities: array(entitiesConfigStruct),
|
entities: array(entitiesConfigStruct),
|
||||||
title: optional(string()),
|
title: optional(string()),
|
||||||
days_to_show: optional(number()),
|
days_to_show: optional(number()),
|
||||||
|
chart_type: optional(union([literal("bar"), literal("line")])),
|
||||||
|
stat_types: optional(union([array(statTypeStruct), statTypeStruct])),
|
||||||
});
|
});
|
||||||
|
|
||||||
@customElement("hui-statistics-graph-card-editor")
|
@customElement("hui-statistics-graph-card-editor")
|
||||||
@ -46,6 +69,18 @@ export class HuiStatisticsGraphCardEditor
|
|||||||
return this._config!.days_to_show || 30;
|
return this._config!.days_to_show || 30;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get _chart_type(): StatisticsGraphCardConfig["chart_type"] {
|
||||||
|
return this._config!.chart_type || "line";
|
||||||
|
}
|
||||||
|
|
||||||
|
get _stat_types(): StatisticType[] {
|
||||||
|
return this._config!.stat_types
|
||||||
|
? Array.isArray(this._config!.stat_types)
|
||||||
|
? this._config!.stat_types
|
||||||
|
: [this._config!.stat_types]
|
||||||
|
: ["mean", "min", "max", "sum"];
|
||||||
|
}
|
||||||
|
|
||||||
protected render(): TemplateResult {
|
protected render(): TemplateResult {
|
||||||
if (!this.hass || !this._config) {
|
if (!this.hass || !this._config) {
|
||||||
return html``;
|
return html``;
|
||||||
@ -63,19 +98,67 @@ export class HuiStatisticsGraphCardEditor
|
|||||||
.configValue=${"title"}
|
.configValue=${"title"}
|
||||||
@value-changed=${this._valueChanged}
|
@value-changed=${this._valueChanged}
|
||||||
></paper-input>
|
></paper-input>
|
||||||
|
<paper-input
|
||||||
|
type="number"
|
||||||
|
.label="${this.hass.localize(
|
||||||
|
"ui.panel.lovelace.editor.card.generic.days_to_show"
|
||||||
|
)} (${this.hass.localize(
|
||||||
|
"ui.panel.lovelace.editor.card.config.optional"
|
||||||
|
)})"
|
||||||
|
.value=${this._days_to_show}
|
||||||
|
min="1"
|
||||||
|
.configValue=${"days_to_show"}
|
||||||
|
@value-changed=${this._valueChanged}
|
||||||
|
></paper-input>
|
||||||
|
<p>Show stat types:</p>
|
||||||
<div class="side-by-side">
|
<div class="side-by-side">
|
||||||
<paper-input
|
<ha-formfield label="Sum">
|
||||||
type="number"
|
<ha-checkbox
|
||||||
.label="${this.hass.localize(
|
.checked=${this._stat_types.includes("sum")}
|
||||||
"ui.panel.lovelace.editor.card.generic.days_to_show"
|
name="sum"
|
||||||
)} (${this.hass.localize(
|
@change=${this._statTypesChanged}
|
||||||
"ui.panel.lovelace.editor.card.config.optional"
|
></ha-checkbox>
|
||||||
)})"
|
</ha-formfield>
|
||||||
.value=${this._days_to_show}
|
<ha-formfield label="Mean">
|
||||||
min="1"
|
<ha-checkbox
|
||||||
.configValue=${"days_to_show"}
|
.checked=${this._stat_types.includes("mean")}
|
||||||
@value-changed=${this._valueChanged}
|
name="mean"
|
||||||
></paper-input>
|
@change=${this._statTypesChanged}
|
||||||
|
></ha-checkbox>
|
||||||
|
</ha-formfield>
|
||||||
|
<ha-formfield label="Min">
|
||||||
|
<ha-checkbox
|
||||||
|
.checked=${this._stat_types.includes("min")}
|
||||||
|
name="min"
|
||||||
|
@change=${this._statTypesChanged}
|
||||||
|
></ha-checkbox>
|
||||||
|
</ha-formfield>
|
||||||
|
<ha-formfield label="Max">
|
||||||
|
<ha-checkbox
|
||||||
|
.checked=${this._stat_types.includes("max")}
|
||||||
|
name="max"
|
||||||
|
@change=${this._statTypesChanged}
|
||||||
|
></ha-checkbox>
|
||||||
|
</ha-formfield>
|
||||||
|
</div>
|
||||||
|
<div class="side-by-side">
|
||||||
|
<p>Chart type:</p>
|
||||||
|
<ha-formfield label="Line">
|
||||||
|
<ha-radio
|
||||||
|
.checked=${this._chart_type === "line"}
|
||||||
|
value="line"
|
||||||
|
name="chart_type"
|
||||||
|
@change=${this._chartTypeChanged}
|
||||||
|
></ha-radio>
|
||||||
|
</ha-formfield>
|
||||||
|
<ha-formfield label="Bar">
|
||||||
|
<ha-radio
|
||||||
|
.checked=${this._chart_type === "bar"}
|
||||||
|
value="bar"
|
||||||
|
name="chart_type"
|
||||||
|
@change=${this._chartTypeChanged}
|
||||||
|
></ha-radio>
|
||||||
|
</ha-formfield>
|
||||||
</div>
|
</div>
|
||||||
<ha-statistics-picker
|
<ha-statistics-picker
|
||||||
.hass=${this.hass}
|
.hass=${this.hass}
|
||||||
@ -89,6 +172,31 @@ export class HuiStatisticsGraphCardEditor
|
|||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private _chartTypeChanged(ev: CustomEvent) {
|
||||||
|
const input = ev.currentTarget as HaRadio;
|
||||||
|
fireEvent(this, "config-changed", {
|
||||||
|
config: { ...this._config!, chart_type: input.value },
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private _statTypesChanged(ev) {
|
||||||
|
const name = ev.currentTarget.name;
|
||||||
|
const checked = ev.currentTarget.checked;
|
||||||
|
if (checked) {
|
||||||
|
fireEvent(this, "config-changed", {
|
||||||
|
config: { ...this._config!, stat_types: [...this._stat_types, name] },
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const statTypes = [...this._stat_types];
|
||||||
|
fireEvent(this, "config-changed", {
|
||||||
|
config: {
|
||||||
|
...this._config!,
|
||||||
|
stat_types: statTypes.filter((stat) => stat !== name),
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
private _valueChanged(ev: CustomEvent): void {
|
private _valueChanged(ev: CustomEvent): void {
|
||||||
if (!this._config || !this.hass) {
|
if (!this._config || !this.hass) {
|
||||||
return;
|
return;
|
||||||
|
Loading…
x
Reference in New Issue
Block a user