From dd98ec771db384ffd0c2774607d91de9826e92ba Mon Sep 17 00:00:00 2001 From: karwosts <32912880+karwosts@users.noreply.github.com> Date: Wed, 28 Feb 2024 10:20:56 -0500 Subject: [PATCH] Infer a limited history chart from state object (#19176) * Infer a limited history chart from state object * bugfix * bugfix * minor fixes from feedback --- .../chart/state-history-chart-line.ts | 2 +- .../chart/state-history-chart-timeline.ts | 2 +- src/components/chart/state-history-charts.ts | 34 ++++++++++++++----- .../timeline-chart/timeline-controller.ts | 4 ++- .../chart/timeline-chart/timeline-scale.ts | 4 +-- src/data/history.ts | 28 ++++++++++++--- src/dialogs/more-info/ha-more-info-history.ts | 1 + src/panels/history/ha-panel-history.ts | 2 ++ .../lovelace/cards/hui-history-graph-card.ts | 1 + 9 files changed, 60 insertions(+), 18 deletions(-) diff --git a/src/components/chart/state-history-chart-line.ts b/src/components/chart/state-history-chart-line.ts index 5f6286ecef61..0923fb0702df 100644 --- a/src/components/chart/state-history-chart-line.ts +++ b/src/components/chart/state-history-chart-line.ts @@ -111,7 +111,7 @@ export class StateHistoryChartLine extends LitElement { config: this.hass.config, }, }, - suggestedMin: this.startTime, + min: this.startTime, suggestedMax: this.endTime, ticks: { maxRotation: 0, diff --git a/src/components/chart/state-history-chart-timeline.ts b/src/components/chart/state-history-chart-timeline.ts index 8bfb0fef8b7f..c70711ae64a0 100644 --- a/src/components/chart/state-history-chart-timeline.ts +++ b/src/components/chart/state-history-chart-timeline.ts @@ -114,7 +114,7 @@ export class StateHistoryChartTimeline extends LitElement { config: this.hass.config, }, }, - suggestedMin: this.startTime, + min: this.startTime, suggestedMax: this.endTime, ticks: { autoSkip: true, diff --git a/src/components/chart/state-history-charts.ts b/src/components/chart/state-history-charts.ts index 7f72aa5f2b3e..eb47c5b31a74 100644 --- a/src/components/chart/state-history-charts.ts +++ b/src/components/chart/state-history-charts.ts @@ -233,16 +233,32 @@ export class StateHistoryCharts extends LitElement { new Date().getTime() - 60 * 60 * this.hoursToShow * 1000 ); } else { - this._computedStartTime = new Date( - (this.historyData?.timeline ?? []).reduce( - (minTime, stateInfo) => - Math.min( - minTime, - new Date(stateInfo.data[0].last_changed).getTime() - ), - new Date().getTime() - ) + let minTimeAll = (this.historyData?.timeline ?? []).reduce( + (minTime, stateInfo) => + Math.min( + minTime, + new Date(stateInfo.data[0].last_changed).getTime() + ), + new Date().getTime() + ); + + minTimeAll = (this.historyData?.line ?? []).reduce( + (minTimeLine, line) => + Math.min( + minTimeLine, + line.data.reduce( + (minTimeData, data) => + Math.min( + minTimeData, + new Date(data.states[0].last_changed).getTime() + ), + minTimeLine + ) + ), + minTimeAll ); + + this._computedStartTime = new Date(minTimeAll); } } } diff --git a/src/components/chart/timeline-chart/timeline-controller.ts b/src/components/chart/timeline-chart/timeline-controller.ts index 5aeadc6e0049..b6b1d88af31c 100644 --- a/src/components/chart/timeline-chart/timeline-controller.ts +++ b/src/components/chart/timeline-chart/timeline-controller.ts @@ -205,7 +205,9 @@ export class TimelineController extends BarController { const y = vScale.getPixelForValue(this.index); - const xStart = iScale.getPixelForValue(data.start.getTime()); + const xStart = iScale.getPixelForValue( + Math.max(iScale.min, data.start.getTime()) + ); const xEnd = iScale.getPixelForValue(data.end.getTime()); const width = xEnd - xStart; diff --git a/src/components/chart/timeline-chart/timeline-scale.ts b/src/components/chart/timeline-chart/timeline-scale.ts index 8d5086dafc34..e987456f8e31 100644 --- a/src/components/chart/timeline-chart/timeline-scale.ts +++ b/src/components/chart/timeline-chart/timeline-scale.ts @@ -49,7 +49,7 @@ export class TimeLineScale extends TimeScale { max = isFinite(max) && !isNaN(max) ? max : +adapter.endOf(Date.now(), unit); // Make sure that max is strictly higher than min (required by the lookup table) - this.min = Math.min(min, max - 1); - this.max = Math.max(min + 1, max); + this.min = adapter.parse(options.min, this) ?? Math.min(min, max - 1); + this.max = adapter.parse(options.max, this) ?? Math.max(min + 1, max); } } diff --git a/src/data/history.ts b/src/data/history.ts index baece3da3789..e0fc8bd328cb 100644 --- a/src/data/history.ts +++ b/src/data/history.ts @@ -81,7 +81,7 @@ export interface EntityHistoryState { /** attributes */ a: { [key: string]: any }; /** last_changed; if set, also applies to lu */ - lc: number; + lc?: number; /** last_updated */ lu: number; } @@ -419,17 +419,37 @@ const BLANK_UNIT = " "; export const computeHistory = ( hass: HomeAssistant, stateHistory: HistoryStates, + entityIds: string[], localize: LocalizeFunc, sensorNumericalDeviceClasses: string[], splitDeviceClasses = false ): HistoryResult => { const lineChartDevices: { [unit: string]: HistoryStates } = {}; const timelineDevices: TimelineEntity[] = []; - if (!stateHistory) { + + const localStateHistory: HistoryStates = {}; + + // Create a limited history from stateObj if entity has no recorded history. + const allEntities = new Set([...entityIds, ...Object.keys(stateHistory)]); + allEntities.forEach((entity) => { + if (entity in stateHistory) { + localStateHistory[entity] = stateHistory[entity]; + } else if (hass.states[entity]) { + localStateHistory[entity] = [ + { + s: hass.states[entity].state, + a: hass.states[entity].attributes, + lu: new Date(hass.states[entity].last_updated).getTime() / 1000, + }, + ]; + } + }); + + if (!localStateHistory) { return { line: [], timeline: [] }; } - Object.keys(stateHistory).forEach((entityId) => { - const stateInfo = stateHistory[entityId]; + Object.keys(localStateHistory).forEach((entityId) => { + const stateInfo = localStateHistory[entityId]; if (stateInfo.length === 0) { return; } diff --git a/src/dialogs/more-info/ha-more-info-history.ts b/src/dialogs/more-info/ha-more-info-history.ts index 2adeb515c4ca..dc5c32f8a10b 100644 --- a/src/dialogs/more-info/ha-more-info-history.ts +++ b/src/dialogs/more-info/ha-more-info-history.ts @@ -228,6 +228,7 @@ export class MoreInfoHistory extends LitElement { this._stateHistory = computeHistory( this.hass!, combinedHistory, + [this.entityId], this.hass!.localize, sensorNumericDeviceClasses ); diff --git a/src/panels/history/ha-panel-history.ts b/src/panels/history/ha-panel-history.ts index dae3bae72565..49797dda56ee 100644 --- a/src/panels/history/ha-panel-history.ts +++ b/src/panels/history/ha-panel-history.ts @@ -433,6 +433,7 @@ class HaPanelHistory extends SubscribeMixin(LitElement) { this._statisticsHistory = computeHistory( this.hass, statsHistoryStates, + [], this.hass.localize, sensorNumericDeviceClasses, true @@ -472,6 +473,7 @@ class HaPanelHistory extends SubscribeMixin(LitElement) { this._stateHistory = computeHistory( this.hass, history, + entityIds, this.hass.localize, sensorNumericDeviceClasses, true diff --git a/src/panels/lovelace/cards/hui-history-graph-card.ts b/src/panels/lovelace/cards/hui-history-graph-card.ts index 1229ff71906d..d8af4508b7bc 100644 --- a/src/panels/lovelace/cards/hui-history-graph-card.ts +++ b/src/panels/lovelace/cards/hui-history-graph-card.ts @@ -120,6 +120,7 @@ export class HuiHistoryGraphCard extends LitElement implements LovelaceCard { this._stateHistory = computeHistory( this.hass!, combinedHistory, + this._entityIds, this.hass!.localize, sensorNumericDeviceClasses, this._config?.split_device_classes