From 31180e3a9e6172c5582e53d47a8e85ef4c34170c Mon Sep 17 00:00:00 2001 From: Petar Petrov Date: Tue, 4 Feb 2025 14:28:31 +0200 Subject: [PATCH] Fix energy charts with leap years (#24059) * Fix energy charts with leap years * handle quarters --- .../energy/common/energy-chart-options.ts | 50 ++++++++++++++++--- .../hui-energy-devices-detail-graph-card.ts | 12 +++-- .../cards/energy/hui-energy-gas-graph-card.ts | 15 ++++-- .../energy/hui-energy-solar-graph-card.ts | 15 ++++-- .../energy/hui-energy-usage-graph-card.ts | 10 ++-- .../energy/hui-energy-water-graph-card.ts | 15 ++++-- 6 files changed, 87 insertions(+), 30 deletions(-) diff --git a/src/panels/lovelace/cards/energy/common/energy-chart-options.ts b/src/panels/lovelace/cards/energy/common/energy-chart-options.ts index 76dde725aa..943ade0bb5 100644 --- a/src/panels/lovelace/cards/energy/common/energy-chart-options.ts +++ b/src/panels/lovelace/cards/energy/common/energy-chart-options.ts @@ -1,5 +1,16 @@ import type { HassConfig } from "home-assistant-js-websocket"; -import { addHours, subHours, differenceInDays } from "date-fns"; +import { + differenceInMonths, + subHours, + differenceInDays, + differenceInYears, + startOfYear, + addMilliseconds, + startOfMonth, + addYears, + addMonths, + addHours, +} from "date-fns"; import type { BarSeriesOption, CallbackDataParams, @@ -7,7 +18,10 @@ import type { } from "echarts/types/dist/shared"; import type { FrontendLocaleData } from "../../../../../data/translation"; import { formatNumber } from "../../../../../common/number/format_number"; -import { formatDateVeryShort } from "../../../../../common/datetime/format_date"; +import { + formatDateMonthYear, + formatDateVeryShort, +} from "../../../../../common/datetime/format_date"; import { formatTime } from "../../../../../common/datetime/format_time"; import type { ECOption } from "../../../../../resources/echarts"; @@ -53,7 +67,7 @@ export function getCommonOptions( xAxis: { type: "time", min: start, - max: end, + max: getSuggestedMax(dayDifference, end), }, yAxis: { type: "value", @@ -88,7 +102,6 @@ export function getCommonOptions( } }); return [mainItems, compareItems] - .filter((items) => items.length > 0) .map((items) => formatTooltip( items, @@ -100,6 +113,7 @@ export function getCommonOptions( formatTotal ) ) + .filter(Boolean) .join("

"); } return formatTooltip( @@ -126,14 +140,16 @@ function formatTooltip( unit?: string, formatTotal?: (total: number) => string ) { - if (!params[0].value) { + if (!params[0]?.value) { return ""; } // when comparing the first value is offset to match the main period // and the real date is in the third value const date = new Date(params[0].value?.[2] ?? params[0].value?.[0]); let period: string; - if (dayDifference > 0) { + if (dayDifference > 89) { + period = `${formatDateMonthYear(date, locale, config)}`; + } else if (dayDifference > 0) { period = `${formatDateVeryShort(date, locale, config)}`; } else { period = `${ @@ -242,3 +258,25 @@ export function fillDataGapsAndRoundCaps(datasets: BarSeriesOption[]) { } }); } + +export function getCompareTransform(start: Date, compareStart?: Date) { + if (!compareStart) { + return (ts: Date) => ts; + } + const compareYearDiff = differenceInYears(start, compareStart); + if ( + compareYearDiff !== 0 && + start.getTime() === startOfYear(start).getTime() + ) { + return (ts: Date) => addYears(ts, compareYearDiff); + } + const compareMonthDiff = differenceInMonths(start, compareStart); + if ( + compareMonthDiff !== 0 && + start.getTime() === startOfMonth(start).getTime() + ) { + return (ts: Date) => addMonths(ts, compareMonthDiff); + } + const compareOffset = start.getTime() - compareStart.getTime(); + return (ts: Date) => addMilliseconds(ts, compareOffset); +} diff --git a/src/panels/lovelace/cards/energy/hui-energy-devices-detail-graph-card.ts b/src/panels/lovelace/cards/energy/hui-energy-devices-detail-graph-card.ts index e0d2db9e60..8a609fe507 100644 --- a/src/panels/lovelace/cards/energy/hui-energy-devices-detail-graph-card.ts +++ b/src/panels/lovelace/cards/energy/hui-energy-devices-detail-graph-card.ts @@ -33,6 +33,7 @@ import { hasConfigChanged } from "../../common/has-changed"; import { fillDataGapsAndRoundCaps, getCommonOptions, + getCompareTransform, } from "./common/energy-chart-options"; import { storage } from "../../../../common/decorators/storage"; import type { ECOption } from "../../../../resources/echarts"; @@ -319,18 +320,19 @@ export class HuiEnergyDevicesDetailGraphCard datapoint[1]; }); }); - const compareOffset = compare - ? this._start.getTime() - this._compareStart!.getTime() - : 0; + const compareTransform = getCompareTransform( + this._start, + this._compareStart! + ); const untrackedConsumption: BarSeriesOption["data"] = []; Object.keys(consumptionData.total).forEach((time) => { const value = consumptionData.total[time] - (totalDeviceConsumption[time] || 0); - const dataPoint = [Number(time), value]; + const dataPoint: (Date | string | number)[] = [time, value]; if (compare) { dataPoint[2] = dataPoint[0]; - dataPoint[0] += compareOffset; + dataPoint[0] = compareTransform(new Date(time)); } untrackedConsumption.push(dataPoint); }); diff --git a/src/panels/lovelace/cards/energy/hui-energy-gas-graph-card.ts b/src/panels/lovelace/cards/energy/hui-energy-gas-graph-card.ts index 0c19285d70..6612a58c8f 100644 --- a/src/panels/lovelace/cards/energy/hui-energy-gas-graph-card.ts +++ b/src/panels/lovelace/cards/energy/hui-energy-gas-graph-card.ts @@ -29,6 +29,7 @@ import { hasConfigChanged } from "../../common/has-changed"; import { fillDataGapsAndRoundCaps, getCommonOptions, + getCompareTransform, } from "./common/energy-chart-options"; import type { ECOption } from "../../../../resources/echarts"; @@ -213,9 +214,10 @@ export class HuiEnergyGasGraphCard compare = false ) { const data: BarSeriesOption[] = []; - const compareOffset = compare - ? this._start.getTime() - this._compareStart!.getTime() - : 0; + const compareTransform = getCompareTransform( + this._start, + this._compareStart! + ); gasSources.forEach((source, idx) => { let prevStart: number | null = null; @@ -236,10 +238,13 @@ export class HuiEnergyGasGraphCard if (prevStart === point.start) { continue; } - const dataPoint = [point.start, point.change]; + const dataPoint: (Date | string | number)[] = [ + point.start, + point.change, + ]; if (compare) { dataPoint[2] = dataPoint[0]; - dataPoint[0] += compareOffset; + dataPoint[0] = compareTransform(new Date(point.start)); } gasConsumptionData.push(dataPoint); prevStart = point.start; diff --git a/src/panels/lovelace/cards/energy/hui-energy-solar-graph-card.ts b/src/panels/lovelace/cards/energy/hui-energy-solar-graph-card.ts index e679022a1b..e1ffa869f0 100644 --- a/src/panels/lovelace/cards/energy/hui-energy-solar-graph-card.ts +++ b/src/panels/lovelace/cards/energy/hui-energy-solar-graph-card.ts @@ -30,6 +30,7 @@ import { hasConfigChanged } from "../../common/has-changed"; import { fillDataGapsAndRoundCaps, getCommonOptions, + getCompareTransform, } from "./common/energy-chart-options"; import type { ECOption } from "../../../../resources/echarts"; @@ -231,9 +232,10 @@ export class HuiEnergySolarGraphCard compare = false ) { const data: BarSeriesOption[] = []; - const compareOffset = compare - ? this._start.getTime() - this._compareStart!.getTime() - : 0; + const compareTransform = getCompareTransform( + this._start, + this._compareStart! + ); solarSources.forEach((source, idx) => { let prevStart: number | null = null; @@ -255,10 +257,13 @@ export class HuiEnergySolarGraphCard if (prevStart === point.start) { continue; } - const dataPoint = [point.start, point.change]; + const dataPoint: (Date | string | number)[] = [ + point.start, + point.change, + ]; if (compare) { dataPoint[2] = dataPoint[0]; - dataPoint[0] += compareOffset; + dataPoint[0] = compareTransform(new Date(point.start)); } solarProductionData.push(dataPoint); prevStart = point.start; diff --git a/src/panels/lovelace/cards/energy/hui-energy-usage-graph-card.ts b/src/panels/lovelace/cards/energy/hui-energy-usage-graph-card.ts index 74fc5dc83f..9836c7f061 100644 --- a/src/panels/lovelace/cards/energy/hui-energy-usage-graph-card.ts +++ b/src/panels/lovelace/cards/energy/hui-energy-usage-graph-card.ts @@ -27,6 +27,7 @@ import { hasConfigChanged } from "../../common/has-changed"; import { fillDataGapsAndRoundCaps, getCommonOptions, + getCompareTransform, } from "./common/energy-chart-options"; import type { ECOption } from "../../../../resources/echarts"; @@ -476,9 +477,10 @@ export class HuiEnergyUsageGraphCard (a, b) => Number(a) - Number(b) ); - const compareOffset = compare - ? this._start.getTime() - this._compareStart!.getTime() - : 0; + const compareTransform = getCompareTransform( + this._start, + this._compareStart! + ); Object.entries(combinedData).forEach(([type, sources]) => { Object.entries(sources).forEach(([statId, source]) => { @@ -494,7 +496,7 @@ export class HuiEnergyUsageGraphCard ]; if (compare) { dataPoint[2] = dataPoint[0]; - dataPoint[0] += compareOffset; + dataPoint[0] = compareTransform(dataPoint[0]); } points.push(dataPoint); } diff --git a/src/panels/lovelace/cards/energy/hui-energy-water-graph-card.ts b/src/panels/lovelace/cards/energy/hui-energy-water-graph-card.ts index 919bd06ed1..adfc3d217a 100644 --- a/src/panels/lovelace/cards/energy/hui-energy-water-graph-card.ts +++ b/src/panels/lovelace/cards/energy/hui-energy-water-graph-card.ts @@ -28,6 +28,7 @@ import { hasConfigChanged } from "../../common/has-changed"; import { fillDataGapsAndRoundCaps, getCommonOptions, + getCompareTransform, } from "./common/energy-chart-options"; import type { ECOption } from "../../../../resources/echarts"; import { formatNumber } from "../../../../common/number/format_number"; @@ -211,9 +212,10 @@ export class HuiEnergyWaterGraphCard compare = false ) { const data: BarSeriesOption[] = []; - const compareOffset = compare - ? this._start.getTime() - this._compareStart!.getTime() - : 0; + const compareTransform = getCompareTransform( + this._start, + this._compareStart! + ); waterSources.forEach((source, idx) => { let prevStart: number | null = null; @@ -234,10 +236,13 @@ export class HuiEnergyWaterGraphCard if (prevStart === point.start) { continue; } - const dataPoint = [point.start, point.change]; + const dataPoint: (Date | string | number)[] = [ + point.start, + point.change, + ]; if (compare) { dataPoint[2] = dataPoint[0]; - dataPoint[0] += compareOffset; + dataPoint[0] = compareTransform(new Date(point.start)); } waterConsumptionData.push(dataPoint); prevStart = point.start;