diff --git a/src/components/chart/axis-label.ts b/src/components/chart/axis-label.ts
index d779269e33..1f8f9a8154 100644
--- a/src/components/chart/axis-label.ts
+++ b/src/components/chart/axis-label.ts
@@ -1,5 +1,4 @@
import type { HassConfig } from "home-assistant-js-websocket";
-import type { XAXisOption } from "echarts/types/dist/shared";
import type { FrontendLocaleData } from "../../data/translation";
import {
formatDateMonth,
@@ -7,56 +6,46 @@ import {
formatDateVeryShort,
formatDateWeekdayShort,
} from "../../common/datetime/format_date";
-import { formatTime } from "../../common/datetime/format_time";
+import {
+ formatTime,
+ formatTimeWithSeconds,
+} from "../../common/datetime/format_time";
-export function getLabelFormatter(
+export function formatTimeLabel(
+ value: number | Date,
locale: FrontendLocaleData,
config: HassConfig,
- dayDifference = 0
+ minutesDifference: number
) {
- return (value: number | Date) => {
- const date = new Date(value);
- if (dayDifference > 88) {
- return date.getMonth() === 0
- ? `{bold|${formatDateMonthYear(date, locale, config)}}`
- : formatDateMonth(date, locale, config);
- }
- if (dayDifference > 35) {
- return date.getDate() === 1
- ? `{bold|${formatDateVeryShort(date, locale, config)}}`
- : formatDateVeryShort(date, locale, config);
- }
- if (dayDifference > 7) {
- const label = formatDateVeryShort(date, locale, config);
- return date.getDate() === 1 ? `{bold|${label}}` : label;
- }
- if (dayDifference > 2) {
- return formatDateWeekdayShort(date, locale, config);
- }
+ const dayDifference = minutesDifference / 60 / 24;
+ const date = new Date(value);
+ if (dayDifference > 88) {
+ return date.getMonth() === 0
+ ? `{bold|${formatDateMonthYear(date, locale, config)}}`
+ : formatDateMonth(date, locale, config);
+ }
+ if (dayDifference > 35) {
+ return date.getDate() === 1
+ ? `{bold|${formatDateVeryShort(date, locale, config)}}`
+ : formatDateVeryShort(date, locale, config);
+ }
+ if (dayDifference > 7) {
+ const label = formatDateVeryShort(date, locale, config);
+ return date.getDate() === 1 ? `{bold|${label}}` : label;
+ }
+ if (dayDifference > 2) {
+ return formatDateWeekdayShort(date, locale, config);
+ }
+ if (minutesDifference && minutesDifference < 5) {
+ return formatTimeWithSeconds(date, locale, config);
+ }
+ if (
+ date.getHours() === 0 &&
+ date.getMinutes() === 0 &&
+ date.getSeconds() === 0
+ ) {
// show only date for the beginning of the day
- if (
- date.getHours() === 0 &&
- date.getMinutes() === 0 &&
- date.getSeconds() === 0
- ) {
- return `{bold|${formatDateVeryShort(date, locale, config)}}`;
- }
- return formatTime(date, locale, config);
- };
-}
-
-export function getTimeAxisLabelConfig(
- locale: FrontendLocaleData,
- config: HassConfig,
- dayDifference?: number
-): XAXisOption["axisLabel"] {
- return {
- formatter: getLabelFormatter(locale, config, dayDifference),
- rich: {
- bold: {
- fontWeight: "bold",
- },
- },
- hideOverlap: true,
- };
+ return `{bold|${formatDateVeryShort(date, locale, config)}}`;
+ }
+ return formatTime(date, locale, config);
}
diff --git a/src/components/chart/ha-chart-base.ts b/src/components/chart/ha-chart-base.ts
index 85a2e4a538..32e7f729eb 100644
--- a/src/components/chart/ha-chart-base.ts
+++ b/src/components/chart/ha-chart-base.ts
@@ -2,6 +2,7 @@ import type { PropertyValues } from "lit";
import { css, html, nothing, LitElement } from "lit";
import { customElement, property, state } from "lit/decorators";
import { styleMap } from "lit/directives/style-map";
+import { classMap } from "lit/directives/class-map";
import { mdiRestart } from "@mdi/js";
import type { EChartsType } from "echarts/core";
import type { DataZoomComponentOption } from "echarts/components";
@@ -12,6 +13,7 @@ import type {
YAXisOption,
} from "echarts/types/dist/shared";
import { consume } from "@lit-labs/context";
+import { differenceInMinutes } from "date-fns";
import { fireEvent } from "../../common/dom/fire_event";
import type { HomeAssistant } from "../../types";
import { isMac } from "../../util/is_mac";
@@ -21,6 +23,7 @@ import { listenMediaQuery } from "../../common/dom/media_query";
import type { Themes } from "../../data/ws-themes";
import { themesContext } from "../../data/context";
import { getAllGraphColors } from "../../common/color/colors";
+import { formatTimeLabel } from "./axis-label";
export const MIN_TIME_BETWEEN_UPDATES = 60 * 5 * 1000;
@@ -45,6 +48,10 @@ export class HaChartBase extends LitElement {
@state() private _isZoomed = false;
+ @state() private _zoomRatio = 1;
+
+ @state() private _minutesDifference = 24 * 60;
+
private _modifierPressed = false;
private _isTouchDevice = "ontouchstart" in window;
@@ -152,7 +159,10 @@ export class HaChartBase extends LitElement {
protected render() {
return html`
+ formatTimeLabel(
+ value,
+ this.hass.locale,
+ this.hass.config,
+ this._minutesDifference * this._zoomRatio
+ );
+
private async _setupChart() {
if (this._loading) return;
const container = this.renderRoot.querySelector(".chart") as HTMLDivElement;
@@ -199,6 +217,7 @@ export class HaChartBase extends LitElement {
this.chart.on("datazoom", (e: any) => {
const { start, end } = e.batch?.[0] ?? e;
this._isZoomed = start !== 0 || end !== 100;
+ this._zoomRatio = (end - start) / 100;
});
this.chart.on("click", (e: ECElementEvent) => {
fireEvent(this, "chart-click", e);
@@ -236,6 +255,45 @@ export class HaChartBase extends LitElement {
}
private _createOptions(): ECOption {
+ let xAxis = this.options?.xAxis;
+ if (xAxis && !Array.isArray(xAxis) && xAxis.type === "time") {
+ if (xAxis.max && xAxis.min) {
+ this._minutesDifference = differenceInMinutes(
+ xAxis.max as Date,
+ xAxis.min as Date
+ );
+ }
+ const dayDifference = this._minutesDifference / 60 / 24;
+ let minInterval: number | undefined;
+ if (dayDifference) {
+ minInterval =
+ dayDifference >= 89 // quarter
+ ? 28 * 3600 * 24 * 1000
+ : dayDifference > 2
+ ? 3600 * 24 * 1000
+ : undefined;
+ }
+ xAxis = {
+ ...xAxis,
+ axisLabel: {
+ formatter: this._formatTimeLabel,
+ rich: {
+ bold: {
+ fontWeight: "bold",
+ },
+ },
+ hideOverlap: true,
+ ...xAxis.axisLabel,
+ },
+ axisLine: {
+ show: false,
+ },
+ splitLine: {
+ show: true,
+ },
+ minInterval,
+ } as XAXisOption;
+ }
const options = {
animation: !this._reducedMotion,
darkMode: this._themes.darkMode ?? false,
@@ -244,6 +302,7 @@ export class HaChartBase extends LitElement {
},
dataZoom: this._getDataZoomConfig(),
...this.options,
+ xAxis,
};
const isMobile = window.matchMedia(
@@ -485,6 +544,9 @@ export class HaChartBase extends LitElement {
color: var(--primary-color);
border: 1px solid var(--divider-color);
}
+ .has-legend .zoom-reset {
+ top: 64px;
+ }
`;
}
diff --git a/src/components/chart/state-history-chart-line.ts b/src/components/chart/state-history-chart-line.ts
index 01bd1916b1..be9e71e845 100644
--- a/src/components/chart/state-history-chart-line.ts
+++ b/src/components/chart/state-history-chart-line.ts
@@ -4,7 +4,6 @@ import { property, state } from "lit/decorators";
import type { VisualMapComponentOption } from "echarts/components";
import type { LineSeriesOption } from "echarts/charts";
import type { YAXisOption } from "echarts/types/dist/shared";
-import { differenceInDays } from "date-fns";
import { styleMap } from "lit/directives/style-map";
import { getGraphColorByIndex } from "../../common/color/colors";
import { computeRTL } from "../../common/util/compute_rtl";
@@ -18,7 +17,6 @@ import {
getNumberFormatOptions,
formatNumber,
} from "../../common/number/format_number";
-import { getTimeAxisLabelConfig } from "./axis-label";
import { measureTextWidth } from "../../util/text";
import { fireEvent } from "../../common/dom/fire_event";
import { CLIMATE_HVAC_ACTION_TO_MODE } from "../../data/climate";
@@ -206,7 +204,6 @@ export class StateHistoryChartLine extends LitElement {
changedProps.has("paddingYAxis") ||
changedProps.has("_yWidth")
) {
- const dayDifference = differenceInDays(this.endTime, this.startTime);
const rtl = computeRTL(this.hass);
let minYAxis: number | ((values: { min: number }) => number) | undefined =
this.minYAxis;
@@ -231,23 +228,6 @@ export class StateHistoryChartLine extends LitElement {
type: "time",
min: this.startTime,
max: this.endTime,
- axisLabel: getTimeAxisLabelConfig(
- this.hass.locale,
- this.hass.config,
- dayDifference
- ),
- axisLine: {
- show: false,
- },
- splitLine: {
- show: true,
- },
- minInterval:
- dayDifference >= 89 // quarter
- ? 28 * 3600 * 24 * 1000
- : dayDifference > 2
- ? 3600 * 24 * 1000
- : undefined,
},
yAxis: {
type: this.logarithmicScale ? "log" : "value",
diff --git a/src/components/chart/state-history-chart-timeline.ts b/src/components/chart/state-history-chart-timeline.ts
index 4069473b62..39a22c827c 100644
--- a/src/components/chart/state-history-chart-timeline.ts
+++ b/src/components/chart/state-history-chart-timeline.ts
@@ -8,7 +8,6 @@ import type {
TooltipFormatterCallback,
TooltipPositionCallbackParams,
} from "echarts/types/dist/shared";
-import { differenceInDays } from "date-fns";
import { formatDateTimeWithSeconds } from "../../common/datetime/format_date_time";
import millisecondsToDuration from "../../common/datetime/milliseconds_to_duration";
import { computeRTL } from "../../common/util/compute_rtl";
@@ -22,7 +21,6 @@ import { luminosity } from "../../common/color/rgb";
import { hex2rgb } from "../../common/color/convert-color";
import { measureTextWidth } from "../../util/text";
import { fireEvent } from "../../common/dom/fire_event";
-import { getTimeAxisLabelConfig } from "./axis-label";
@customElement("state-history-chart-timeline")
export class StateHistoryChartTimeline extends LitElement {
@@ -191,7 +189,6 @@ export class StateHistoryChartTimeline extends LitElement {
: 0;
const labelMargin = 5;
const rtl = computeRTL(this.hass);
- const dayDifference = differenceInDays(this.endTime, this.startTime);
this._chartOptions = {
xAxis: {
type: "time",
@@ -203,20 +200,6 @@ export class StateHistoryChartTimeline extends LitElement {
splitLine: {
show: false,
},
- axisLine: {
- show: false,
- },
- axisLabel: getTimeAxisLabelConfig(
- this.hass.locale,
- this.hass.config,
- dayDifference
- ),
- minInterval:
- dayDifference >= 89 // quarter
- ? 28 * 3600 * 24 * 1000
- : dayDifference > 2
- ? 3600 * 24 * 1000
- : undefined,
},
yAxis: {
type: "category",
diff --git a/src/components/chart/statistics-chart.ts b/src/components/chart/statistics-chart.ts
index 2dd3fd68c5..62090d23ac 100644
--- a/src/components/chart/statistics-chart.ts
+++ b/src/components/chart/statistics-chart.ts
@@ -30,7 +30,6 @@ import {
getNumberFormatOptions,
} from "../../common/number/format_number";
import { formatDateTimeWithSeconds } from "../../common/datetime/format_date_time";
-import { getTimeAxisLabelConfig } from "./axis-label";
export const supportedStatTypeMap: Record = {
mean: "mean",
@@ -216,7 +215,6 @@ export class StatisticsChart extends LitElement {
};
private _createOptions() {
- const splitLineStyle = this.hass.themes?.darkMode ? { opacity: 0.15 } : {};
const dayDifference = this.daysToShow ?? 1;
let minYAxis: number | ((values: { min: number }) => number) | undefined =
this.minYAxis;
@@ -236,29 +234,32 @@ export class StatisticsChart extends LitElement {
} else if (this.logarithmicScale) {
maxYAxis = ({ max }) => (max > 0 ? max * 1.05 : max * 0.95);
}
+ const endTime = this.endTime ?? new Date();
+ let startTime = this.startTime;
+
+ if (!startTime) {
+ // Calculate default start time based on dayDifference
+ startTime = new Date(
+ endTime.getTime() - dayDifference * 24 * 3600 * 1000
+ );
+
+ // Check chart data for earlier points
+ this._chartData.forEach((series) => {
+ if (!Array.isArray(series.data)) return;
+ series.data.forEach((point) => {
+ const timestamp = Array.isArray(point) ? point[0] : point.value?.[0];
+ if (new Date(timestamp) < startTime!) {
+ startTime = new Date(timestamp);
+ }
+ });
+ });
+ }
+
this._chartOptions = {
xAxis: {
type: "time",
- axisLabel: getTimeAxisLabelConfig(
- this.hass.locale,
- this.hass.config,
- dayDifference
- ),
- min: this.startTime,
- max: this.endTime,
- axisLine: {
- show: false,
- },
- splitLine: {
- show: true,
- lineStyle: splitLineStyle,
- },
- minInterval:
- dayDifference >= 89 // quarter
- ? 28 * 3600 * 24 * 1000
- : dayDifference > 2
- ? 3600 * 24 * 1000
- : undefined,
+ min: startTime,
+ max: endTime,
},
yAxis: {
type: this.logarithmicScale ? "log" : "value",
@@ -274,7 +275,6 @@ export class StatisticsChart extends LitElement {
max: this._clampYAxis(maxYAxis),
splitLine: {
show: true,
- lineStyle: splitLineStyle,
},
},
legend: {
@@ -348,6 +348,7 @@ export class StatisticsChart extends LitElement {
if (endTime > new Date()) {
endTime = new Date();
}
+ this.endTime = endTime;
let unit: string | undefined | null;
diff --git a/src/panels/config/hardware/ha-config-hardware.ts b/src/panels/config/hardware/ha-config-hardware.ts
index 004bcb0cd1..12c14def9a 100644
--- a/src/panels/config/hardware/ha-config-hardware.ts
+++ b/src/panels/config/hardware/ha-config-hardware.ts
@@ -39,7 +39,6 @@ import { hardwareBrandsUrl } from "../../../util/brands-url";
import { showhardwareAvailableDialog } from "./show-dialog-hardware-available";
import { extractApiErrorMessage } from "../../../data/hassio/common";
import type { ECOption } from "../../../resources/echarts";
-import { getTimeAxisLabelConfig } from "../../../components/chart/axis-label";
const DATASAMPLES = 60;
@@ -153,13 +152,6 @@ class HaConfigHardware extends SubscribeMixin(LitElement) {
this._chartOptions = {
xAxis: {
type: "time",
- axisLabel: getTimeAxisLabelConfig(this.hass.locale, this.hass.config),
- splitLine: {
- show: true,
- },
- axisLine: {
- show: false,
- },
},
yAxis: {
type: "value",
diff --git a/src/panels/lovelace/cards/energy/common/energy-chart-options.ts b/src/panels/lovelace/cards/energy/common/energy-chart-options.ts
index 3619c731d3..76dde725aa 100644
--- a/src/panels/lovelace/cards/energy/common/energy-chart-options.ts
+++ b/src/panels/lovelace/cards/energy/common/energy-chart-options.ts
@@ -10,7 +10,6 @@ import { formatNumber } from "../../../../../common/number/format_number";
import { formatDateVeryShort } from "../../../../../common/datetime/format_date";
import { formatTime } from "../../../../../common/datetime/format_time";
import type { ECOption } from "../../../../../resources/echarts";
-import { getTimeAxisLabelConfig } from "../../../../../components/chart/axis-label";
export function getSuggestedMax(dayDifference: number, end: Date): number {
let suggestedMax = new Date(end);
@@ -52,23 +51,9 @@ export function getCommonOptions(
const options: ECOption = {
xAxis: {
- id: "xAxisMain",
type: "time",
- min: start.getTime(),
- max: getSuggestedMax(dayDifference, end),
- axisLabel: getTimeAxisLabelConfig(locale, config, dayDifference),
- axisLine: {
- show: false,
- },
- splitLine: {
- show: true,
- },
- minInterval:
- dayDifference >= 89 // quarter
- ? 28 * 3600 * 24 * 1000
- : dayDifference > 2
- ? 3600 * 24 * 1000
- : undefined,
+ min: start,
+ max: end,
},
yAxis: {
type: "value",