mirror of
https://github.com/home-assistant/frontend.git
synced 2025-07-19 07:16:39 +00:00
Add period selection to energy dashboard (#9756)
This commit is contained in:
parent
3897e3d452
commit
dc50e54afc
@ -1,4 +1,4 @@
|
|||||||
import { addHours, differenceInHours } from "date-fns";
|
import { addHours, differenceInHours, endOfDay } from "date-fns";
|
||||||
import { HassEntity } from "home-assistant-js-websocket";
|
import { HassEntity } from "home-assistant-js-websocket";
|
||||||
import { StatisticValue } from "../../../src/data/history";
|
import { StatisticValue } from "../../../src/data/history";
|
||||||
import { MockHomeAssistant } from "../../../src/fake_data/provide_hass";
|
import { MockHomeAssistant } from "../../../src/fake_data/provide_hass";
|
||||||
@ -222,6 +222,7 @@ const statisticsFunctions: Record<
|
|||||||
"sensor.energy_production_tarif_2": (id, start, end) => {
|
"sensor.energy_production_tarif_2": (id, start, end) => {
|
||||||
const productionStart = new Date(start.getTime() + 9 * 60 * 60 * 1000);
|
const productionStart = new Date(start.getTime() + 9 * 60 * 60 * 1000);
|
||||||
const productionEnd = new Date(start.getTime() + 21 * 60 * 60 * 1000);
|
const productionEnd = new Date(start.getTime() + 21 * 60 * 60 * 1000);
|
||||||
|
const dayEnd = new Date(endOfDay(productionEnd));
|
||||||
const production = generateCurvedStatistics(
|
const production = generateCurvedStatistics(
|
||||||
id,
|
id,
|
||||||
productionStart,
|
productionStart,
|
||||||
@ -237,15 +238,17 @@ const statisticsFunctions: Record<
|
|||||||
const evening = generateSumStatistics(
|
const evening = generateSumStatistics(
|
||||||
id,
|
id,
|
||||||
productionEnd,
|
productionEnd,
|
||||||
end,
|
dayEnd,
|
||||||
productionFinalVal,
|
productionFinalVal,
|
||||||
0
|
0
|
||||||
);
|
);
|
||||||
return [...morning, ...production, ...evening];
|
const rest = generateSumStatistics(id, dayEnd, end, productionFinalVal, 1);
|
||||||
|
return [...morning, ...production, ...evening, ...rest];
|
||||||
},
|
},
|
||||||
"sensor.solar_production": (id, start, end) => {
|
"sensor.solar_production": (id, start, end) => {
|
||||||
const productionStart = new Date(start.getTime() + 7 * 60 * 60 * 1000);
|
const productionStart = new Date(start.getTime() + 7 * 60 * 60 * 1000);
|
||||||
const productionEnd = new Date(start.getTime() + 23 * 60 * 60 * 1000);
|
const productionEnd = new Date(start.getTime() + 23 * 60 * 60 * 1000);
|
||||||
|
const dayEnd = new Date(endOfDay(productionEnd));
|
||||||
const production = generateCurvedStatistics(
|
const production = generateCurvedStatistics(
|
||||||
id,
|
id,
|
||||||
productionStart,
|
productionStart,
|
||||||
@ -261,11 +264,12 @@ const statisticsFunctions: Record<
|
|||||||
const evening = generateSumStatistics(
|
const evening = generateSumStatistics(
|
||||||
id,
|
id,
|
||||||
productionEnd,
|
productionEnd,
|
||||||
end,
|
dayEnd,
|
||||||
productionFinalVal,
|
productionFinalVal,
|
||||||
0
|
0
|
||||||
);
|
);
|
||||||
return [...morning, ...production, ...evening];
|
const rest = generateSumStatistics(id, dayEnd, end, productionFinalVal, 2);
|
||||||
|
return [...morning, ...production, ...evening, ...rest];
|
||||||
},
|
},
|
||||||
"sensor.grid_fossil_fuel_percentage": (id, start, end) =>
|
"sensor.grid_fossil_fuel_percentage": (id, start, end) =>
|
||||||
generateMeanStatistics(id, start, end, 35, 1.3),
|
generateMeanStatistics(id, start, end, 35, 1.3),
|
||||||
|
@ -17,7 +17,7 @@
|
|||||||
"lint": "yarn run lint:eslint && yarn run lint:prettier && yarn run lint:types",
|
"lint": "yarn run lint:eslint && yarn run lint:prettier && yarn run lint:types",
|
||||||
"format": "yarn run format:eslint && yarn run format:prettier",
|
"format": "yarn run format:eslint && yarn run format:prettier",
|
||||||
"mocha": "ts-mocha -p test-mocha/tsconfig.test.json \"test-mocha/**/*.ts\"",
|
"mocha": "ts-mocha -p test-mocha/tsconfig.test.json \"test-mocha/**/*.ts\"",
|
||||||
"test": "yarn run lint && yarn run mocha"
|
"test": "yarn run mocha"
|
||||||
},
|
},
|
||||||
"author": "Paulus Schoutsen <Paulus@PaulusSchoutsen.nl> (http://paulusschoutsen.nl)",
|
"author": "Paulus Schoutsen <Paulus@PaulusSchoutsen.nl> (http://paulusschoutsen.nl)",
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
|
@ -3,33 +3,11 @@ import memoizeOne from "memoize-one";
|
|||||||
import { FrontendLocaleData } from "../../data/translation";
|
import { FrontendLocaleData } from "../../data/translation";
|
||||||
import { toLocaleDateStringSupportsOptions } from "./check_options_support";
|
import { toLocaleDateStringSupportsOptions } from "./check_options_support";
|
||||||
|
|
||||||
const formatDateMem = memoizeOne(
|
// Tuesday, August 10
|
||||||
(locale: FrontendLocaleData) =>
|
export const formatDateWeekday = toLocaleDateStringSupportsOptions
|
||||||
new Intl.DateTimeFormat(locale.language, {
|
|
||||||
year: "numeric",
|
|
||||||
month: "long",
|
|
||||||
day: "numeric",
|
|
||||||
})
|
|
||||||
);
|
|
||||||
|
|
||||||
export const formatDate = toLocaleDateStringSupportsOptions
|
|
||||||
? (dateObj: Date, locale: FrontendLocaleData) =>
|
? (dateObj: Date, locale: FrontendLocaleData) =>
|
||||||
formatDateMem(locale).format(dateObj)
|
formatDateWeekdayMem(locale).format(dateObj)
|
||||||
: (dateObj: Date) => format(dateObj, "longDate");
|
: (dateObj: Date) => format(dateObj, "dddd, MMMM D");
|
||||||
|
|
||||||
const formatDateShortMem = memoizeOne(
|
|
||||||
(locale: FrontendLocaleData) =>
|
|
||||||
new Intl.DateTimeFormat(locale.language, {
|
|
||||||
day: "numeric",
|
|
||||||
month: "short",
|
|
||||||
})
|
|
||||||
);
|
|
||||||
|
|
||||||
export const formatDateShort = toLocaleDateStringSupportsOptions
|
|
||||||
? (dateObj: Date, locale: FrontendLocaleData) =>
|
|
||||||
formatDateShortMem(locale).format(dateObj)
|
|
||||||
: (dateObj: Date) => format(dateObj, "shortDate");
|
|
||||||
|
|
||||||
const formatDateWeekdayMem = memoizeOne(
|
const formatDateWeekdayMem = memoizeOne(
|
||||||
(locale: FrontendLocaleData) =>
|
(locale: FrontendLocaleData) =>
|
||||||
new Intl.DateTimeFormat(locale.language, {
|
new Intl.DateTimeFormat(locale.language, {
|
||||||
@ -39,7 +17,80 @@ const formatDateWeekdayMem = memoizeOne(
|
|||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
export const formatDateWeekday = toLocaleDateStringSupportsOptions
|
// August 10, 2021
|
||||||
|
export const formatDate = toLocaleDateStringSupportsOptions
|
||||||
? (dateObj: Date, locale: FrontendLocaleData) =>
|
? (dateObj: Date, locale: FrontendLocaleData) =>
|
||||||
formatDateWeekdayMem(locale).format(dateObj)
|
formatDateMem(locale).format(dateObj)
|
||||||
: (dateObj: Date) => format(dateObj, "dddd, MMM D");
|
: (dateObj: Date) => format(dateObj, "MMMM D, YYYY");
|
||||||
|
const formatDateMem = memoizeOne(
|
||||||
|
(locale: FrontendLocaleData) =>
|
||||||
|
new Intl.DateTimeFormat(locale.language, {
|
||||||
|
year: "numeric",
|
||||||
|
month: "long",
|
||||||
|
day: "numeric",
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
// 10/08/2021
|
||||||
|
export const formatDateNumeric = toLocaleDateStringSupportsOptions
|
||||||
|
? (dateObj: Date, locale: FrontendLocaleData) =>
|
||||||
|
formatDateNumericMem(locale).format(dateObj)
|
||||||
|
: (dateObj: Date) => format(dateObj, "M/D/YYYY");
|
||||||
|
const formatDateNumericMem = memoizeOne(
|
||||||
|
(locale: FrontendLocaleData) =>
|
||||||
|
new Intl.DateTimeFormat(locale.language, {
|
||||||
|
year: "numeric",
|
||||||
|
month: "numeric",
|
||||||
|
day: "numeric",
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
// Aug 10
|
||||||
|
export const formatDateShort = toLocaleDateStringSupportsOptions
|
||||||
|
? (dateObj: Date, locale: FrontendLocaleData) =>
|
||||||
|
formatDateShortMem(locale).format(dateObj)
|
||||||
|
: (dateObj: Date) => format(dateObj, "MMM D");
|
||||||
|
const formatDateShortMem = memoizeOne(
|
||||||
|
(locale: FrontendLocaleData) =>
|
||||||
|
new Intl.DateTimeFormat(locale.language, {
|
||||||
|
day: "numeric",
|
||||||
|
month: "short",
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
// August 2021
|
||||||
|
export const formatDateMonthYear = toLocaleDateStringSupportsOptions
|
||||||
|
? (dateObj: Date, locale: FrontendLocaleData) =>
|
||||||
|
formatDateMonthYearMem(locale).format(dateObj)
|
||||||
|
: (dateObj: Date) => format(dateObj, "MMMM YYYY");
|
||||||
|
const formatDateMonthYearMem = memoizeOne(
|
||||||
|
(locale: FrontendLocaleData) =>
|
||||||
|
new Intl.DateTimeFormat(locale.language, {
|
||||||
|
month: "long",
|
||||||
|
year: "numeric",
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
// August
|
||||||
|
export const formatDateMonth = toLocaleDateStringSupportsOptions
|
||||||
|
? (dateObj: Date, locale: FrontendLocaleData) =>
|
||||||
|
formatDateMonthMem(locale).format(dateObj)
|
||||||
|
: (dateObj: Date) => format(dateObj, "MMMM");
|
||||||
|
const formatDateMonthMem = memoizeOne(
|
||||||
|
(locale: FrontendLocaleData) =>
|
||||||
|
new Intl.DateTimeFormat(locale.language, {
|
||||||
|
month: "long",
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
// 2021
|
||||||
|
export const formatDateYear = toLocaleDateStringSupportsOptions
|
||||||
|
? (dateObj: Date, locale: FrontendLocaleData) =>
|
||||||
|
formatDateYearMem(locale).format(dateObj)
|
||||||
|
: (dateObj: Date) => format(dateObj, "YYYY");
|
||||||
|
const formatDateYearMem = memoizeOne(
|
||||||
|
(locale: FrontendLocaleData) =>
|
||||||
|
new Intl.DateTimeFormat(locale.language, {
|
||||||
|
year: "numeric",
|
||||||
|
})
|
||||||
|
);
|
||||||
|
@ -4,6 +4,12 @@ import { FrontendLocaleData } from "../../data/translation";
|
|||||||
import { toLocaleStringSupportsOptions } from "./check_options_support";
|
import { toLocaleStringSupportsOptions } from "./check_options_support";
|
||||||
import { useAmPm } from "./use_am_pm";
|
import { useAmPm } from "./use_am_pm";
|
||||||
|
|
||||||
|
// August 9, 2021, 8:23 AM
|
||||||
|
export const formatDateTime = toLocaleStringSupportsOptions
|
||||||
|
? (dateObj: Date, locale: FrontendLocaleData) =>
|
||||||
|
formatDateTimeMem(locale).format(dateObj)
|
||||||
|
: (dateObj: Date, locale: FrontendLocaleData) =>
|
||||||
|
format(dateObj, "MMMM D, YYYY, HH:mm" + useAmPm(locale) ? " A" : "");
|
||||||
const formatDateTimeMem = memoizeOne(
|
const formatDateTimeMem = memoizeOne(
|
||||||
(locale: FrontendLocaleData) =>
|
(locale: FrontendLocaleData) =>
|
||||||
new Intl.DateTimeFormat(locale.language, {
|
new Intl.DateTimeFormat(locale.language, {
|
||||||
@ -16,12 +22,12 @@ const formatDateTimeMem = memoizeOne(
|
|||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
export const formatDateTime = toLocaleStringSupportsOptions
|
// August 9, 2021, 8:23:15 AM
|
||||||
|
export const formatDateTimeWithSeconds = toLocaleStringSupportsOptions
|
||||||
? (dateObj: Date, locale: FrontendLocaleData) =>
|
? (dateObj: Date, locale: FrontendLocaleData) =>
|
||||||
formatDateTimeMem(locale).format(dateObj)
|
formatDateTimeWithSecondsMem(locale).format(dateObj)
|
||||||
: (dateObj: Date, locale: FrontendLocaleData) =>
|
: (dateObj: Date, locale: FrontendLocaleData) =>
|
||||||
format(dateObj, "MMMM D, YYYY, HH:mm" + useAmPm(locale) ? " A" : "");
|
format(dateObj, "MMMM D, YYYY, HH:mm:ss" + useAmPm(locale) ? " A" : "");
|
||||||
|
|
||||||
const formatDateTimeWithSecondsMem = memoizeOne(
|
const formatDateTimeWithSecondsMem = memoizeOne(
|
||||||
(locale: FrontendLocaleData) =>
|
(locale: FrontendLocaleData) =>
|
||||||
new Intl.DateTimeFormat(locale.language, {
|
new Intl.DateTimeFormat(locale.language, {
|
||||||
@ -35,8 +41,20 @@ const formatDateTimeWithSecondsMem = memoizeOne(
|
|||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
export const formatDateTimeWithSeconds = toLocaleStringSupportsOptions
|
// 9/8/2021, 8:23 AM
|
||||||
|
export const formatDateTimeNumeric = toLocaleStringSupportsOptions
|
||||||
? (dateObj: Date, locale: FrontendLocaleData) =>
|
? (dateObj: Date, locale: FrontendLocaleData) =>
|
||||||
formatDateTimeWithSecondsMem(locale).format(dateObj)
|
formatDateTimeNumericMem(locale).format(dateObj)
|
||||||
: (dateObj: Date, locale: FrontendLocaleData) =>
|
: (dateObj: Date, locale: FrontendLocaleData) =>
|
||||||
format(dateObj, "MMMM D, YYYY, HH:mm:ss" + useAmPm(locale) ? " A" : "");
|
format(dateObj, "M/D/YYYY, HH:mm" + useAmPm(locale) ? " A" : "");
|
||||||
|
const formatDateTimeNumericMem = memoizeOne(
|
||||||
|
(locale: FrontendLocaleData) =>
|
||||||
|
new Intl.DateTimeFormat(locale.language, {
|
||||||
|
year: "numeric",
|
||||||
|
month: "numeric",
|
||||||
|
day: "numeric",
|
||||||
|
hour: "numeric",
|
||||||
|
minute: "2-digit",
|
||||||
|
hour12: useAmPm(locale),
|
||||||
|
})
|
||||||
|
);
|
||||||
|
@ -4,6 +4,12 @@ import { FrontendLocaleData } from "../../data/translation";
|
|||||||
import { toLocaleTimeStringSupportsOptions } from "./check_options_support";
|
import { toLocaleTimeStringSupportsOptions } from "./check_options_support";
|
||||||
import { useAmPm } from "./use_am_pm";
|
import { useAmPm } from "./use_am_pm";
|
||||||
|
|
||||||
|
// 9:15 PM || 21:15
|
||||||
|
export const formatTime = toLocaleTimeStringSupportsOptions
|
||||||
|
? (dateObj: Date, locale: FrontendLocaleData) =>
|
||||||
|
formatTimeMem(locale).format(dateObj)
|
||||||
|
: (dateObj: Date, locale: FrontendLocaleData) =>
|
||||||
|
format(dateObj, "shortTime" + useAmPm(locale) ? " A" : "");
|
||||||
const formatTimeMem = memoizeOne(
|
const formatTimeMem = memoizeOne(
|
||||||
(locale: FrontendLocaleData) =>
|
(locale: FrontendLocaleData) =>
|
||||||
new Intl.DateTimeFormat(locale.language, {
|
new Intl.DateTimeFormat(locale.language, {
|
||||||
@ -13,12 +19,12 @@ const formatTimeMem = memoizeOne(
|
|||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
export const formatTime = toLocaleTimeStringSupportsOptions
|
// 9:15:24 PM || 21:15:24
|
||||||
|
export const formatTimeWithSeconds = toLocaleTimeStringSupportsOptions
|
||||||
? (dateObj: Date, locale: FrontendLocaleData) =>
|
? (dateObj: Date, locale: FrontendLocaleData) =>
|
||||||
formatTimeMem(locale).format(dateObj)
|
formatTimeWithSecondsMem(locale).format(dateObj)
|
||||||
: (dateObj: Date, locale: FrontendLocaleData) =>
|
: (dateObj: Date, locale: FrontendLocaleData) =>
|
||||||
format(dateObj, "shortTime" + useAmPm(locale) ? " A" : "");
|
format(dateObj, "mediumTime" + useAmPm(locale) ? " A" : "");
|
||||||
|
|
||||||
const formatTimeWithSecondsMem = memoizeOne(
|
const formatTimeWithSecondsMem = memoizeOne(
|
||||||
(locale: FrontendLocaleData) =>
|
(locale: FrontendLocaleData) =>
|
||||||
new Intl.DateTimeFormat(locale.language, {
|
new Intl.DateTimeFormat(locale.language, {
|
||||||
@ -29,12 +35,12 @@ const formatTimeWithSecondsMem = memoizeOne(
|
|||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
export const formatTimeWithSeconds = toLocaleTimeStringSupportsOptions
|
// Tuesday 7:00 PM || Tuesday 19:00
|
||||||
|
export const formatTimeWeekday = toLocaleTimeStringSupportsOptions
|
||||||
? (dateObj: Date, locale: FrontendLocaleData) =>
|
? (dateObj: Date, locale: FrontendLocaleData) =>
|
||||||
formatTimeWithSecondsMem(locale).format(dateObj)
|
formatTimeWeekdayMem(locale).format(dateObj)
|
||||||
: (dateObj: Date, locale: FrontendLocaleData) =>
|
: (dateObj: Date, locale: FrontendLocaleData) =>
|
||||||
format(dateObj, "mediumTime" + useAmPm(locale) ? " A" : "");
|
format(dateObj, "dddd, HH:mm" + useAmPm(locale) ? " A" : "");
|
||||||
|
|
||||||
const formatTimeWeekdayMem = memoizeOne(
|
const formatTimeWeekdayMem = memoizeOne(
|
||||||
(locale: FrontendLocaleData) =>
|
(locale: FrontendLocaleData) =>
|
||||||
new Intl.DateTimeFormat(locale.language, {
|
new Intl.DateTimeFormat(locale.language, {
|
||||||
@ -44,9 +50,3 @@ const formatTimeWeekdayMem = memoizeOne(
|
|||||||
hour12: useAmPm(locale),
|
hour12: useAmPm(locale),
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
export const formatTimeWeekday = toLocaleTimeStringSupportsOptions
|
|
||||||
? (dateObj: Date, locale: FrontendLocaleData) =>
|
|
||||||
formatTimeWeekdayMem(locale).format(dateObj)
|
|
||||||
: (dateObj: Date, locale: FrontendLocaleData) =>
|
|
||||||
format(dateObj, "dddd, HH:mm" + useAmPm(locale) ? " A" : "");
|
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
|
import memoizeOne from "memoize-one";
|
||||||
import { FrontendLocaleData, TimeFormat } from "../../data/translation";
|
import { FrontendLocaleData, TimeFormat } from "../../data/translation";
|
||||||
|
|
||||||
export const useAmPm = (locale: FrontendLocaleData): boolean => {
|
export const useAmPm = memoizeOne((locale: FrontendLocaleData): boolean => {
|
||||||
if (
|
if (
|
||||||
locale.time_format === TimeFormat.language ||
|
locale.time_format === TimeFormat.language ||
|
||||||
locale.time_format === TimeFormat.system
|
locale.time_format === TimeFormat.system
|
||||||
@ -12,4 +13,4 @@ export const useAmPm = (locale: FrontendLocaleData): boolean => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return locale.time_format === TimeFormat.am_pm;
|
return locale.time_format === TimeFormat.am_pm;
|
||||||
};
|
});
|
||||||
|
@ -35,7 +35,14 @@ import {
|
|||||||
endOfQuarter,
|
endOfQuarter,
|
||||||
endOfYear,
|
endOfYear,
|
||||||
} from "date-fns";
|
} from "date-fns";
|
||||||
import { formatDate, formatDateShort } from "../../common/datetime/format_date";
|
import {
|
||||||
|
formatDate,
|
||||||
|
formatDateMonth,
|
||||||
|
formatDateMonthYear,
|
||||||
|
formatDateShort,
|
||||||
|
formatDateWeekday,
|
||||||
|
formatDateYear,
|
||||||
|
} from "../../common/datetime/format_date";
|
||||||
import {
|
import {
|
||||||
formatDateTime,
|
formatDateTime,
|
||||||
formatDateTimeWithSeconds,
|
formatDateTimeWithSeconds,
|
||||||
@ -53,8 +60,11 @@ const FORMATS = {
|
|||||||
minute: "minute",
|
minute: "minute",
|
||||||
hour: "hour",
|
hour: "hour",
|
||||||
day: "day",
|
day: "day",
|
||||||
|
date: "date",
|
||||||
|
weekday: "weekday",
|
||||||
week: "week",
|
week: "week",
|
||||||
month: "month",
|
month: "month",
|
||||||
|
monthyear: "monthyear",
|
||||||
quarter: "quarter",
|
quarter: "quarter",
|
||||||
year: "year",
|
year: "year",
|
||||||
};
|
};
|
||||||
@ -81,16 +91,22 @@ _adapters._date.override({
|
|||||||
return formatTime(new Date(time), this.options.locale);
|
return formatTime(new Date(time), this.options.locale);
|
||||||
case "hour":
|
case "hour":
|
||||||
return formatTime(new Date(time), this.options.locale);
|
return formatTime(new Date(time), this.options.locale);
|
||||||
|
case "weekday":
|
||||||
|
return formatDateWeekday(new Date(time), this.options.locale);
|
||||||
|
case "date":
|
||||||
|
return formatDate(new Date(time), this.options.locale);
|
||||||
case "day":
|
case "day":
|
||||||
return formatDateShort(new Date(time), this.options.locale);
|
return formatDateShort(new Date(time), this.options.locale);
|
||||||
case "week":
|
case "week":
|
||||||
return formatDate(new Date(time), this.options.locale);
|
return formatDate(new Date(time), this.options.locale);
|
||||||
case "month":
|
case "month":
|
||||||
return formatDate(new Date(time), this.options.locale);
|
return formatDateMonth(new Date(time), this.options.locale);
|
||||||
|
case "monthyear":
|
||||||
|
return formatDateMonthYear(new Date(time), this.options.locale);
|
||||||
case "quarter":
|
case "quarter":
|
||||||
return formatDate(new Date(time), this.options.locale);
|
return formatDate(new Date(time), this.options.locale);
|
||||||
case "year":
|
case "year":
|
||||||
return formatDate(new Date(time), this.options.locale);
|
return formatDateYear(new Date(time), this.options.locale);
|
||||||
default:
|
default:
|
||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
|
@ -15,6 +15,8 @@ export class HaButtonToggleGroup extends LitElement {
|
|||||||
|
|
||||||
@property({ type: Boolean }) public fullWidth = false;
|
@property({ type: Boolean }) public fullWidth = false;
|
||||||
|
|
||||||
|
@property({ type: Boolean }) public dense = false;
|
||||||
|
|
||||||
protected render(): TemplateResult {
|
protected render(): TemplateResult {
|
||||||
return html`
|
return html`
|
||||||
<div>
|
<div>
|
||||||
@ -34,6 +36,8 @@ export class HaButtonToggleGroup extends LitElement {
|
|||||||
? `${100 / this.buttons.length}%`
|
? `${100 / this.buttons.length}%`
|
||||||
: "initial",
|
: "initial",
|
||||||
})}
|
})}
|
||||||
|
outlined
|
||||||
|
.dense=${this.dense}
|
||||||
.value=${button.value}
|
.value=${button.value}
|
||||||
?active=${this.active === button.value}
|
?active=${this.active === button.value}
|
||||||
@click=${this._handleClick}
|
@click=${this._handleClick}
|
||||||
@ -56,10 +60,16 @@ export class HaButtonToggleGroup extends LitElement {
|
|||||||
--mdc-icon-button-size: var(--button-toggle-size, 36px);
|
--mdc-icon-button-size: var(--button-toggle-size, 36px);
|
||||||
--mdc-icon-size: var(--button-toggle-icon-size, 20px);
|
--mdc-icon-size: var(--button-toggle-icon-size, 20px);
|
||||||
}
|
}
|
||||||
mwc-icon-button,
|
|
||||||
mwc-button {
|
mwc-button {
|
||||||
|
--mdc-shape-small: 0;
|
||||||
|
--mdc-button-outline-width: 1px 0 1px 1px;
|
||||||
|
}
|
||||||
|
mwc-icon-button {
|
||||||
border: 1px solid var(--primary-color);
|
border: 1px solid var(--primary-color);
|
||||||
border-right-width: 0px;
|
border-right-width: 0px;
|
||||||
|
}
|
||||||
|
mwc-icon-button,
|
||||||
|
mwc-button {
|
||||||
position: relative;
|
position: relative;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
@ -82,16 +92,19 @@ export class HaButtonToggleGroup extends LitElement {
|
|||||||
}
|
}
|
||||||
mwc-icon-button:first-child,
|
mwc-icon-button:first-child,
|
||||||
mwc-button:first-child {
|
mwc-button:first-child {
|
||||||
|
--mdc-shape-small: 4px 0 0 4px;
|
||||||
border-radius: 4px 0 0 4px;
|
border-radius: 4px 0 0 4px;
|
||||||
}
|
}
|
||||||
mwc-icon-button:last-child,
|
mwc-icon-button:last-child,
|
||||||
mwc-button:last-child {
|
mwc-button:last-child {
|
||||||
border-radius: 0 4px 4px 0;
|
border-radius: 0 4px 4px 0;
|
||||||
border-right-width: 1px;
|
border-right-width: 1px;
|
||||||
|
--mdc-shape-small: 0 4px 4px 0;
|
||||||
|
--mdc-button-outline-width: 1px;
|
||||||
}
|
}
|
||||||
mwc-icon-button:only-child,
|
mwc-icon-button:only-child,
|
||||||
mwc-button:only-child {
|
mwc-button:only-child {
|
||||||
border-radius: 4px;
|
--mdc-shape-small: 4px;
|
||||||
border-right-width: 1px;
|
border-right-width: 1px;
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
@ -1,10 +1,8 @@
|
|||||||
import { html, LitElement } from "lit";
|
import { html, LitElement } from "lit";
|
||||||
import { customElement, property } from "lit/decorators";
|
import { customElement, property } from "lit/decorators";
|
||||||
import memoizeOne from "memoize-one";
|
|
||||||
import { useAmPm } from "../../common/datetime/use_am_pm";
|
import { useAmPm } from "../../common/datetime/use_am_pm";
|
||||||
import { fireEvent } from "../../common/dom/fire_event";
|
import { fireEvent } from "../../common/dom/fire_event";
|
||||||
import { TimeSelector } from "../../data/selector";
|
import { TimeSelector } from "../../data/selector";
|
||||||
import { FrontendLocaleData } from "../../data/translation";
|
|
||||||
import { HomeAssistant } from "../../types";
|
import { HomeAssistant } from "../../types";
|
||||||
import "../paper-time-input";
|
import "../paper-time-input";
|
||||||
|
|
||||||
@ -20,12 +18,8 @@ export class HaTimeSelector extends LitElement {
|
|||||||
|
|
||||||
@property({ type: Boolean }) public disabled = false;
|
@property({ type: Boolean }) public disabled = false;
|
||||||
|
|
||||||
private _useAmPmMem = memoizeOne((locale: FrontendLocaleData): boolean =>
|
|
||||||
useAmPm(locale)
|
|
||||||
);
|
|
||||||
|
|
||||||
protected render() {
|
protected render() {
|
||||||
const useAMPM = this._useAmPmMem(this.hass.locale);
|
const useAMPM = useAmPm(this.hass.locale);
|
||||||
|
|
||||||
const parts = this.value?.split(":") || [];
|
const parts = this.value?.split(":") || [];
|
||||||
const hours = parts[0];
|
const hours = parts[0];
|
||||||
@ -50,7 +44,7 @@ export class HaTimeSelector extends LitElement {
|
|||||||
|
|
||||||
private _timeChanged(ev) {
|
private _timeChanged(ev) {
|
||||||
let value = ev.target.value;
|
let value = ev.target.value;
|
||||||
const useAMPM = this._useAmPmMem(this.hass.locale);
|
const useAMPM = useAmPm(this.hass.locale);
|
||||||
let hours = Number(ev.target.hour || 0);
|
let hours = Number(ev.target.hour || 0);
|
||||||
if (value && useAMPM) {
|
if (value && useAMPM) {
|
||||||
if (ev.target.amPm === "PM") {
|
if (ev.target.amPm === "PM") {
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
import { addDays, addMonths, startOfDay, startOfMonth } from "date-fns";
|
||||||
import { HassEntity } from "home-assistant-js-websocket";
|
import { HassEntity } from "home-assistant-js-websocket";
|
||||||
import { computeStateDisplay } from "../common/entity/compute_state_display";
|
import { computeStateDisplay } from "../common/entity/compute_state_display";
|
||||||
import { computeStateDomain } from "../common/entity/compute_state_domain";
|
import { computeStateDomain } from "../common/entity/compute_state_domain";
|
||||||
@ -406,3 +407,90 @@ export const calculateStatisticsSumGrowthWithPercentage = (
|
|||||||
|
|
||||||
return sum;
|
return sum;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const reduceSumStatisticsByDay = (
|
||||||
|
values: StatisticValue[]
|
||||||
|
): StatisticValue[] => {
|
||||||
|
if (!values?.length) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
const result: StatisticValue[] = [];
|
||||||
|
if (
|
||||||
|
values.length > 1 &&
|
||||||
|
new Date(values[0].start).getDate() === new Date(values[1].start).getDate()
|
||||||
|
) {
|
||||||
|
// add init value if the first value isn't end of previous period
|
||||||
|
result.push({
|
||||||
|
...values[0]!,
|
||||||
|
start: startOfMonth(addDays(new Date(values[0].start), -1)).toISOString(),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
let lastValue: StatisticValue;
|
||||||
|
let prevDate: number | undefined;
|
||||||
|
for (const value of values) {
|
||||||
|
const date = new Date(value.start).getDate();
|
||||||
|
if (prevDate === undefined) {
|
||||||
|
prevDate = date;
|
||||||
|
}
|
||||||
|
if (prevDate !== date) {
|
||||||
|
// Last value of the day
|
||||||
|
result.push({
|
||||||
|
...lastValue!,
|
||||||
|
start: startOfDay(new Date(lastValue!.start)).toISOString(),
|
||||||
|
});
|
||||||
|
prevDate = date;
|
||||||
|
}
|
||||||
|
lastValue = value;
|
||||||
|
}
|
||||||
|
// Add final value
|
||||||
|
result.push({
|
||||||
|
...lastValue!,
|
||||||
|
start: startOfDay(new Date(lastValue!.start)).toISOString(),
|
||||||
|
});
|
||||||
|
return result;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const reduceSumStatisticsByMonth = (
|
||||||
|
values: StatisticValue[]
|
||||||
|
): StatisticValue[] => {
|
||||||
|
if (!values?.length) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
const result: StatisticValue[] = [];
|
||||||
|
if (
|
||||||
|
values.length > 1 &&
|
||||||
|
new Date(values[0].start).getMonth() ===
|
||||||
|
new Date(values[1].start).getMonth()
|
||||||
|
) {
|
||||||
|
// add init value if the first value isn't end of previous period
|
||||||
|
result.push({
|
||||||
|
...values[0]!,
|
||||||
|
start: startOfMonth(
|
||||||
|
addMonths(new Date(values[0].start), -1)
|
||||||
|
).toISOString(),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
let lastValue: StatisticValue;
|
||||||
|
let prevMonth: number | undefined;
|
||||||
|
for (const value of values) {
|
||||||
|
const month = new Date(value.start).getMonth();
|
||||||
|
if (prevMonth === undefined) {
|
||||||
|
prevMonth = month;
|
||||||
|
}
|
||||||
|
if (prevMonth !== month) {
|
||||||
|
// Last value of the day
|
||||||
|
result.push({
|
||||||
|
...lastValue!,
|
||||||
|
start: startOfMonth(new Date(lastValue!.start)).toISOString(),
|
||||||
|
});
|
||||||
|
prevMonth = month;
|
||||||
|
}
|
||||||
|
lastValue = value;
|
||||||
|
}
|
||||||
|
// Add final value
|
||||||
|
result.push({
|
||||||
|
...lastValue!,
|
||||||
|
start: startOfMonth(new Date(lastValue!.start)).toISOString(),
|
||||||
|
});
|
||||||
|
return result;
|
||||||
|
};
|
||||||
|
@ -10,7 +10,13 @@ import {
|
|||||||
ChartOptions,
|
ChartOptions,
|
||||||
ScatterDataPoint,
|
ScatterDataPoint,
|
||||||
} from "chart.js";
|
} from "chart.js";
|
||||||
import { endOfToday, isToday, startOfToday } from "date-fns";
|
import {
|
||||||
|
addHours,
|
||||||
|
differenceInDays,
|
||||||
|
endOfToday,
|
||||||
|
isToday,
|
||||||
|
startOfToday,
|
||||||
|
} from "date-fns";
|
||||||
import { HomeAssistant } from "../../../../types";
|
import { HomeAssistant } from "../../../../types";
|
||||||
import { LovelaceCard } from "../../types";
|
import { LovelaceCard } from "../../types";
|
||||||
import { EnergySolarGraphCardConfig } from "../types";
|
import { EnergySolarGraphCardConfig } from "../types";
|
||||||
@ -39,6 +45,10 @@ import {
|
|||||||
} from "../../../../common/string/format_number";
|
} from "../../../../common/string/format_number";
|
||||||
import { SubscribeMixin } from "../../../../mixins/subscribe-mixin";
|
import { SubscribeMixin } from "../../../../mixins/subscribe-mixin";
|
||||||
import { FrontendLocaleData } from "../../../../data/translation";
|
import { FrontendLocaleData } from "../../../../data/translation";
|
||||||
|
import {
|
||||||
|
reduceSumStatisticsByMonth,
|
||||||
|
reduceSumStatisticsByDay,
|
||||||
|
} from "../../../../data/history";
|
||||||
|
|
||||||
@customElement("hui-energy-solar-graph-card")
|
@customElement("hui-energy-solar-graph-card")
|
||||||
export class HuiEnergySolarGraphCard
|
export class HuiEnergySolarGraphCard
|
||||||
@ -110,84 +120,108 @@ export class HuiEnergySolarGraphCard
|
|||||||
}
|
}
|
||||||
|
|
||||||
private _createOptions = memoizeOne(
|
private _createOptions = memoizeOne(
|
||||||
(start: Date, end: Date, locale: FrontendLocaleData): ChartOptions => ({
|
(start: Date, end: Date, locale: FrontendLocaleData): ChartOptions => {
|
||||||
parsing: false,
|
const dayDifference = differenceInDays(end, start);
|
||||||
animation: false,
|
return {
|
||||||
scales: {
|
parsing: false,
|
||||||
x: {
|
animation: false,
|
||||||
type: "time",
|
scales: {
|
||||||
suggestedMin: start.getTime(),
|
x: {
|
||||||
suggestedMax: end.getTime(),
|
type: "time",
|
||||||
adapters: {
|
suggestedMin: (dayDifference > 2
|
||||||
date: {
|
? addHours(start, -11)
|
||||||
locale: locale,
|
: start
|
||||||
|
).getTime(),
|
||||||
|
suggestedMax: (dayDifference > 2
|
||||||
|
? addHours(end, -11)
|
||||||
|
: end
|
||||||
|
).getTime(),
|
||||||
|
adapters: {
|
||||||
|
date: {
|
||||||
|
locale: locale,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
ticks: {
|
||||||
|
maxRotation: 0,
|
||||||
|
sampleSize: 5,
|
||||||
|
autoSkipPadding: 20,
|
||||||
|
major: {
|
||||||
|
enabled: true,
|
||||||
|
},
|
||||||
|
font: (context) =>
|
||||||
|
context.tick && context.tick.major
|
||||||
|
? ({ weight: "bold" } as any)
|
||||||
|
: {},
|
||||||
|
},
|
||||||
|
time: {
|
||||||
|
tooltipFormat:
|
||||||
|
dayDifference > 35
|
||||||
|
? "monthyear"
|
||||||
|
: dayDifference > 7
|
||||||
|
? "date"
|
||||||
|
: dayDifference > 2
|
||||||
|
? "weekday"
|
||||||
|
: dayDifference > 0
|
||||||
|
? "datetime"
|
||||||
|
: "hour",
|
||||||
|
minUnit:
|
||||||
|
dayDifference > 35
|
||||||
|
? "month"
|
||||||
|
: dayDifference > 2
|
||||||
|
? "day"
|
||||||
|
: "hour",
|
||||||
|
},
|
||||||
|
offset: true,
|
||||||
|
},
|
||||||
|
y: {
|
||||||
|
type: "linear",
|
||||||
|
title: {
|
||||||
|
display: true,
|
||||||
|
text: "kWh",
|
||||||
|
},
|
||||||
|
ticks: {
|
||||||
|
beginAtZero: true,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
ticks: {
|
},
|
||||||
maxRotation: 0,
|
plugins: {
|
||||||
sampleSize: 5,
|
tooltip: {
|
||||||
autoSkipPadding: 20,
|
mode: "nearest",
|
||||||
major: {
|
callbacks: {
|
||||||
enabled: true,
|
label: (context) =>
|
||||||
|
`${context.dataset.label}: ${formatNumber(
|
||||||
|
context.parsed.y,
|
||||||
|
locale
|
||||||
|
)} kWh`,
|
||||||
},
|
},
|
||||||
font: (context) =>
|
|
||||||
context.tick && context.tick.major
|
|
||||||
? ({ weight: "bold" } as any)
|
|
||||||
: {},
|
|
||||||
},
|
},
|
||||||
time: {
|
filler: {
|
||||||
tooltipFormat: "datetime",
|
propagate: false,
|
||||||
},
|
},
|
||||||
offset: true,
|
legend: {
|
||||||
},
|
display: false,
|
||||||
y: {
|
labels: {
|
||||||
type: "linear",
|
usePointStyle: true,
|
||||||
title: {
|
},
|
||||||
display: true,
|
|
||||||
text: "kWh",
|
|
||||||
},
|
|
||||||
ticks: {
|
|
||||||
beginAtZero: true,
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
hover: {
|
||||||
plugins: {
|
|
||||||
tooltip: {
|
|
||||||
mode: "nearest",
|
mode: "nearest",
|
||||||
callbacks: {
|
},
|
||||||
label: (context) =>
|
elements: {
|
||||||
`${context.dataset.label}: ${formatNumber(
|
line: {
|
||||||
context.parsed.y,
|
tension: 0.3,
|
||||||
locale
|
borderWidth: 1.5,
|
||||||
)} kWh`,
|
},
|
||||||
|
bar: { borderWidth: 1.5, borderRadius: 4 },
|
||||||
|
point: {
|
||||||
|
hitRadius: 5,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
filler: {
|
// @ts-expect-error
|
||||||
propagate: false,
|
locale: numberFormatToLocale(locale),
|
||||||
},
|
};
|
||||||
legend: {
|
}
|
||||||
display: false,
|
|
||||||
labels: {
|
|
||||||
usePointStyle: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
hover: {
|
|
||||||
mode: "nearest",
|
|
||||||
},
|
|
||||||
elements: {
|
|
||||||
line: {
|
|
||||||
tension: 0.3,
|
|
||||||
borderWidth: 1.5,
|
|
||||||
},
|
|
||||||
bar: { borderWidth: 1.5, borderRadius: 4 },
|
|
||||||
point: {
|
|
||||||
hitRadius: 5,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
// @ts-expect-error
|
|
||||||
locale: numberFormatToLocale(locale),
|
|
||||||
})
|
|
||||||
);
|
);
|
||||||
|
|
||||||
private async _getStatistics(energyData: EnergyData): Promise<void> {
|
private async _getStatistics(energyData: EnergyData): Promise<void> {
|
||||||
@ -229,6 +263,11 @@ export class HuiEnergySolarGraphCard
|
|||||||
.getPropertyValue("--energy-solar-color")
|
.getPropertyValue("--energy-solar-color")
|
||||||
.trim();
|
.trim();
|
||||||
|
|
||||||
|
const dayDifference = differenceInDays(
|
||||||
|
energyData.end || new Date(),
|
||||||
|
energyData.start
|
||||||
|
);
|
||||||
|
|
||||||
solarSources.forEach((source, idx) => {
|
solarSources.forEach((source, idx) => {
|
||||||
const data: ChartDataset<"bar" | "line">[] = [];
|
const data: ChartDataset<"bar" | "line">[] = [];
|
||||||
const entity = this.hass.states[source.stat_energy_from];
|
const entity = this.hass.states[source.stat_energy_from];
|
||||||
@ -244,9 +283,20 @@ export class HuiEnergySolarGraphCard
|
|||||||
const solarProductionData: ScatterDataPoint[] = [];
|
const solarProductionData: ScatterDataPoint[] = [];
|
||||||
|
|
||||||
// Process solar production data.
|
// Process solar production data.
|
||||||
if (energyData.stats[source.stat_energy_from]) {
|
if (source.stat_energy_from in energyData.stats) {
|
||||||
for (const point of energyData.stats[source.stat_energy_from]) {
|
const stats =
|
||||||
if (!point.sum) {
|
dayDifference > 35
|
||||||
|
? reduceSumStatisticsByMonth(
|
||||||
|
energyData.stats[source.stat_energy_from]
|
||||||
|
)
|
||||||
|
: dayDifference > 2
|
||||||
|
? reduceSumStatisticsByDay(
|
||||||
|
energyData.stats[source.stat_energy_from]
|
||||||
|
)
|
||||||
|
: energyData.stats[source.stat_energy_from];
|
||||||
|
|
||||||
|
for (const point of stats) {
|
||||||
|
if (point.sum === null) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if (prevValue === null) {
|
if (prevValue === null) {
|
||||||
@ -294,7 +344,14 @@ export class HuiEnergySolarGraphCard
|
|||||||
) {
|
) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
dateObj.setMinutes(0, 0, 0);
|
if (dayDifference > 35) {
|
||||||
|
dateObj.setDate(1);
|
||||||
|
}
|
||||||
|
if (dayDifference > 2) {
|
||||||
|
dateObj.setHours(0, 0, 0, 0);
|
||||||
|
} else {
|
||||||
|
dateObj.setMinutes(0, 0, 0);
|
||||||
|
}
|
||||||
const time = dateObj.getTime();
|
const time = dateObj.getTime();
|
||||||
if (time in forecastsData) {
|
if (time in forecastsData) {
|
||||||
forecastsData[time] += value;
|
forecastsData[time] += value;
|
||||||
|
@ -1,5 +1,11 @@
|
|||||||
import { ChartData, ChartDataset, ChartOptions } from "chart.js";
|
import { ChartData, ChartDataset, ChartOptions } from "chart.js";
|
||||||
import { startOfToday, endOfToday, isToday } from "date-fns";
|
import {
|
||||||
|
startOfToday,
|
||||||
|
endOfToday,
|
||||||
|
isToday,
|
||||||
|
differenceInDays,
|
||||||
|
addHours,
|
||||||
|
} from "date-fns";
|
||||||
import { UnsubscribeFunc } from "home-assistant-js-websocket";
|
import { UnsubscribeFunc } from "home-assistant-js-websocket";
|
||||||
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
|
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
|
||||||
import { customElement, property, state } from "lit/decorators";
|
import { customElement, property, state } from "lit/decorators";
|
||||||
@ -21,6 +27,10 @@ import {
|
|||||||
import "../../../../components/chart/ha-chart-base";
|
import "../../../../components/chart/ha-chart-base";
|
||||||
import "../../../../components/ha-card";
|
import "../../../../components/ha-card";
|
||||||
import { EnergyData, getEnergyDataCollection } from "../../../../data/energy";
|
import { EnergyData, getEnergyDataCollection } from "../../../../data/energy";
|
||||||
|
import {
|
||||||
|
reduceSumStatisticsByDay,
|
||||||
|
reduceSumStatisticsByMonth,
|
||||||
|
} from "../../../../data/history";
|
||||||
import { FrontendLocaleData } from "../../../../data/translation";
|
import { FrontendLocaleData } from "../../../../data/translation";
|
||||||
import { SubscribeMixin } from "../../../../mixins/subscribe-mixin";
|
import { SubscribeMixin } from "../../../../mixins/subscribe-mixin";
|
||||||
import { HomeAssistant } from "../../../../types";
|
import { HomeAssistant } from "../../../../types";
|
||||||
@ -97,106 +107,136 @@ export class HuiEnergyUsageGraphCard
|
|||||||
}
|
}
|
||||||
|
|
||||||
private _createOptions = memoizeOne(
|
private _createOptions = memoizeOne(
|
||||||
(start: Date, end: Date, locale: FrontendLocaleData): ChartOptions => ({
|
(start: Date, end: Date, locale: FrontendLocaleData): ChartOptions => {
|
||||||
parsing: false,
|
const dayDifference = differenceInDays(end, start);
|
||||||
animation: false,
|
return {
|
||||||
scales: {
|
parsing: false,
|
||||||
x: {
|
animation: false,
|
||||||
type: "time",
|
scales: {
|
||||||
suggestedMin: start.getTime(),
|
x: {
|
||||||
suggestedMax: end.getTime(),
|
type: "time",
|
||||||
adapters: {
|
suggestedMin: (dayDifference > 2
|
||||||
date: {
|
? addHours(start, -11)
|
||||||
locale: locale,
|
: start
|
||||||
|
).getTime(),
|
||||||
|
suggestedMax: (dayDifference > 2
|
||||||
|
? addHours(end, -11)
|
||||||
|
: end
|
||||||
|
).getTime(),
|
||||||
|
adapters: {
|
||||||
|
date: {
|
||||||
|
locale: locale,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
ticks: {
|
||||||
|
maxRotation: 0,
|
||||||
|
sampleSize: 5,
|
||||||
|
autoSkipPadding: 20,
|
||||||
|
major: {
|
||||||
|
enabled: true,
|
||||||
|
},
|
||||||
|
font: (context) =>
|
||||||
|
context.tick && context.tick.major
|
||||||
|
? ({ weight: "bold" } as any)
|
||||||
|
: {},
|
||||||
|
},
|
||||||
|
time: {
|
||||||
|
tooltipFormat:
|
||||||
|
dayDifference > 35
|
||||||
|
? "monthyear"
|
||||||
|
: dayDifference > 7
|
||||||
|
? "date"
|
||||||
|
: dayDifference > 2
|
||||||
|
? "weekday"
|
||||||
|
: dayDifference > 0
|
||||||
|
? "datetime"
|
||||||
|
: "hour",
|
||||||
|
minUnit:
|
||||||
|
dayDifference > 35
|
||||||
|
? "month"
|
||||||
|
: dayDifference > 2
|
||||||
|
? "day"
|
||||||
|
: "hour",
|
||||||
|
},
|
||||||
|
offset: true,
|
||||||
|
},
|
||||||
|
y: {
|
||||||
|
stacked: true,
|
||||||
|
type: "linear",
|
||||||
|
title: {
|
||||||
|
display: true,
|
||||||
|
text: "kWh",
|
||||||
|
},
|
||||||
|
ticks: {
|
||||||
|
beginAtZero: true,
|
||||||
|
callback: (value) => formatNumber(Math.abs(value), locale),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
ticks: {
|
|
||||||
maxRotation: 0,
|
|
||||||
sampleSize: 5,
|
|
||||||
autoSkipPadding: 20,
|
|
||||||
major: {
|
|
||||||
enabled: true,
|
|
||||||
},
|
|
||||||
font: (context) =>
|
|
||||||
context.tick && context.tick.major
|
|
||||||
? ({ weight: "bold" } as any)
|
|
||||||
: {},
|
|
||||||
},
|
|
||||||
time: {
|
|
||||||
tooltipFormat: "datetime",
|
|
||||||
},
|
|
||||||
offset: true,
|
|
||||||
},
|
},
|
||||||
y: {
|
plugins: {
|
||||||
stacked: true,
|
tooltip: {
|
||||||
type: "linear",
|
mode: "x",
|
||||||
title: {
|
intersect: true,
|
||||||
display: true,
|
position: "nearest",
|
||||||
text: "kWh",
|
filter: (val) => val.formattedValue !== "0",
|
||||||
},
|
callbacks: {
|
||||||
ticks: {
|
label: (context) =>
|
||||||
beginAtZero: true,
|
`${context.dataset.label}: ${formatNumber(
|
||||||
callback: (value) => formatNumber(Math.abs(value), locale),
|
Math.abs(context.parsed.y),
|
||||||
},
|
locale
|
||||||
},
|
)} kWh`,
|
||||||
},
|
footer: (contexts) => {
|
||||||
plugins: {
|
let totalConsumed = 0;
|
||||||
tooltip: {
|
let totalReturned = 0;
|
||||||
mode: "x",
|
for (const context of contexts) {
|
||||||
intersect: true,
|
const value = (context.dataset.data[context.dataIndex] as any)
|
||||||
position: "nearest",
|
.y;
|
||||||
filter: (val) => val.formattedValue !== "0",
|
if (value > 0) {
|
||||||
callbacks: {
|
totalConsumed += value;
|
||||||
label: (context) =>
|
} else {
|
||||||
`${context.dataset.label}: ${formatNumber(
|
totalReturned += Math.abs(value);
|
||||||
Math.abs(context.parsed.y),
|
}
|
||||||
locale
|
|
||||||
)} kWh`,
|
|
||||||
footer: (contexts) => {
|
|
||||||
let totalConsumed = 0;
|
|
||||||
let totalReturned = 0;
|
|
||||||
for (const context of contexts) {
|
|
||||||
const value = (context.dataset.data[context.dataIndex] as any)
|
|
||||||
.y;
|
|
||||||
if (value > 0) {
|
|
||||||
totalConsumed += value;
|
|
||||||
} else {
|
|
||||||
totalReturned += Math.abs(value);
|
|
||||||
}
|
}
|
||||||
}
|
return [
|
||||||
return [
|
totalConsumed
|
||||||
totalConsumed
|
? `Total consumed: ${formatNumber(
|
||||||
? `Total consumed: ${formatNumber(totalConsumed, locale)} kWh`
|
totalConsumed,
|
||||||
: "",
|
locale
|
||||||
totalReturned
|
)} kWh`
|
||||||
? `Total returned: ${formatNumber(totalReturned, locale)} kWh`
|
: "",
|
||||||
: "",
|
totalReturned
|
||||||
].filter(Boolean);
|
? `Total returned: ${formatNumber(
|
||||||
|
totalReturned,
|
||||||
|
locale
|
||||||
|
)} kWh`
|
||||||
|
: "",
|
||||||
|
].filter(Boolean);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
filler: {
|
||||||
|
propagate: false,
|
||||||
|
},
|
||||||
|
legend: {
|
||||||
|
display: false,
|
||||||
|
labels: {
|
||||||
|
usePointStyle: true,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
filler: {
|
hover: {
|
||||||
propagate: false,
|
mode: "nearest",
|
||||||
},
|
},
|
||||||
legend: {
|
elements: {
|
||||||
display: false,
|
bar: { borderWidth: 1.5, borderRadius: 4 },
|
||||||
labels: {
|
point: {
|
||||||
usePointStyle: true,
|
hitRadius: 5,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
// @ts-expect-error
|
||||||
hover: {
|
locale: numberFormatToLocale(locale),
|
||||||
mode: "nearest",
|
};
|
||||||
},
|
}
|
||||||
elements: {
|
|
||||||
bar: { borderWidth: 1.5, borderRadius: 4 },
|
|
||||||
point: {
|
|
||||||
hitRadius: 5,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
// @ts-expect-error
|
|
||||||
locale: numberFormatToLocale(locale),
|
|
||||||
})
|
|
||||||
);
|
);
|
||||||
|
|
||||||
private async _getStatistics(energyData: EnergyData): Promise<void> {
|
private async _getStatistics(energyData: EnergyData): Promise<void> {
|
||||||
@ -233,7 +273,13 @@ export class HuiEnergyUsageGraphCard
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const dayDifference = differenceInDays(
|
||||||
|
energyData.end || new Date(),
|
||||||
|
energyData.start
|
||||||
|
);
|
||||||
|
|
||||||
const statisticsData = Object.values(energyData.stats);
|
const statisticsData = Object.values(energyData.stats);
|
||||||
|
|
||||||
const datasets: ChartDataset<"bar">[] = [];
|
const datasets: ChartDataset<"bar">[] = [];
|
||||||
let endTime: Date;
|
let endTime: Date;
|
||||||
|
|
||||||
@ -287,14 +333,20 @@ export class HuiEnergyUsageGraphCard
|
|||||||
const totalStats: { [start: string]: number } = {};
|
const totalStats: { [start: string]: number } = {};
|
||||||
const sets: { [statId: string]: { [start: string]: number } } = {};
|
const sets: { [statId: string]: { [start: string]: number } } = {};
|
||||||
statIds!.forEach((id) => {
|
statIds!.forEach((id) => {
|
||||||
const stats = energyData.stats[id];
|
const stats =
|
||||||
|
dayDifference > 35
|
||||||
|
? reduceSumStatisticsByMonth(energyData.stats[id])
|
||||||
|
: dayDifference > 2
|
||||||
|
? reduceSumStatisticsByDay(energyData.stats[id])
|
||||||
|
: energyData.stats[id];
|
||||||
if (!stats) {
|
if (!stats) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const set = {};
|
const set = {};
|
||||||
let prevValue: number;
|
let prevValue: number;
|
||||||
stats.forEach((stat) => {
|
stats.forEach((stat) => {
|
||||||
if (!stat.sum) {
|
if (stat.sum === null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (!prevValue) {
|
if (!prevValue) {
|
||||||
|
@ -1,15 +1,45 @@
|
|||||||
import { mdiChevronLeft, mdiChevronRight } from "@mdi/js";
|
import { mdiChevronLeft, mdiChevronRight } from "@mdi/js";
|
||||||
import { endOfToday, addDays, endOfDay, isToday, startOfToday } from "date-fns";
|
import {
|
||||||
|
endOfToday,
|
||||||
|
addDays,
|
||||||
|
endOfDay,
|
||||||
|
startOfToday,
|
||||||
|
endOfWeek,
|
||||||
|
endOfMonth,
|
||||||
|
startOfDay,
|
||||||
|
startOfWeek,
|
||||||
|
startOfMonth,
|
||||||
|
addMonths,
|
||||||
|
addWeeks,
|
||||||
|
startOfYear,
|
||||||
|
addYears,
|
||||||
|
endOfYear,
|
||||||
|
isWithinInterval,
|
||||||
|
differenceInDays,
|
||||||
|
} from "date-fns";
|
||||||
import { UnsubscribeFunc } from "home-assistant-js-websocket";
|
import { UnsubscribeFunc } from "home-assistant-js-websocket";
|
||||||
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
|
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
|
||||||
import { customElement, property, state } from "lit/decorators";
|
import { customElement, property, state } from "lit/decorators";
|
||||||
import { formatDate } from "../../../common/datetime/format_date";
|
import {
|
||||||
|
formatDate,
|
||||||
|
formatDateMonthYear,
|
||||||
|
formatDateShort,
|
||||||
|
formatDateYear,
|
||||||
|
} from "../../../common/datetime/format_date";
|
||||||
import { EnergyData, getEnergyDataCollection } from "../../../data/energy";
|
import { EnergyData, getEnergyDataCollection } from "../../../data/energy";
|
||||||
import { SubscribeMixin } from "../../../mixins/subscribe-mixin";
|
import { SubscribeMixin } from "../../../mixins/subscribe-mixin";
|
||||||
import { HomeAssistant } from "../../../types";
|
import { HomeAssistant, ToggleButton } from "../../../types";
|
||||||
import "@material/mwc-icon-button/mwc-icon-button";
|
import "@material/mwc-icon-button/mwc-icon-button";
|
||||||
import "../../../components/ha-svg-icon";
|
import "../../../components/ha-svg-icon";
|
||||||
import "@material/mwc-button/mwc-button";
|
import "@material/mwc-button/mwc-button";
|
||||||
|
import "../../../components/ha-button-toggle-group";
|
||||||
|
|
||||||
|
const viewButtons: ToggleButton[] = [
|
||||||
|
{ label: "Day", value: "day" },
|
||||||
|
{ label: "Week", value: "week" },
|
||||||
|
{ label: "Month", value: "month" },
|
||||||
|
{ label: "Year", value: "year" },
|
||||||
|
];
|
||||||
|
|
||||||
@customElement("hui-energy-period-selector")
|
@customElement("hui-energy-period-selector")
|
||||||
export class HuiEnergyPeriodSelector extends SubscribeMixin(LitElement) {
|
export class HuiEnergyPeriodSelector extends SubscribeMixin(LitElement) {
|
||||||
@ -21,6 +51,8 @@ export class HuiEnergyPeriodSelector extends SubscribeMixin(LitElement) {
|
|||||||
|
|
||||||
@state() _endDate?: Date;
|
@state() _endDate?: Date;
|
||||||
|
|
||||||
|
@state() private _period?: "day" | "week" | "month" | "year";
|
||||||
|
|
||||||
public hassSubscribe(): UnsubscribeFunc[] {
|
public hassSubscribe(): UnsubscribeFunc[] {
|
||||||
return [
|
return [
|
||||||
getEnergyDataCollection(this.hass, {
|
getEnergyDataCollection(this.hass, {
|
||||||
@ -37,41 +69,110 @@ export class HuiEnergyPeriodSelector extends SubscribeMixin(LitElement) {
|
|||||||
return html`
|
return html`
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="label">
|
<div class="label">
|
||||||
${formatDate(this._startDate, this.hass.locale)}
|
${this._period === "day"
|
||||||
|
? formatDate(this._startDate, this.hass.locale)
|
||||||
|
: this._period === "month"
|
||||||
|
? formatDateMonthYear(this._startDate, this.hass.locale)
|
||||||
|
: this._period === "year"
|
||||||
|
? formatDateYear(this._startDate, this.hass.locale)
|
||||||
|
: `${formatDateShort(
|
||||||
|
this._startDate,
|
||||||
|
this.hass.locale
|
||||||
|
)} - ${formatDateShort(
|
||||||
|
this._endDate || new Date(),
|
||||||
|
this.hass.locale
|
||||||
|
)}`}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<mwc-icon-button label="Previous Day" @click=${this._pickPreviousDay}>
|
<mwc-icon-button label="Previous" @click=${this._pickPrevious}>
|
||||||
<ha-svg-icon .path=${mdiChevronLeft}></ha-svg-icon>
|
<ha-svg-icon .path=${mdiChevronLeft}></ha-svg-icon>
|
||||||
</mwc-icon-button>
|
</mwc-icon-button>
|
||||||
<mwc-icon-button label="Next Day" @click=${this._pickNextDay}>
|
<mwc-icon-button label="Next" @click=${this._pickNext}>
|
||||||
<ha-svg-icon .path=${mdiChevronRight}></ha-svg-icon>
|
<ha-svg-icon .path=${mdiChevronRight}></ha-svg-icon>
|
||||||
</mwc-icon-button>
|
</mwc-icon-button>
|
||||||
|
|
||||||
<mwc-button
|
<mwc-button dense outlined @click=${this._pickToday}>
|
||||||
|
Today
|
||||||
|
</mwc-button>
|
||||||
|
<ha-button-toggle-group
|
||||||
|
.buttons=${viewButtons}
|
||||||
|
.active=${this._period}
|
||||||
dense
|
dense
|
||||||
outlined
|
@value-changed=${this._handleView}
|
||||||
.disabled=${isToday(this._startDate)}
|
></ha-button-toggle-group>
|
||||||
@click=${this._pickToday}
|
|
||||||
>Today</mwc-button
|
|
||||||
>
|
|
||||||
</div>
|
</div>
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private _handleView(ev: CustomEvent): void {
|
||||||
|
this._period = ev.detail.value;
|
||||||
|
const today = startOfToday();
|
||||||
|
const start =
|
||||||
|
!this._startDate ||
|
||||||
|
isWithinInterval(today, {
|
||||||
|
start: this._startDate,
|
||||||
|
end: this._endDate || endOfToday(),
|
||||||
|
})
|
||||||
|
? today
|
||||||
|
: this._startDate;
|
||||||
|
|
||||||
|
this._setDate(
|
||||||
|
this._period === "day"
|
||||||
|
? startOfDay(start)
|
||||||
|
: this._period === "week"
|
||||||
|
? startOfWeek(start, { weekStartsOn: 1 })
|
||||||
|
: this._period === "month"
|
||||||
|
? startOfMonth(start)
|
||||||
|
: startOfYear(start)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
private _pickToday() {
|
private _pickToday() {
|
||||||
this._setDate(startOfToday());
|
this._setDate(
|
||||||
|
this._period === "day"
|
||||||
|
? startOfToday()
|
||||||
|
: this._period === "week"
|
||||||
|
? startOfWeek(new Date(), { weekStartsOn: 1 })
|
||||||
|
: this._period === "month"
|
||||||
|
? startOfMonth(new Date())
|
||||||
|
: startOfYear(new Date())
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
private _pickPreviousDay() {
|
private _pickPrevious() {
|
||||||
this._setDate(addDays(this._startDate!, -1));
|
const newStart =
|
||||||
|
this._period === "day"
|
||||||
|
? addDays(this._startDate!, -1)
|
||||||
|
: this._period === "week"
|
||||||
|
? addWeeks(this._startDate!, -1)
|
||||||
|
: this._period === "month"
|
||||||
|
? addMonths(this._startDate!, -1)
|
||||||
|
: addYears(this._startDate!, -1);
|
||||||
|
this._setDate(newStart);
|
||||||
}
|
}
|
||||||
|
|
||||||
private _pickNextDay() {
|
private _pickNext() {
|
||||||
this._setDate(addDays(this._startDate!, +1));
|
const newStart =
|
||||||
|
this._period === "day"
|
||||||
|
? addDays(this._startDate!, 1)
|
||||||
|
: this._period === "week"
|
||||||
|
? addWeeks(this._startDate!, 1)
|
||||||
|
: this._period === "month"
|
||||||
|
? addMonths(this._startDate!, 1)
|
||||||
|
: addYears(this._startDate!, 1);
|
||||||
|
this._setDate(newStart);
|
||||||
}
|
}
|
||||||
|
|
||||||
private _setDate(startDate: Date) {
|
private _setDate(startDate: Date) {
|
||||||
const endDate = endOfDay(startDate);
|
const endDate =
|
||||||
|
this._period === "day"
|
||||||
|
? endOfDay(startDate)
|
||||||
|
: this._period === "week"
|
||||||
|
? endOfWeek(startDate, { weekStartsOn: 1 })
|
||||||
|
: this._period === "month"
|
||||||
|
? endOfMonth(startDate)
|
||||||
|
: endOfYear(startDate);
|
||||||
|
|
||||||
const energyCollection = getEnergyDataCollection(this.hass, {
|
const energyCollection = getEnergyDataCollection(this.hass, {
|
||||||
key: this.collectionKey,
|
key: this.collectionKey,
|
||||||
});
|
});
|
||||||
@ -82,6 +183,17 @@ export class HuiEnergyPeriodSelector extends SubscribeMixin(LitElement) {
|
|||||||
private _updateDates(energyData: EnergyData): void {
|
private _updateDates(energyData: EnergyData): void {
|
||||||
this._startDate = energyData.start;
|
this._startDate = energyData.start;
|
||||||
this._endDate = energyData.end || endOfToday();
|
this._endDate = energyData.end || endOfToday();
|
||||||
|
const dayDifference = differenceInDays(this._endDate, this._startDate);
|
||||||
|
this._period =
|
||||||
|
dayDifference < 1
|
||||||
|
? "day"
|
||||||
|
: dayDifference === 6
|
||||||
|
? "week"
|
||||||
|
: dayDifference > 26 && dayDifference < 31 // 28, 29, 30 or 31 days in a month
|
||||||
|
? "month"
|
||||||
|
: dayDifference === 364 || dayDifference === 365 // Leap year
|
||||||
|
? "year"
|
||||||
|
: undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
static get styles(): CSSResultGroup {
|
static get styles(): CSSResultGroup {
|
||||||
@ -96,16 +208,20 @@ export class HuiEnergyPeriodSelector extends SubscribeMixin(LitElement) {
|
|||||||
text-align: center;
|
text-align: center;
|
||||||
font-size: 20px;
|
font-size: 20px;
|
||||||
}
|
}
|
||||||
|
:host {
|
||||||
|
--mdc-button-outline-color: currentColor;
|
||||||
|
--primary-color: currentColor;
|
||||||
|
--mdc-theme-primary: currentColor;
|
||||||
|
--mdc-button-disabled-outline-color: var(--disabled-text-color);
|
||||||
|
--mdc-button-disabled-ink-color: var(--disabled-text-color);
|
||||||
|
--mdc-icon-button-ripple-opacity: 0.2;
|
||||||
|
}
|
||||||
mwc-icon-button {
|
mwc-icon-button {
|
||||||
--mdc-icon-button-size: 28px;
|
--mdc-icon-button-size: 28px;
|
||||||
}
|
}
|
||||||
mwc-button {
|
mwc-button,
|
||||||
|
ha-button-toggle-group {
|
||||||
padding-left: 8px;
|
padding-left: 8px;
|
||||||
--mdc-theme-primary: currentColor;
|
|
||||||
--mdc-button-outline-color: currentColor;
|
|
||||||
|
|
||||||
--mdc-button-disabled-outline-color: var(--disabled-text-color);
|
|
||||||
--mdc-button-disabled-ink-color: var(--disabled-text-color);
|
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
@ -130,7 +130,7 @@ export type FullCalendarView =
|
|||||||
|
|
||||||
export interface ToggleButton {
|
export interface ToggleButton {
|
||||||
label: string;
|
label: string;
|
||||||
iconPath: string;
|
iconPath?: string;
|
||||||
value: string;
|
value: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user