Fix energy charts with leap years (#24059)

* Fix energy charts with leap years

* handle quarters
This commit is contained in:
Petar Petrov 2025-02-04 14:28:31 +02:00 committed by Bram Kragten
parent ce0f02a45b
commit 31180e3a9e
6 changed files with 87 additions and 30 deletions

View File

@ -1,5 +1,16 @@
import type { HassConfig } from "home-assistant-js-websocket"; 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 { import type {
BarSeriesOption, BarSeriesOption,
CallbackDataParams, CallbackDataParams,
@ -7,7 +18,10 @@ import type {
} from "echarts/types/dist/shared"; } from "echarts/types/dist/shared";
import type { FrontendLocaleData } from "../../../../../data/translation"; import type { FrontendLocaleData } from "../../../../../data/translation";
import { formatNumber } from "../../../../../common/number/format_number"; 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 { formatTime } from "../../../../../common/datetime/format_time";
import type { ECOption } from "../../../../../resources/echarts"; import type { ECOption } from "../../../../../resources/echarts";
@ -53,7 +67,7 @@ export function getCommonOptions(
xAxis: { xAxis: {
type: "time", type: "time",
min: start, min: start,
max: end, max: getSuggestedMax(dayDifference, end),
}, },
yAxis: { yAxis: {
type: "value", type: "value",
@ -88,7 +102,6 @@ export function getCommonOptions(
} }
}); });
return [mainItems, compareItems] return [mainItems, compareItems]
.filter((items) => items.length > 0)
.map((items) => .map((items) =>
formatTooltip( formatTooltip(
items, items,
@ -100,6 +113,7 @@ export function getCommonOptions(
formatTotal formatTotal
) )
) )
.filter(Boolean)
.join("<br><br>"); .join("<br><br>");
} }
return formatTooltip( return formatTooltip(
@ -126,14 +140,16 @@ function formatTooltip(
unit?: string, unit?: string,
formatTotal?: (total: number) => string formatTotal?: (total: number) => string
) { ) {
if (!params[0].value) { if (!params[0]?.value) {
return ""; return "";
} }
// when comparing the first value is offset to match the main period // when comparing the first value is offset to match the main period
// and the real date is in the third value // and the real date is in the third value
const date = new Date(params[0].value?.[2] ?? params[0].value?.[0]); const date = new Date(params[0].value?.[2] ?? params[0].value?.[0]);
let period: string; let period: string;
if (dayDifference > 0) { if (dayDifference > 89) {
period = `${formatDateMonthYear(date, locale, config)}`;
} else if (dayDifference > 0) {
period = `${formatDateVeryShort(date, locale, config)}`; period = `${formatDateVeryShort(date, locale, config)}`;
} else { } else {
period = `${ 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);
}

View File

@ -33,6 +33,7 @@ import { hasConfigChanged } from "../../common/has-changed";
import { import {
fillDataGapsAndRoundCaps, fillDataGapsAndRoundCaps,
getCommonOptions, getCommonOptions,
getCompareTransform,
} from "./common/energy-chart-options"; } from "./common/energy-chart-options";
import { storage } from "../../../../common/decorators/storage"; import { storage } from "../../../../common/decorators/storage";
import type { ECOption } from "../../../../resources/echarts"; import type { ECOption } from "../../../../resources/echarts";
@ -319,18 +320,19 @@ export class HuiEnergyDevicesDetailGraphCard
datapoint[1]; datapoint[1];
}); });
}); });
const compareOffset = compare const compareTransform = getCompareTransform(
? this._start.getTime() - this._compareStart!.getTime() this._start,
: 0; this._compareStart!
);
const untrackedConsumption: BarSeriesOption["data"] = []; const untrackedConsumption: BarSeriesOption["data"] = [];
Object.keys(consumptionData.total).forEach((time) => { Object.keys(consumptionData.total).forEach((time) => {
const value = const value =
consumptionData.total[time] - (totalDeviceConsumption[time] || 0); consumptionData.total[time] - (totalDeviceConsumption[time] || 0);
const dataPoint = [Number(time), value]; const dataPoint: (Date | string | number)[] = [time, value];
if (compare) { if (compare) {
dataPoint[2] = dataPoint[0]; dataPoint[2] = dataPoint[0];
dataPoint[0] += compareOffset; dataPoint[0] = compareTransform(new Date(time));
} }
untrackedConsumption.push(dataPoint); untrackedConsumption.push(dataPoint);
}); });

View File

@ -29,6 +29,7 @@ import { hasConfigChanged } from "../../common/has-changed";
import { import {
fillDataGapsAndRoundCaps, fillDataGapsAndRoundCaps,
getCommonOptions, getCommonOptions,
getCompareTransform,
} from "./common/energy-chart-options"; } from "./common/energy-chart-options";
import type { ECOption } from "../../../../resources/echarts"; import type { ECOption } from "../../../../resources/echarts";
@ -213,9 +214,10 @@ export class HuiEnergyGasGraphCard
compare = false compare = false
) { ) {
const data: BarSeriesOption[] = []; const data: BarSeriesOption[] = [];
const compareOffset = compare const compareTransform = getCompareTransform(
? this._start.getTime() - this._compareStart!.getTime() this._start,
: 0; this._compareStart!
);
gasSources.forEach((source, idx) => { gasSources.forEach((source, idx) => {
let prevStart: number | null = null; let prevStart: number | null = null;
@ -236,10 +238,13 @@ export class HuiEnergyGasGraphCard
if (prevStart === point.start) { if (prevStart === point.start) {
continue; continue;
} }
const dataPoint = [point.start, point.change]; const dataPoint: (Date | string | number)[] = [
point.start,
point.change,
];
if (compare) { if (compare) {
dataPoint[2] = dataPoint[0]; dataPoint[2] = dataPoint[0];
dataPoint[0] += compareOffset; dataPoint[0] = compareTransform(new Date(point.start));
} }
gasConsumptionData.push(dataPoint); gasConsumptionData.push(dataPoint);
prevStart = point.start; prevStart = point.start;

View File

@ -30,6 +30,7 @@ import { hasConfigChanged } from "../../common/has-changed";
import { import {
fillDataGapsAndRoundCaps, fillDataGapsAndRoundCaps,
getCommonOptions, getCommonOptions,
getCompareTransform,
} from "./common/energy-chart-options"; } from "./common/energy-chart-options";
import type { ECOption } from "../../../../resources/echarts"; import type { ECOption } from "../../../../resources/echarts";
@ -231,9 +232,10 @@ export class HuiEnergySolarGraphCard
compare = false compare = false
) { ) {
const data: BarSeriesOption[] = []; const data: BarSeriesOption[] = [];
const compareOffset = compare const compareTransform = getCompareTransform(
? this._start.getTime() - this._compareStart!.getTime() this._start,
: 0; this._compareStart!
);
solarSources.forEach((source, idx) => { solarSources.forEach((source, idx) => {
let prevStart: number | null = null; let prevStart: number | null = null;
@ -255,10 +257,13 @@ export class HuiEnergySolarGraphCard
if (prevStart === point.start) { if (prevStart === point.start) {
continue; continue;
} }
const dataPoint = [point.start, point.change]; const dataPoint: (Date | string | number)[] = [
point.start,
point.change,
];
if (compare) { if (compare) {
dataPoint[2] = dataPoint[0]; dataPoint[2] = dataPoint[0];
dataPoint[0] += compareOffset; dataPoint[0] = compareTransform(new Date(point.start));
} }
solarProductionData.push(dataPoint); solarProductionData.push(dataPoint);
prevStart = point.start; prevStart = point.start;

View File

@ -27,6 +27,7 @@ import { hasConfigChanged } from "../../common/has-changed";
import { import {
fillDataGapsAndRoundCaps, fillDataGapsAndRoundCaps,
getCommonOptions, getCommonOptions,
getCompareTransform,
} from "./common/energy-chart-options"; } from "./common/energy-chart-options";
import type { ECOption } from "../../../../resources/echarts"; import type { ECOption } from "../../../../resources/echarts";
@ -476,9 +477,10 @@ export class HuiEnergyUsageGraphCard
(a, b) => Number(a) - Number(b) (a, b) => Number(a) - Number(b)
); );
const compareOffset = compare const compareTransform = getCompareTransform(
? this._start.getTime() - this._compareStart!.getTime() this._start,
: 0; this._compareStart!
);
Object.entries(combinedData).forEach(([type, sources]) => { Object.entries(combinedData).forEach(([type, sources]) => {
Object.entries(sources).forEach(([statId, source]) => { Object.entries(sources).forEach(([statId, source]) => {
@ -494,7 +496,7 @@ export class HuiEnergyUsageGraphCard
]; ];
if (compare) { if (compare) {
dataPoint[2] = dataPoint[0]; dataPoint[2] = dataPoint[0];
dataPoint[0] += compareOffset; dataPoint[0] = compareTransform(dataPoint[0]);
} }
points.push(dataPoint); points.push(dataPoint);
} }

View File

@ -28,6 +28,7 @@ import { hasConfigChanged } from "../../common/has-changed";
import { import {
fillDataGapsAndRoundCaps, fillDataGapsAndRoundCaps,
getCommonOptions, getCommonOptions,
getCompareTransform,
} from "./common/energy-chart-options"; } from "./common/energy-chart-options";
import type { ECOption } from "../../../../resources/echarts"; import type { ECOption } from "../../../../resources/echarts";
import { formatNumber } from "../../../../common/number/format_number"; import { formatNumber } from "../../../../common/number/format_number";
@ -211,9 +212,10 @@ export class HuiEnergyWaterGraphCard
compare = false compare = false
) { ) {
const data: BarSeriesOption[] = []; const data: BarSeriesOption[] = [];
const compareOffset = compare const compareTransform = getCompareTransform(
? this._start.getTime() - this._compareStart!.getTime() this._start,
: 0; this._compareStart!
);
waterSources.forEach((source, idx) => { waterSources.forEach((source, idx) => {
let prevStart: number | null = null; let prevStart: number | null = null;
@ -234,10 +236,13 @@ export class HuiEnergyWaterGraphCard
if (prevStart === point.start) { if (prevStart === point.start) {
continue; continue;
} }
const dataPoint = [point.start, point.change]; const dataPoint: (Date | string | number)[] = [
point.start,
point.change,
];
if (compare) { if (compare) {
dataPoint[2] = dataPoint[0]; dataPoint[2] = dataPoint[0];
dataPoint[0] += compareOffset; dataPoint[0] = compareTransform(new Date(point.start));
} }
waterConsumptionData.push(dataPoint); waterConsumptionData.push(dataPoint);
prevStart = point.start; prevStart = point.start;