mirror of
https://github.com/home-assistant/frontend.git
synced 2025-07-21 16:26:43 +00:00
Show seconds on x axis when chart is zoomed a lot (#24043)
Show seconds on x axis when charts is zoomed a lot
This commit is contained in:
parent
6eb43a7d61
commit
4698a63642
@ -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);
|
||||
}
|
||||
|
@ -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`
|
||||
<div
|
||||
class="chart-container"
|
||||
class=${classMap({
|
||||
"chart-container": true,
|
||||
"has-legend": !!this.options?.legend,
|
||||
})}
|
||||
style=${styleMap({
|
||||
height: this.height ?? `${this._getDefaultHeight()}px`,
|
||||
})}
|
||||
@ -173,6 +183,14 @@ export class HaChartBase extends LitElement {
|
||||
`;
|
||||
}
|
||||
|
||||
private _formatTimeLabel = (value: number | Date) =>
|
||||
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;
|
||||
}
|
||||
`;
|
||||
}
|
||||
|
||||
|
@ -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",
|
||||
|
@ -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",
|
||||
|
@ -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<StatisticType, StatisticType> = {
|
||||
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;
|
||||
|
||||
|
@ -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",
|
||||
|
@ -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",
|
||||
|
Loading…
x
Reference in New Issue
Block a user