mirror of
https://github.com/home-assistant/frontend.git
synced 2026-02-15 07:25:54 +00:00
Improve energy dashboard monthly/this-quarter chart time axes (#29435)
* Add splitNumber option to monthly ECharts When there are a small number of bars (<=3) for monthly data, set the splitNumber parameter to force the date x-axis to show whole months. * Add axis tick fomratting for short months This ensures that the month format is consistent between 2/3 month and longer ranges. * Avoid calling getSuggestedMax twice * Fix another case of power chart cutting off last hour of data The previous fix only solved the problem for 5-minute data, not hourly or daily. This should solve the issue regardless, and allows the energy chart to have other line-based plots in the future. * Update other uses of getSuggestedMax() * Fix statistics-chart Last Period Rendering 1. When appending the "current state" value, if the current time intersects with the final period, we can end up with the chart folding back on itself. This is fixed by ensuring for the final period we push the earlier of the statistic end time and the display end time (which is in turn limited to now). 2. Always close off the last data point at the chart end time. Otherwise for line charts, the final period doesn't get rendered. * Remove unused monthStyle formatter. Co-authored-by: Petar Petrov <MindFreeze@users.noreply.github.com> * Rename getSuggestedMax function parameter in energy chart Co-authored-by: Petar Petrov <MindFreeze@users.noreply.github.com> * Document magic numbers in montly energy chart * Make padding a constant for clarity. * Explain the purpose of splitNumber. --------- Co-authored-by: Petar Petrov <MindFreeze@users.noreply.github.com>
This commit is contained in:
@@ -572,6 +572,7 @@ export class StatisticsChart extends LitElement {
|
||||
let firstSum: number | null | undefined = null;
|
||||
stats.forEach((stat) => {
|
||||
const startDate = new Date(stat.start);
|
||||
const endDate = new Date(stat.end);
|
||||
if (prevDate === startDate) {
|
||||
return;
|
||||
}
|
||||
@@ -601,10 +602,25 @@ export class StatisticsChart extends LitElement {
|
||||
dataValues.push(val);
|
||||
});
|
||||
if (!this._hiddenStats.has(statistic_id)) {
|
||||
pushData(startDate, new Date(stat.end), dataValues);
|
||||
pushData(
|
||||
startDate,
|
||||
endDate.getTime() < endTime.getTime() ? endDate : endTime,
|
||||
dataValues
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
// Close out the last stat segment at prevEndTime
|
||||
const lastEndTime = prevEndTime;
|
||||
const lastValues = prevValues;
|
||||
if (lastEndTime && lastValues) {
|
||||
statDataSets.forEach((d, i) => {
|
||||
d.data!.push(
|
||||
this._transformDataValue([lastEndTime, ...lastValues[i]!])
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
// Append current state if viewing recent data
|
||||
const now = new Date();
|
||||
// allow 10m of leeway for "now", because stats are 5 minute aggregated
|
||||
@@ -619,16 +635,6 @@ export class StatisticsChart extends LitElement {
|
||||
isFinite(currentValue) &&
|
||||
!this._hiddenStats.has(statistic_id)
|
||||
) {
|
||||
// First, close out the last stat segment at prevEndTime
|
||||
const lastEndTime = prevEndTime;
|
||||
const lastValues = prevValues;
|
||||
if (lastEndTime && lastValues) {
|
||||
statDataSets.forEach((d, i) => {
|
||||
d.data!.push(
|
||||
this._transformDataValue([lastEndTime, ...lastValues[i]!])
|
||||
);
|
||||
});
|
||||
}
|
||||
// Then push the current state at now
|
||||
statTypes.forEach((type, i) => {
|
||||
const val: (number | null)[] = [];
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
import type { HassConfig } from "home-assistant-js-websocket";
|
||||
import {
|
||||
differenceInMonths,
|
||||
subHours,
|
||||
differenceInDays,
|
||||
differenceInMonths,
|
||||
differenceInCalendarMonths,
|
||||
differenceInYears,
|
||||
startOfYear,
|
||||
addMilliseconds,
|
||||
@@ -12,6 +13,7 @@ import {
|
||||
addHours,
|
||||
startOfDay,
|
||||
addDays,
|
||||
subDays,
|
||||
} from "date-fns";
|
||||
import type {
|
||||
BarSeriesOption,
|
||||
@@ -33,10 +35,22 @@ import { filterXSS } from "../../../../../common/util/xss";
|
||||
import type { StatisticPeriod } from "../../../../../data/recorder";
|
||||
import { getSuggestedPeriod } from "../../../../../data/energy";
|
||||
|
||||
export function getSuggestedMax(period: StatisticPeriod, end: Date): Date {
|
||||
// Number of days of padding when showing time axis in months
|
||||
const MONTH_TIME_AXIS_PADDING = 5;
|
||||
|
||||
export function getSuggestedMax(
|
||||
period: StatisticPeriod,
|
||||
end: Date,
|
||||
noRounding: boolean
|
||||
): Date {
|
||||
// Maximum period depends on whether plotting a line chart or discrete bars.
|
||||
// - For line charts we must be plotting all the way to end of a given period,
|
||||
// otherwise we cut off the last period of data.
|
||||
// - For bar charts we need to round down to the start of the final bars period
|
||||
// to avoid unnecessary padding of the chart.
|
||||
let suggestedMax = new Date(end);
|
||||
|
||||
if (period === "5minute") {
|
||||
if (noRounding || period === "5minute") {
|
||||
return suggestedMax;
|
||||
}
|
||||
suggestedMax.setMinutes(0, 0, 0);
|
||||
@@ -82,17 +96,44 @@ export function getCommonOptions(
|
||||
detailedDailyData = false
|
||||
): ECOption {
|
||||
const suggestedPeriod = getSuggestedPeriod(start, end, detailedDailyData);
|
||||
const suggestedMax = getSuggestedMax(suggestedPeriod, end, detailedDailyData);
|
||||
|
||||
const compare = compareStart !== undefined && compareEnd !== undefined;
|
||||
const showCompareYear =
|
||||
compare && start.getFullYear() !== compareStart.getFullYear();
|
||||
|
||||
const options: ECOption = {
|
||||
const monthTimeAxis: ECOption = {
|
||||
xAxis: {
|
||||
type: "time",
|
||||
min: subDays(start, MONTH_TIME_AXIS_PADDING),
|
||||
max: addDays(suggestedMax, MONTH_TIME_AXIS_PADDING),
|
||||
axisLabel: {
|
||||
formatter: {
|
||||
year: "{yearStyle|{MMMM} {yyyy}}",
|
||||
month: "{MMMM}",
|
||||
},
|
||||
rich: {
|
||||
yearStyle: {
|
||||
fontWeight: "bold",
|
||||
},
|
||||
},
|
||||
},
|
||||
// For shorter month ranges, force splitting to ensure time axis renders
|
||||
// as whole month intervals. Limit the number of forced ticks to 6 months
|
||||
// (so a max calendar difference of 5) to reduce clutter.
|
||||
splitNumber: Math.min(differenceInCalendarMonths(end, start), 5),
|
||||
},
|
||||
};
|
||||
const normalTimeAxis: ECOption = {
|
||||
xAxis: {
|
||||
type: "time",
|
||||
min: start,
|
||||
max: getSuggestedMax(suggestedPeriod, end),
|
||||
max: suggestedMax,
|
||||
},
|
||||
};
|
||||
|
||||
const options: ECOption = {
|
||||
...(suggestedPeriod === "month" ? monthTimeAxis : normalTimeAxis),
|
||||
yAxis: {
|
||||
type: "value",
|
||||
name: unit,
|
||||
|
||||
@@ -332,7 +332,11 @@ export class HuiStatisticsGraphCard extends LitElement implements LovelaceCard {
|
||||
.maxYAxis=${this._config.max_y_axis}
|
||||
.startTime=${this._energyStart}
|
||||
.endTime=${this._energyEnd && this._energyStart
|
||||
? getSuggestedMax(this._period!, this._energyEnd)
|
||||
? getSuggestedMax(
|
||||
this._period!,
|
||||
this._energyEnd,
|
||||
(this._config.chart_type ?? "line") === "line"
|
||||
)
|
||||
: undefined}
|
||||
.fitYData=${this._config.fit_y_data || false}
|
||||
.hideLegend=${this._config.hide_legend || false}
|
||||
|
||||
Reference in New Issue
Block a user