fix and finish statistics card (#9658)

This commit is contained in:
Bram Kragten 2021-07-30 21:52:38 +02:00 committed by GitHub
parent 5234e9bce5
commit cfad45b7c2
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 179 additions and 32 deletions

View File

@ -55,7 +55,7 @@ export default class HaChartBase extends LitElement {
this._setupChart();
return;
}
if (changedProps.has("type")) {
if (changedProps.has("chartType")) {
this.chart.config.type = this.chartType;
}
if (changedProps.has("data")) {

View File

@ -38,8 +38,8 @@ class StatisticsChart extends LitElement {
@property({ type: Array }) public statTypes: Array<StatisticType> = [
"sum",
"min",
"max",
"mean",
"max",
];
@property() public chartType: ChartType = "line";
@ -58,7 +58,7 @@ class StatisticsChart extends LitElement {
if (!this.hasUpdated) {
this._createOptions();
}
if (changedProps.has("statisticsData")) {
if (changedProps.has("statisticsData") || changedProps.has("statTypes")) {
this._generateData();
}
}
@ -164,6 +164,9 @@ class StatisticsChart extends LitElement {
}
private _generateData() {
if (!this.statisticsData) {
return;
}
let colorIndex = 0;
const statisticsData = Object.values(this.statisticsData);
const totalDataSets: ChartDataset<"line">[] = [];
@ -231,21 +234,21 @@ class StatisticsChart extends LitElement {
prevValues = dataValues;
};
const color = getColorByIndex(colorIndex);
colorIndex++;
const addDataSet = (
nameY: string,
borderColor: string,
backgroundColor: string,
step = false,
fill = false,
color?: string
fill?: boolean | number | string
) => {
if (!color) {
color = getColorByIndex(colorIndex);
colorIndex++;
}
statDataSets.push({
label: nameY,
fill: fill ? "origin" : false,
borderColor: color,
backgroundColor: color + "7F",
fill: fill || false,
borderColor,
backgroundColor: backgroundColor,
stepped: step ? "before" : false,
pointRadius: 0,
data: [],
@ -254,20 +257,50 @@ class StatisticsChart extends LitElement {
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)) {
statTypes.push(type);
addDataSet(
`${name} (${this.hass.localize(
`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.
stats.forEach((stat) => {
const date = new Date(stat.start);
if (prevDate === date) {
return;
}
prevDate = date;
const dataValues: Array<number | null> = [];
statTypes.forEach((type) => {
let val: number | null;
@ -278,7 +311,6 @@ class StatisticsChart extends LitElement {
}
dataValues.push(val !== null ? Math.round(val * 100) / 100 : null);
});
const date = new Date(stat.start);
pushData(date, dataValues);
});

View File

@ -60,10 +60,14 @@ export class Gauge extends LitElement {
protected render() {
return svg`
<svg viewBox="0 0 100 50" class="gauge">
<path
${
!this.needle || !this.levels
? svg`<path
class="dial"
d="M 10 50 A 40 40 0 0 1 90 50"
></path>
></path>`
: ""
}
${
this.levels

View File

@ -206,7 +206,7 @@ class HuiGaugeCard extends LitElement implements LovelaceCard {
const sections = this._config!.severity;
if (!sections) {
return [];
return [{ level: 0, stroke: severityMap.normal }];
}
const sectionsArray = Object.keys(sections);

View File

@ -114,7 +114,10 @@ export class HuiStatisticsGraphCard extends LitElement implements LovelaceCard {
| StatisticsGraphCardConfig
| undefined;
if (oldConfig?.entities !== this._config.entities) {
if (
oldConfig?.entities !== this._config.entities ||
oldConfig?.days_to_show !== this._config.days_to_show
) {
this._getStatistics();
// statistics are created every hour
clearInterval(this._interval);

View File

@ -1,7 +1,16 @@
import "@polymer/paper-input/paper-input";
import { CSSResultGroup, html, LitElement, TemplateResult } from "lit";
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 { HomeAssistant } from "../../../../types";
import { StatisticsGraphCardConfig } from "../../cards/types";
@ -11,12 +20,26 @@ import { EditorTarget } from "../types";
import { configElementStyle } from "./config-elements-style";
import "../../../../components/entity/ha-statistics-picker";
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({
type: string(),
entities: array(entitiesConfigStruct),
title: optional(string()),
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")
@ -46,6 +69,18 @@ export class HuiStatisticsGraphCardEditor
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 {
if (!this.hass || !this._config) {
return html``;
@ -63,19 +98,67 @@ export class HuiStatisticsGraphCardEditor
.configValue=${"title"}
@value-changed=${this._valueChanged}
></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">
<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>
<ha-formfield label="Sum">
<ha-checkbox
.checked=${this._stat_types.includes("sum")}
name="sum"
@change=${this._statTypesChanged}
></ha-checkbox>
</ha-formfield>
<ha-formfield label="Mean">
<ha-checkbox
.checked=${this._stat_types.includes("mean")}
name="mean"
@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>
<ha-statistics-picker
.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 {
if (!this._config || !this.hass) {
return;