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

View File

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

View File

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

View File

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

View File

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

View File

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