diff --git a/src/common/string/format_number.ts b/src/common/string/format_number.ts index 01279cb715..56ab2ec8dc 100644 --- a/src/common/string/format_number.ts +++ b/src/common/string/format_number.ts @@ -78,7 +78,10 @@ const getDefaultFormatOptions = ( num: string | number, options?: Intl.NumberFormatOptions ): Intl.NumberFormatOptions => { - const defaultOptions: Intl.NumberFormatOptions = options || {}; + const defaultOptions: Intl.NumberFormatOptions = { + maximumFractionDigits: 2, + ...options, + }; if (typeof num !== "string") { return defaultOptions; diff --git a/src/components/chart/state-history-chart-line.ts b/src/components/chart/state-history-chart-line.ts index b2bddfba4f..574d86112a 100644 --- a/src/components/chart/state-history-chart-line.ts +++ b/src/components/chart/state-history-chart-line.ts @@ -2,7 +2,10 @@ import type { ChartData, ChartDataset, ChartOptions } from "chart.js"; import { html, LitElement, PropertyValues } from "lit"; import { property, state } from "lit/decorators"; import { getColorByIndex } from "../../common/color/colors"; -import { numberFormatToLocale } from "../../common/string/format_number"; +import { + formatNumber, + numberFormatToLocale, +} from "../../common/string/format_number"; import { LineChartEntity, LineChartState } from "../../data/history"; import { HomeAssistant } from "../../types"; import "./ha-chart-base"; @@ -85,7 +88,10 @@ class StateHistoryChartLine extends LitElement { mode: "nearest", callbacks: { label: (context) => - `${context.dataset.label}: ${context.parsed.y} ${this.unit}`, + `${context.dataset.label}: ${formatNumber( + context.parsed.y, + this.hass.locale + )} ${this.unit}`, }, }, filler: { diff --git a/src/components/chart/statistics-chart.ts b/src/components/chart/statistics-chart.ts index 68431696ba..409f5a08ac 100644 --- a/src/components/chart/statistics-chart.ts +++ b/src/components/chart/statistics-chart.ts @@ -16,10 +16,15 @@ import { customElement, property, state } from "lit/decorators"; import { getColorByIndex } from "../../common/color/colors"; import { isComponentLoaded } from "../../common/config/is_component_loaded"; import { computeStateName } from "../../common/entity/compute_state_name"; -import { numberFormatToLocale } from "../../common/string/format_number"; import { + formatNumber, + numberFormatToLocale, +} from "../../common/string/format_number"; +import { + getStatisticIds, Statistics, statisticsHaveType, + StatisticsMetaData, StatisticType, } from "../../data/history"; import type { HomeAssistant } from "../../types"; @@ -31,8 +36,12 @@ class StatisticsChart extends LitElement { @property({ attribute: false }) public statisticsData!: Statistics; + @property({ type: Array }) public statisticIds?: StatisticsMetaData[]; + @property() public names: boolean | Record = false; + @property() public unit?: string; + @property({ attribute: false }) public endTime?: Date; @property({ type: Array }) public statTypes: Array = [ @@ -46,12 +55,12 @@ class StatisticsChart extends LitElement { @property({ type: Boolean }) public isLoadingData = false; - @state() private _chartData?: ChartData; + @state() private _chartData: ChartData = { datasets: [] }; @state() private _chartOptions?: ChartOptions; protected shouldUpdate(changedProps: PropertyValues): boolean { - return !(changedProps.size === 1 && changedProps.has("hass")); + return changedProps.size > 1 || !changedProps.has("hass"); } public willUpdate(changedProps: PropertyValues) { @@ -127,20 +136,31 @@ class StatisticsChart extends LitElement { ticks: { maxTicksLimit: 7, }, + title: { + display: this.unit, + text: this.unit, + }, }, }, plugins: { tooltip: { mode: "nearest", callbacks: { - label: (context) => `${context.dataset.label}: ${context.parsed.y}`, + label: (context) => + `${context.dataset.label}: ${formatNumber( + context.parsed.y, + this.hass.locale + )} ${ + // @ts-ignore + context.dataset.unit || "" + }`, }, }, filler: { propagate: true, }, legend: { - display: false, + display: true, labels: { usePointStyle: true, }, @@ -154,6 +174,7 @@ class StatisticsChart extends LitElement { tension: 0.4, borderWidth: 1.5, }, + bar: { borderWidth: 1.5, borderRadius: 4 }, point: { hitRadius: 5, }, @@ -163,10 +184,19 @@ class StatisticsChart extends LitElement { }; } - private _generateData() { + private async _getStatisticIds() { + this.statisticIds = await getStatisticIds(this.hass); + } + + private async _generateData() { if (!this.statisticsData) { return; } + + if (!this.statisticIds) { + await this._getStatisticIds(); + } + let colorIndex = 0; const statisticsData = Object.values(this.statisticsData); const totalDataSets: ChartDataset<"line">[] = []; @@ -191,6 +221,8 @@ class StatisticsChart extends LitElement { endTime = new Date(); } + let unit: string | undefined | null; + const names = this.names || {}; statisticsData.forEach((stats) => { const firstStat = stats[0]; @@ -203,6 +235,19 @@ class StatisticsChart extends LitElement { name = firstStat.statistic_id; } } + + const meta = this.statisticIds!.find( + (stat) => stat.statistic_id === firstStat.statistic_id + ); + + if (!this.unit) { + if (unit === undefined) { + unit = meta?.unit_of_measurement; + } else if (unit !== meta?.unit_of_measurement) { + unit = null; + } + } + // array containing [value1, value2, etc] let prevValues: Array | null = null; @@ -237,59 +282,47 @@ class StatisticsChart extends LitElement { const color = getColorByIndex(colorIndex); colorIndex++; - const addDataSet = ( - nameY: string, - borderColor: string, - backgroundColor: string, - step = false, - fill?: boolean | number | string - ) => { - statDataSets.push({ - label: nameY, - fill: fill || false, - borderColor, - backgroundColor: backgroundColor, - stepped: step ? "before" : false, - pointRadius: 0, - data: [], - }); - }; - const statTypes: this["statTypes"] = []; - 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"); + const sortedTypes = drawBands + ? [...this.statTypes].sort((a, b) => { + if (a === "min" || b === "max") { + return -1; + } + if (a === "max" || b === "min") { + return +1; + } + return 0; + }) + : this.statTypes; + sortedTypes.forEach((type) => { if (statisticsHaveType(stats, type)) { + const band = drawBands && (type === "min" || type === "max"); statTypes.push(type); - addDataSet( - `${name} (${this.hass.localize( + statDataSets.push({ + label: `${name} (${this.hass.localize( `ui.components.statistics_charts.statistic_types.${type}` - )})`, - drawBands && (type === "min" || type === "max") - ? color + "7F" - : color, - color + "7F", - false, - drawBands + )}) + `, + fill: drawBands ? type === "min" ? "+1" : type === "max" ? "-1" : false - : false - ); + : false, + borderColor: band ? color + "7F" : color, + backgroundColor: band ? color + "3F" : color + "7F", + pointRadius: 0, + data: [], + // @ts-ignore + unit: meta?.unit_of_measurement, + band, + }); } }); @@ -321,6 +354,19 @@ 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), + title: { display: unit, text: unit }, + }, + }, + }; + } + this._chartData = { datasets: totalDataSets, }; diff --git a/src/components/entity/ha-statistics-picker.ts b/src/components/entity/ha-statistics-picker.ts index 22a6593ffd..46e10060d0 100644 --- a/src/components/entity/ha-statistics-picker.ts +++ b/src/components/entity/ha-statistics-picker.ts @@ -27,9 +27,8 @@ class HaStatisticsPicker extends LitElement { return html``; } - const currentStatistics = this._currentStatistics; return html` - ${currentStatistics.map( + ${this._currentStatistics.map( (statisticId) => html`
{ this._entities.push(entity.entity); if (entity.name) {