mirror of
https://github.com/home-assistant/frontend.git
synced 2025-07-26 02:36:37 +00:00
Fix trailing energy gaps, refactor chart options (#19117)
* Fix trailing gap on energy graph cards * Use date methods instead of unix time calculation * Fix trailing energy gaps, refactor chart options --------- Co-authored-by: Till Fleisch <till@fleisch.dev>
This commit is contained in:
parent
bded31b311
commit
df54687de1
168
src/panels/lovelace/cards/energy/common/energy-chart-options.ts
Normal file
168
src/panels/lovelace/cards/energy/common/energy-chart-options.ts
Normal file
@ -0,0 +1,168 @@
|
|||||||
|
import { ChartOptions } from "chart.js";
|
||||||
|
import { HassConfig } from "home-assistant-js-websocket";
|
||||||
|
import {
|
||||||
|
addHours,
|
||||||
|
subHours,
|
||||||
|
differenceInDays,
|
||||||
|
differenceInHours,
|
||||||
|
} from "date-fns/esm";
|
||||||
|
import { FrontendLocaleData } from "../../../../../data/translation";
|
||||||
|
import {
|
||||||
|
formatNumber,
|
||||||
|
numberFormatToLocale,
|
||||||
|
} from "../../../../../common/number/format_number";
|
||||||
|
import { formatDateVeryShort } from "../../../../../common/datetime/format_date";
|
||||||
|
import { formatTime } from "../../../../../common/datetime/format_time";
|
||||||
|
|
||||||
|
export function getSuggestedMax(dayDifference: number, end: Date): number {
|
||||||
|
let suggestedMax = new Date(end);
|
||||||
|
|
||||||
|
// Sometimes around DST we get a time of 0:59 instead of 23:59 as expected.
|
||||||
|
// Correct for this when showing days/months so we don't get an extra day.
|
||||||
|
if (dayDifference > 2 && suggestedMax.getHours() === 0) {
|
||||||
|
suggestedMax = subHours(suggestedMax, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
suggestedMax.setMinutes(0, 0, 0);
|
||||||
|
if (dayDifference > 35) {
|
||||||
|
suggestedMax.setDate(1);
|
||||||
|
}
|
||||||
|
if (dayDifference > 2) {
|
||||||
|
suggestedMax.setHours(0);
|
||||||
|
}
|
||||||
|
return suggestedMax.getTime();
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getCommonOptions(
|
||||||
|
start: Date,
|
||||||
|
end: Date,
|
||||||
|
locale: FrontendLocaleData,
|
||||||
|
config: HassConfig,
|
||||||
|
unit?: string,
|
||||||
|
compareStart?: Date,
|
||||||
|
compareEnd?: Date
|
||||||
|
): ChartOptions {
|
||||||
|
const dayDifference = differenceInDays(end, start);
|
||||||
|
const compare = compareStart !== undefined && compareEnd !== undefined;
|
||||||
|
if (compare && dayDifference <= 35) {
|
||||||
|
const difference = differenceInHours(end, start);
|
||||||
|
const differenceCompare = differenceInHours(compareEnd!, compareStart!);
|
||||||
|
// If the compare period doesn't match the main period, adjust them to match
|
||||||
|
if (differenceCompare > difference) {
|
||||||
|
end = addHours(end, differenceCompare - difference);
|
||||||
|
} else if (difference > differenceCompare) {
|
||||||
|
compareEnd = addHours(compareEnd!, difference - differenceCompare);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const options: ChartOptions = {
|
||||||
|
parsing: false,
|
||||||
|
animation: false,
|
||||||
|
interaction: {
|
||||||
|
mode: "x",
|
||||||
|
},
|
||||||
|
scales: {
|
||||||
|
x: {
|
||||||
|
type: "time",
|
||||||
|
suggestedMin: start.getTime(),
|
||||||
|
max: getSuggestedMax(dayDifference, end),
|
||||||
|
adapters: {
|
||||||
|
date: {
|
||||||
|
locale,
|
||||||
|
config,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
ticks: {
|
||||||
|
maxRotation: 0,
|
||||||
|
sampleSize: 5,
|
||||||
|
autoSkipPadding: 20,
|
||||||
|
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",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
y: {
|
||||||
|
stacked: true,
|
||||||
|
type: "linear",
|
||||||
|
title: {
|
||||||
|
display: true,
|
||||||
|
text: unit,
|
||||||
|
},
|
||||||
|
ticks: {
|
||||||
|
beginAtZero: true,
|
||||||
|
callback: (value) => formatNumber(Math.abs(value), locale),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
plugins: {
|
||||||
|
tooltip: {
|
||||||
|
position: "nearest",
|
||||||
|
filter: (val) => val.formattedValue !== "0",
|
||||||
|
itemSort: function (a, b) {
|
||||||
|
return b.datasetIndex - a.datasetIndex;
|
||||||
|
},
|
||||||
|
callbacks: {
|
||||||
|
title: (datasets) => {
|
||||||
|
if (dayDifference > 0) {
|
||||||
|
return datasets[0].label;
|
||||||
|
}
|
||||||
|
const date = new Date(datasets[0].parsed.x);
|
||||||
|
return `${
|
||||||
|
compare ? `${formatDateVeryShort(date, locale, config)}: ` : ""
|
||||||
|
}${formatTime(date, locale, config)} – ${formatTime(
|
||||||
|
addHours(date, 1),
|
||||||
|
locale,
|
||||||
|
config
|
||||||
|
)}`;
|
||||||
|
},
|
||||||
|
label: (context) =>
|
||||||
|
`${context.dataset.label}: ${formatNumber(
|
||||||
|
context.parsed.y,
|
||||||
|
locale
|
||||||
|
)} ${unit}`,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
filler: {
|
||||||
|
propagate: false,
|
||||||
|
},
|
||||||
|
legend: {
|
||||||
|
display: false,
|
||||||
|
labels: {
|
||||||
|
usePointStyle: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
elements: {
|
||||||
|
bar: { borderWidth: 1.5, borderRadius: 4 },
|
||||||
|
point: {
|
||||||
|
hitRadius: 50,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
// @ts-expect-error
|
||||||
|
locale: numberFormatToLocale(locale),
|
||||||
|
};
|
||||||
|
if (compare) {
|
||||||
|
options.scales!.xAxisCompare = {
|
||||||
|
...(options.scales!.x as Record<string, any>),
|
||||||
|
suggestedMin: compareStart!.getTime(),
|
||||||
|
max: getSuggestedMax(dayDifference, compareEnd!),
|
||||||
|
display: false,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return options;
|
||||||
|
}
|
@ -4,14 +4,7 @@ import {
|
|||||||
ChartOptions,
|
ChartOptions,
|
||||||
ScatterDataPoint,
|
ScatterDataPoint,
|
||||||
} from "chart.js";
|
} from "chart.js";
|
||||||
import {
|
import { endOfToday, isToday, startOfToday } from "date-fns";
|
||||||
addHours,
|
|
||||||
differenceInDays,
|
|
||||||
differenceInHours,
|
|
||||||
endOfToday,
|
|
||||||
isToday,
|
|
||||||
startOfToday,
|
|
||||||
} from "date-fns";
|
|
||||||
import { HassConfig, UnsubscribeFunc } from "home-assistant-js-websocket";
|
import { HassConfig, UnsubscribeFunc } from "home-assistant-js-websocket";
|
||||||
import {
|
import {
|
||||||
css,
|
css,
|
||||||
@ -31,12 +24,7 @@ import {
|
|||||||
rgb2lab,
|
rgb2lab,
|
||||||
} from "../../../../common/color/convert-color";
|
} from "../../../../common/color/convert-color";
|
||||||
import { labBrighten, labDarken } from "../../../../common/color/lab";
|
import { labBrighten, labDarken } from "../../../../common/color/lab";
|
||||||
import { formatDateVeryShort } from "../../../../common/datetime/format_date";
|
import { formatNumber } from "../../../../common/number/format_number";
|
||||||
import { formatTime } from "../../../../common/datetime/format_time";
|
|
||||||
import {
|
|
||||||
formatNumber,
|
|
||||||
numberFormatToLocale,
|
|
||||||
} from "../../../../common/number/format_number";
|
|
||||||
import "../../../../components/chart/ha-chart-base";
|
import "../../../../components/chart/ha-chart-base";
|
||||||
import "../../../../components/ha-card";
|
import "../../../../components/ha-card";
|
||||||
import {
|
import {
|
||||||
@ -56,6 +44,7 @@ import { HomeAssistant } from "../../../../types";
|
|||||||
import { LovelaceCard } from "../../types";
|
import { LovelaceCard } from "../../types";
|
||||||
import { EnergyGasGraphCardConfig } from "../types";
|
import { EnergyGasGraphCardConfig } from "../types";
|
||||||
import { hasConfigChanged } from "../../common/has-changed";
|
import { hasConfigChanged } from "../../common/has-changed";
|
||||||
|
import { getCommonOptions } from "./common/energy-chart-options";
|
||||||
|
|
||||||
@customElement("hui-energy-gas-graph-card")
|
@customElement("hui-energy-gas-graph-card")
|
||||||
export class HuiEnergyGasGraphCard
|
export class HuiEnergyGasGraphCard
|
||||||
@ -159,105 +148,23 @@ export class HuiEnergyGasGraphCard
|
|||||||
compareStart?: Date,
|
compareStart?: Date,
|
||||||
compareEnd?: Date
|
compareEnd?: Date
|
||||||
): ChartOptions => {
|
): ChartOptions => {
|
||||||
const dayDifference = differenceInDays(end, start);
|
const commonOptions = getCommonOptions(
|
||||||
const compare = compareStart !== undefined && compareEnd !== undefined;
|
start,
|
||||||
if (compare) {
|
end,
|
||||||
const difference = differenceInHours(end, start);
|
|
||||||
const differenceCompare = differenceInHours(compareEnd!, compareStart!);
|
|
||||||
// If the compare period doesn't match the main period, adjust them to match
|
|
||||||
if (differenceCompare > difference) {
|
|
||||||
end = addHours(end, differenceCompare - difference);
|
|
||||||
} else if (difference > differenceCompare) {
|
|
||||||
compareEnd = addHours(compareEnd!, difference - differenceCompare);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const options: ChartOptions = {
|
|
||||||
parsing: false,
|
|
||||||
animation: false,
|
|
||||||
interaction: {
|
|
||||||
mode: "x",
|
|
||||||
},
|
|
||||||
scales: {
|
|
||||||
x: {
|
|
||||||
type: "time",
|
|
||||||
suggestedMin: start.getTime(),
|
|
||||||
suggestedMax: end.getTime(),
|
|
||||||
adapters: {
|
|
||||||
date: {
|
|
||||||
locale,
|
locale,
|
||||||
config,
|
config,
|
||||||
},
|
unit,
|
||||||
},
|
compareStart,
|
||||||
ticks: {
|
compareEnd
|
||||||
maxRotation: 0,
|
);
|
||||||
sampleSize: 5,
|
const options: ChartOptions = {
|
||||||
autoSkipPadding: 20,
|
...commonOptions,
|
||||||
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: unit,
|
|
||||||
},
|
|
||||||
ticks: {
|
|
||||||
beginAtZero: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
plugins: {
|
plugins: {
|
||||||
|
...commonOptions.plugins,
|
||||||
tooltip: {
|
tooltip: {
|
||||||
position: "nearest",
|
...commonOptions.plugins!.tooltip,
|
||||||
filter: (val) => val.formattedValue !== "0",
|
|
||||||
itemSort: function (a, b) {
|
|
||||||
return b.datasetIndex - a.datasetIndex;
|
|
||||||
},
|
|
||||||
callbacks: {
|
callbacks: {
|
||||||
title: (datasets) => {
|
...commonOptions.plugins!.tooltip!.callbacks,
|
||||||
if (dayDifference > 0) {
|
|
||||||
return datasets[0].label;
|
|
||||||
}
|
|
||||||
const date = new Date(datasets[0].parsed.x);
|
|
||||||
return `${
|
|
||||||
compare
|
|
||||||
? `${formatDateVeryShort(date, locale, config)}: `
|
|
||||||
: ""
|
|
||||||
}${formatTime(date, locale, config)} – ${formatTime(
|
|
||||||
addHours(date, 1),
|
|
||||||
locale,
|
|
||||||
config
|
|
||||||
)}`;
|
|
||||||
},
|
|
||||||
label: (context) =>
|
|
||||||
`${context.dataset.label}: ${formatNumber(
|
|
||||||
context.parsed.y,
|
|
||||||
locale
|
|
||||||
)} ${unit}`,
|
|
||||||
footer: (contexts) => {
|
footer: (contexts) => {
|
||||||
if (contexts.length < 2) {
|
if (contexts.length < 2) {
|
||||||
return [];
|
return [];
|
||||||
@ -278,33 +185,8 @@ export class HuiEnergyGasGraphCard
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
filler: {
|
|
||||||
propagate: false,
|
|
||||||
},
|
},
|
||||||
legend: {
|
|
||||||
display: false,
|
|
||||||
labels: {
|
|
||||||
usePointStyle: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
elements: {
|
|
||||||
bar: { borderWidth: 1.5, borderRadius: 4 },
|
|
||||||
point: {
|
|
||||||
hitRadius: 50,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
// @ts-expect-error
|
|
||||||
locale: numberFormatToLocale(locale),
|
|
||||||
};
|
};
|
||||||
if (compare) {
|
|
||||||
options.scales!.xAxisCompare = {
|
|
||||||
...(options.scales!.x as Record<string, any>),
|
|
||||||
suggestedMin: compareStart!.getTime(),
|
|
||||||
suggestedMax: compareEnd!.getTime(),
|
|
||||||
display: false,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
return options;
|
return options;
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
@ -5,9 +5,7 @@ import {
|
|||||||
ScatterDataPoint,
|
ScatterDataPoint,
|
||||||
} from "chart.js";
|
} from "chart.js";
|
||||||
import {
|
import {
|
||||||
addHours,
|
|
||||||
differenceInDays,
|
differenceInDays,
|
||||||
differenceInHours,
|
|
||||||
endOfToday,
|
endOfToday,
|
||||||
isToday,
|
isToday,
|
||||||
startOfToday,
|
startOfToday,
|
||||||
@ -31,12 +29,7 @@ import {
|
|||||||
rgb2lab,
|
rgb2lab,
|
||||||
} from "../../../../common/color/convert-color";
|
} from "../../../../common/color/convert-color";
|
||||||
import { labBrighten, labDarken } from "../../../../common/color/lab";
|
import { labBrighten, labDarken } from "../../../../common/color/lab";
|
||||||
import { formatDateVeryShort } from "../../../../common/datetime/format_date";
|
import { formatNumber } from "../../../../common/number/format_number";
|
||||||
import { formatTime } from "../../../../common/datetime/format_time";
|
|
||||||
import {
|
|
||||||
formatNumber,
|
|
||||||
numberFormatToLocale,
|
|
||||||
} from "../../../../common/number/format_number";
|
|
||||||
import "../../../../components/chart/ha-chart-base";
|
import "../../../../components/chart/ha-chart-base";
|
||||||
import "../../../../components/ha-card";
|
import "../../../../components/ha-card";
|
||||||
import {
|
import {
|
||||||
@ -57,6 +50,7 @@ import { HomeAssistant } from "../../../../types";
|
|||||||
import { LovelaceCard } from "../../types";
|
import { LovelaceCard } from "../../types";
|
||||||
import { EnergySolarGraphCardConfig } from "../types";
|
import { EnergySolarGraphCardConfig } from "../types";
|
||||||
import { hasConfigChanged } from "../../common/has-changed";
|
import { hasConfigChanged } from "../../common/has-changed";
|
||||||
|
import { getCommonOptions } from "./common/energy-chart-options";
|
||||||
|
|
||||||
@customElement("hui-energy-solar-graph-card")
|
@customElement("hui-energy-solar-graph-card")
|
||||||
export class HuiEnergySolarGraphCard
|
export class HuiEnergySolarGraphCard
|
||||||
@ -156,104 +150,23 @@ export class HuiEnergySolarGraphCard
|
|||||||
compareStart?: Date,
|
compareStart?: Date,
|
||||||
compareEnd?: Date
|
compareEnd?: Date
|
||||||
): ChartOptions => {
|
): ChartOptions => {
|
||||||
const dayDifference = differenceInDays(end, start);
|
const commonOptions = getCommonOptions(
|
||||||
const compare = compareStart !== undefined && compareEnd !== undefined;
|
start,
|
||||||
if (compare) {
|
end,
|
||||||
const difference = differenceInHours(end, start);
|
|
||||||
const differenceCompare = differenceInHours(compareEnd!, compareStart!);
|
|
||||||
// If the compare period doesn't match the main period, adjust them to match
|
|
||||||
if (differenceCompare > difference) {
|
|
||||||
end = addHours(end, differenceCompare - difference);
|
|
||||||
} else if (difference > differenceCompare) {
|
|
||||||
compareEnd = addHours(compareEnd!, difference - differenceCompare);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const options: ChartOptions = {
|
|
||||||
parsing: false,
|
|
||||||
animation: false,
|
|
||||||
interaction: {
|
|
||||||
mode: "x",
|
|
||||||
},
|
|
||||||
scales: {
|
|
||||||
x: {
|
|
||||||
type: "time",
|
|
||||||
suggestedMin: start.getTime(),
|
|
||||||
suggestedMax: end.getTime(),
|
|
||||||
adapters: {
|
|
||||||
date: {
|
|
||||||
locale,
|
locale,
|
||||||
config,
|
config,
|
||||||
},
|
"kWh",
|
||||||
},
|
compareStart,
|
||||||
ticks: {
|
compareEnd
|
||||||
maxRotation: 0,
|
);
|
||||||
sampleSize: 5,
|
const options: ChartOptions = {
|
||||||
autoSkipPadding: 20,
|
...commonOptions,
|
||||||
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",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
y: {
|
|
||||||
stacked: true,
|
|
||||||
type: "linear",
|
|
||||||
title: {
|
|
||||||
display: true,
|
|
||||||
text: "kWh",
|
|
||||||
},
|
|
||||||
ticks: {
|
|
||||||
beginAtZero: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
plugins: {
|
plugins: {
|
||||||
|
...commonOptions.plugins,
|
||||||
tooltip: {
|
tooltip: {
|
||||||
position: "nearest",
|
...commonOptions.plugins!.tooltip,
|
||||||
filter: (val) => val.formattedValue !== "0",
|
|
||||||
itemSort: function (a, b) {
|
|
||||||
return b.datasetIndex - a.datasetIndex;
|
|
||||||
},
|
|
||||||
callbacks: {
|
callbacks: {
|
||||||
title: (datasets) => {
|
...commonOptions.plugins!.tooltip!.callbacks,
|
||||||
if (dayDifference > 0) {
|
|
||||||
return datasets[0].label;
|
|
||||||
}
|
|
||||||
const date = new Date(datasets[0].parsed.x);
|
|
||||||
return `${
|
|
||||||
compare
|
|
||||||
? `${formatDateVeryShort(date, locale, config)}: `
|
|
||||||
: ""
|
|
||||||
}${formatTime(date, locale, config)} – ${formatTime(
|
|
||||||
addHours(date, 1),
|
|
||||||
locale,
|
|
||||||
config
|
|
||||||
)}`;
|
|
||||||
},
|
|
||||||
label: (context) =>
|
|
||||||
`${context.dataset.label}: ${formatNumber(
|
|
||||||
context.parsed.y,
|
|
||||||
locale
|
|
||||||
)} kWh`,
|
|
||||||
footer: (contexts) => {
|
footer: (contexts) => {
|
||||||
const production_contexts = contexts.filter(
|
const production_contexts = contexts.filter(
|
||||||
(c) => c.dataset?.stack === "solar"
|
(c) => c.dataset?.stack === "solar"
|
||||||
@ -277,15 +190,6 @@ export class HuiEnergySolarGraphCard
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
filler: {
|
|
||||||
propagate: false,
|
|
||||||
},
|
|
||||||
legend: {
|
|
||||||
display: false,
|
|
||||||
labels: {
|
|
||||||
usePointStyle: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
elements: {
|
elements: {
|
||||||
line: {
|
line: {
|
||||||
@ -297,17 +201,7 @@ export class HuiEnergySolarGraphCard
|
|||||||
hitRadius: 5,
|
hitRadius: 5,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
// @ts-expect-error
|
|
||||||
locale: numberFormatToLocale(locale),
|
|
||||||
};
|
};
|
||||||
if (compare) {
|
|
||||||
options.scales!.xAxisCompare = {
|
|
||||||
...(options.scales!.x as Record<string, any>),
|
|
||||||
suggestedMin: compareStart!.getTime(),
|
|
||||||
suggestedMax: compareEnd!.getTime(),
|
|
||||||
display: false,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
return options;
|
return options;
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
@ -4,14 +4,7 @@ import {
|
|||||||
ChartOptions,
|
ChartOptions,
|
||||||
ScatterDataPoint,
|
ScatterDataPoint,
|
||||||
} from "chart.js";
|
} from "chart.js";
|
||||||
import {
|
import { endOfToday, isToday, startOfToday } from "date-fns/esm";
|
||||||
addHours,
|
|
||||||
differenceInDays,
|
|
||||||
differenceInHours,
|
|
||||||
endOfToday,
|
|
||||||
isToday,
|
|
||||||
startOfToday,
|
|
||||||
} from "date-fns/esm";
|
|
||||||
import { HassConfig, UnsubscribeFunc } from "home-assistant-js-websocket";
|
import { HassConfig, UnsubscribeFunc } from "home-assistant-js-websocket";
|
||||||
import {
|
import {
|
||||||
css,
|
css,
|
||||||
@ -31,12 +24,7 @@ import {
|
|||||||
rgb2lab,
|
rgb2lab,
|
||||||
} from "../../../../common/color/convert-color";
|
} from "../../../../common/color/convert-color";
|
||||||
import { labBrighten, labDarken } from "../../../../common/color/lab";
|
import { labBrighten, labDarken } from "../../../../common/color/lab";
|
||||||
import { formatDateVeryShort } from "../../../../common/datetime/format_date";
|
import { formatNumber } from "../../../../common/number/format_number";
|
||||||
import { formatTime } from "../../../../common/datetime/format_time";
|
|
||||||
import {
|
|
||||||
formatNumber,
|
|
||||||
numberFormatToLocale,
|
|
||||||
} from "../../../../common/number/format_number";
|
|
||||||
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";
|
||||||
@ -51,6 +39,7 @@ import { HomeAssistant } from "../../../../types";
|
|||||||
import { LovelaceCard } from "../../types";
|
import { LovelaceCard } from "../../types";
|
||||||
import { EnergyUsageGraphCardConfig } from "../types";
|
import { EnergyUsageGraphCardConfig } from "../types";
|
||||||
import { hasConfigChanged } from "../../common/has-changed";
|
import { hasConfigChanged } from "../../common/has-changed";
|
||||||
|
import { getCommonOptions } from "./common/energy-chart-options";
|
||||||
|
|
||||||
interface ColorSet {
|
interface ColorSet {
|
||||||
base: string;
|
base: string;
|
||||||
@ -155,81 +144,21 @@ export class HuiEnergyUsageGraphCard
|
|||||||
compareStart?: Date,
|
compareStart?: Date,
|
||||||
compareEnd?: Date
|
compareEnd?: Date
|
||||||
): ChartOptions => {
|
): ChartOptions => {
|
||||||
const dayDifference = differenceInDays(end, start);
|
const commonOptions = getCommonOptions(
|
||||||
const compare = compareStart !== undefined && compareEnd !== undefined;
|
start,
|
||||||
if (compare) {
|
end,
|
||||||
const difference = differenceInHours(end, start);
|
|
||||||
const differenceCompare = differenceInHours(compareEnd!, compareStart!);
|
|
||||||
// If the compare period doesn't match the main period, adjust them to match
|
|
||||||
if (differenceCompare > difference) {
|
|
||||||
end = addHours(end, differenceCompare - difference);
|
|
||||||
} else if (difference > differenceCompare) {
|
|
||||||
compareEnd = addHours(compareEnd!, difference - differenceCompare);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const options: ChartOptions = {
|
|
||||||
parsing: false,
|
|
||||||
animation: false,
|
|
||||||
interaction: {
|
|
||||||
mode: "x",
|
|
||||||
},
|
|
||||||
scales: {
|
|
||||||
x: {
|
|
||||||
type: "time",
|
|
||||||
suggestedMin: start.getTime(),
|
|
||||||
suggestedMax: end.getTime(),
|
|
||||||
adapters: {
|
|
||||||
date: {
|
|
||||||
locale,
|
locale,
|
||||||
config,
|
config,
|
||||||
},
|
"kWh",
|
||||||
},
|
compareStart,
|
||||||
ticks: {
|
compareEnd
|
||||||
maxRotation: 0,
|
);
|
||||||
sampleSize: 5,
|
const options: ChartOptions = {
|
||||||
autoSkipPadding: 20,
|
...commonOptions,
|
||||||
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",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
y: {
|
|
||||||
stacked: true,
|
|
||||||
type: "linear",
|
|
||||||
title: {
|
|
||||||
display: true,
|
|
||||||
text: "kWh",
|
|
||||||
},
|
|
||||||
ticks: {
|
|
||||||
beginAtZero: true,
|
|
||||||
callback: (value) => formatNumber(Math.abs(value), locale),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
plugins: {
|
plugins: {
|
||||||
|
...commonOptions.plugins,
|
||||||
tooltip: {
|
tooltip: {
|
||||||
position: "nearest",
|
...commonOptions.plugins!.tooltip,
|
||||||
filter: (val) => val.formattedValue !== "0",
|
|
||||||
itemSort: function (a: any, b: any) {
|
itemSort: function (a: any, b: any) {
|
||||||
if (a.raw?.y > 0 && b.raw?.y < 0) {
|
if (a.raw?.y > 0 && b.raw?.y < 0) {
|
||||||
return -1;
|
return -1;
|
||||||
@ -243,26 +172,7 @@ export class HuiEnergyUsageGraphCard
|
|||||||
return a.datasetIndex - b.datasetIndex;
|
return a.datasetIndex - b.datasetIndex;
|
||||||
},
|
},
|
||||||
callbacks: {
|
callbacks: {
|
||||||
title: (datasets) => {
|
...commonOptions.plugins!.tooltip!.callbacks,
|
||||||
if (dayDifference > 0) {
|
|
||||||
return datasets[0].label;
|
|
||||||
}
|
|
||||||
const date = new Date(datasets[0].parsed.x);
|
|
||||||
return `${
|
|
||||||
compare
|
|
||||||
? `${formatDateVeryShort(date, locale, config)}: `
|
|
||||||
: ""
|
|
||||||
}${formatTime(date, locale, config)} – ${formatTime(
|
|
||||||
addHours(date, 1),
|
|
||||||
locale,
|
|
||||||
config
|
|
||||||
)}`;
|
|
||||||
},
|
|
||||||
label: (context) =>
|
|
||||||
`${context.dataset.label}: ${formatNumber(
|
|
||||||
Math.abs(context.parsed.y),
|
|
||||||
locale
|
|
||||||
)} kWh`,
|
|
||||||
footer: (contexts) => {
|
footer: (contexts) => {
|
||||||
let totalConsumed = 0;
|
let totalConsumed = 0;
|
||||||
let totalReturned = 0;
|
let totalReturned = 0;
|
||||||
@ -292,33 +202,8 @@ export class HuiEnergyUsageGraphCard
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
filler: {
|
|
||||||
propagate: false,
|
|
||||||
},
|
},
|
||||||
legend: {
|
|
||||||
display: false,
|
|
||||||
labels: {
|
|
||||||
usePointStyle: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
elements: {
|
|
||||||
bar: { borderWidth: 1.5, borderRadius: 4 },
|
|
||||||
point: {
|
|
||||||
hitRadius: 50,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
// @ts-expect-error
|
|
||||||
locale: numberFormatToLocale(locale),
|
|
||||||
};
|
};
|
||||||
if (compare) {
|
|
||||||
options.scales!.xAxisCompare = {
|
|
||||||
...(options.scales!.x as Record<string, any>),
|
|
||||||
suggestedMin: compareStart!.getTime(),
|
|
||||||
suggestedMax: compareEnd!.getTime(),
|
|
||||||
display: false,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
return options;
|
return options;
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
@ -4,14 +4,7 @@ import {
|
|||||||
ChartOptions,
|
ChartOptions,
|
||||||
ScatterDataPoint,
|
ScatterDataPoint,
|
||||||
} from "chart.js";
|
} from "chart.js";
|
||||||
import {
|
import { endOfToday, isToday, startOfToday } from "date-fns";
|
||||||
addHours,
|
|
||||||
differenceInDays,
|
|
||||||
differenceInHours,
|
|
||||||
endOfToday,
|
|
||||||
isToday,
|
|
||||||
startOfToday,
|
|
||||||
} from "date-fns";
|
|
||||||
import { HassConfig, UnsubscribeFunc } from "home-assistant-js-websocket";
|
import { HassConfig, UnsubscribeFunc } from "home-assistant-js-websocket";
|
||||||
import {
|
import {
|
||||||
css,
|
css,
|
||||||
@ -31,12 +24,7 @@ import {
|
|||||||
rgb2lab,
|
rgb2lab,
|
||||||
} from "../../../../common/color/convert-color";
|
} from "../../../../common/color/convert-color";
|
||||||
import { labBrighten, labDarken } from "../../../../common/color/lab";
|
import { labBrighten, labDarken } from "../../../../common/color/lab";
|
||||||
import { formatDateVeryShort } from "../../../../common/datetime/format_date";
|
import { formatNumber } from "../../../../common/number/format_number";
|
||||||
import { formatTime } from "../../../../common/datetime/format_time";
|
|
||||||
import {
|
|
||||||
formatNumber,
|
|
||||||
numberFormatToLocale,
|
|
||||||
} from "../../../../common/number/format_number";
|
|
||||||
import "../../../../components/chart/ha-chart-base";
|
import "../../../../components/chart/ha-chart-base";
|
||||||
import "../../../../components/ha-card";
|
import "../../../../components/ha-card";
|
||||||
import {
|
import {
|
||||||
@ -56,6 +44,7 @@ import { HomeAssistant } from "../../../../types";
|
|||||||
import { LovelaceCard } from "../../types";
|
import { LovelaceCard } from "../../types";
|
||||||
import { EnergyWaterGraphCardConfig } from "../types";
|
import { EnergyWaterGraphCardConfig } from "../types";
|
||||||
import { hasConfigChanged } from "../../common/has-changed";
|
import { hasConfigChanged } from "../../common/has-changed";
|
||||||
|
import { getCommonOptions } from "./common/energy-chart-options";
|
||||||
|
|
||||||
@customElement("hui-energy-water-graph-card")
|
@customElement("hui-energy-water-graph-card")
|
||||||
export class HuiEnergyWaterGraphCard
|
export class HuiEnergyWaterGraphCard
|
||||||
@ -159,105 +148,23 @@ export class HuiEnergyWaterGraphCard
|
|||||||
compareStart?: Date,
|
compareStart?: Date,
|
||||||
compareEnd?: Date
|
compareEnd?: Date
|
||||||
): ChartOptions => {
|
): ChartOptions => {
|
||||||
const dayDifference = differenceInDays(end, start);
|
const commonOptions = getCommonOptions(
|
||||||
const compare = compareStart !== undefined && compareEnd !== undefined;
|
start,
|
||||||
if (compare) {
|
end,
|
||||||
const difference = differenceInHours(end, start);
|
|
||||||
const differenceCompare = differenceInHours(compareEnd!, compareStart!);
|
|
||||||
// If the compare period doesn't match the main period, adjust them to match
|
|
||||||
if (differenceCompare > difference) {
|
|
||||||
end = addHours(end, differenceCompare - difference);
|
|
||||||
} else if (difference > differenceCompare) {
|
|
||||||
compareEnd = addHours(compareEnd!, difference - differenceCompare);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const options: ChartOptions = {
|
|
||||||
parsing: false,
|
|
||||||
animation: false,
|
|
||||||
interaction: {
|
|
||||||
mode: "x",
|
|
||||||
},
|
|
||||||
scales: {
|
|
||||||
x: {
|
|
||||||
type: "time",
|
|
||||||
suggestedMin: start.getTime(),
|
|
||||||
suggestedMax: end.getTime(),
|
|
||||||
adapters: {
|
|
||||||
date: {
|
|
||||||
locale,
|
locale,
|
||||||
config,
|
config,
|
||||||
},
|
unit,
|
||||||
},
|
compareStart,
|
||||||
ticks: {
|
compareEnd
|
||||||
maxRotation: 0,
|
);
|
||||||
sampleSize: 5,
|
const options: ChartOptions = {
|
||||||
autoSkipPadding: 20,
|
...commonOptions,
|
||||||
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: unit,
|
|
||||||
},
|
|
||||||
ticks: {
|
|
||||||
beginAtZero: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
plugins: {
|
plugins: {
|
||||||
|
...commonOptions.plugins,
|
||||||
tooltip: {
|
tooltip: {
|
||||||
position: "nearest",
|
...commonOptions.plugins!.tooltip,
|
||||||
filter: (val) => val.formattedValue !== "0",
|
|
||||||
itemSort: function (a, b) {
|
|
||||||
return b.datasetIndex - a.datasetIndex;
|
|
||||||
},
|
|
||||||
callbacks: {
|
callbacks: {
|
||||||
title: (datasets) => {
|
...commonOptions.plugins!.tooltip!.callbacks,
|
||||||
if (dayDifference > 0) {
|
|
||||||
return datasets[0].label;
|
|
||||||
}
|
|
||||||
const date = new Date(datasets[0].parsed.x);
|
|
||||||
return `${
|
|
||||||
compare
|
|
||||||
? `${formatDateVeryShort(date, locale, config)}: `
|
|
||||||
: ""
|
|
||||||
}${formatTime(date, locale, config)} – ${formatTime(
|
|
||||||
addHours(date, 1),
|
|
||||||
locale,
|
|
||||||
config
|
|
||||||
)}`;
|
|
||||||
},
|
|
||||||
label: (context) =>
|
|
||||||
`${context.dataset.label}: ${formatNumber(
|
|
||||||
context.parsed.y,
|
|
||||||
locale
|
|
||||||
)} ${unit}`,
|
|
||||||
footer: (contexts) => {
|
footer: (contexts) => {
|
||||||
if (contexts.length < 2) {
|
if (contexts.length < 2) {
|
||||||
return [];
|
return [];
|
||||||
@ -278,33 +185,8 @@ export class HuiEnergyWaterGraphCard
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
filler: {
|
|
||||||
propagate: false,
|
|
||||||
},
|
},
|
||||||
legend: {
|
|
||||||
display: false,
|
|
||||||
labels: {
|
|
||||||
usePointStyle: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
elements: {
|
|
||||||
bar: { borderWidth: 1.5, borderRadius: 4 },
|
|
||||||
point: {
|
|
||||||
hitRadius: 50,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
// @ts-expect-error
|
|
||||||
locale: numberFormatToLocale(locale),
|
|
||||||
};
|
};
|
||||||
if (compare) {
|
|
||||||
options.scales!.xAxisCompare = {
|
|
||||||
...(options.scales!.x as Record<string, any>),
|
|
||||||
suggestedMin: compareStart!.getTime(),
|
|
||||||
suggestedMax: compareEnd!.getTime(),
|
|
||||||
display: false,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
return options;
|
return options;
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
Loading…
x
Reference in New Issue
Block a user