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(); 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")) {

View File

@ -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);
}); });

View File

@ -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

View File

@ -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);

View File

@ -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);

View File

@ -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;