From e39194b9021e5d9c3876c1d180187dc03973363d Mon Sep 17 00:00:00 2001
From: Mario Hros <966992+k3a@users.noreply.github.com>
Date: Mon, 25 Dec 2023 14:50:58 +0200
Subject: [PATCH] use a tooltip button for More Info chart functionality on
non-mouse devices, lower z-index for tooltips to avoid overdrawing
sidebar/dialogs
---
src/components/chart/ha-chart-base.ts | 6 +-
.../chart/state-history-chart-line.ts | 94 ++++++++++++++-----
.../chart/state-history-chart-timeline.ts | 80 ++++++++++++----
3 files changed, 140 insertions(+), 40 deletions(-)
diff --git a/src/components/chart/ha-chart-base.ts b/src/components/chart/ha-chart-base.ts
index 36ddc2915c00..6a1dbf21e449 100644
--- a/src/components/chart/ha-chart-base.ts
+++ b/src/components/chart/ha-chart-base.ts
@@ -261,6 +261,7 @@ export class HaChartBase extends LitElement {
${this._tooltip.footer.map((item) => html`${item}
`)}
`
: ""}
+
`
: ""}
@@ -449,9 +450,12 @@ export class HaChartBase extends LitElement {
color: white;
border-radius: 4px;
pointer-events: none;
- z-index: 1000;
+ z-index: 1;
width: 200px;
box-sizing: border-box;
+ -ms-user-select: none;
+ -webkit-user-select: none;
+ -moz-user-select: none;
}
:host([rtl]) .chartTooltip {
direction: rtl;
diff --git a/src/components/chart/state-history-chart-line.ts b/src/components/chart/state-history-chart-line.ts
index e712762470cd..f290bd58921a 100644
--- a/src/components/chart/state-history-chart-line.ts
+++ b/src/components/chart/state-history-chart-line.ts
@@ -1,5 +1,13 @@
-import type { ChartData, ChartDataset, ChartOptions } from "chart.js";
-import { html, LitElement, PropertyValues } from "lit";
+import type {
+ ChartData,
+ ChartDataset,
+ ChartOptions,
+ ChartEvent,
+ ActiveElement,
+ Chart,
+} from "chart.js";
+import { mdiInformationOutline } from "@mdi/js";
+import { html, css, CSSResultGroup, LitElement, PropertyValues } from "lit";
import { property, query, state } from "lit/decorators";
import { getGraphColorByIndex } from "../../common/color/colors";
import { fireEvent } from "../../common/dom/fire_event";
@@ -16,6 +24,7 @@ import {
HaChartBase,
MIN_TIME_BETWEEN_UPDATES,
} from "./ha-chart-base";
+import "../ha-svg-icon";
const safeParseFloat = (value) => {
const parsed = parseFloat(value);
@@ -57,6 +66,10 @@ export class StateHistoryChartLine extends LitElement {
@state() private _yWidth = 0;
+ @state() private _showMoreInfoTooltipButton = false;
+
+ @state() private _moreInfoEntityId = "";
+
private _chartTime: Date = new Date();
@query("ha-chart-base") private _chart?: HaChartBase;
@@ -73,7 +86,16 @@ export class StateHistoryChartLine extends LitElement {
.options=${this._chartOptions}
.paddingYAxis=${this.paddingYAxis - this._yWidth}
chart-type="line"
- >
+ >
+ ${this._showMoreInfoTooltipButton
+ ? html``
+ : ""}
+
`;
}
@@ -206,28 +228,17 @@ export class StateHistoryChartLine extends LitElement {
},
// @ts-expect-error
locale: numberFormatToLocale(this.hass.locale),
- onClick: (e: any) => {
- if (!this.clickForMoreInfo) {
- return;
- }
+ onClick: (e, elements, chart) => {
+ this._handleClickOrHover(e, elements, chart);
- const chart = e.chart;
-
- const points = chart.getElementsAtEventForMode(
- e,
- "nearest",
- { intersect: true },
- true
- );
-
- if (points.length) {
- const firstPoint = points[0];
- fireEvent(this, "hass-more-info", {
- entityId: this._entityIds[firstPoint.datasetIndex],
- });
- chart.canvas.dispatchEvent(new Event("mouseout")); // to hide tooltip
+ // if the More Info tooltip button is not shown, the pointer used to make
+ // this event was a mouse so open the more info dialog now
+ if (this.clickForMoreInfo && !this._showMoreInfoTooltipButton) {
+ this._moreInfoClick();
}
},
+ onHover: (e, elements, chart) =>
+ this._handleClickOrHover(e, elements, chart),
};
}
if (
@@ -573,6 +584,45 @@ export class StateHistoryChartLine extends LitElement {
this._entityIds = entityIds;
this._datasetToDataIndex = datasetToDataIndex;
}
+
+ private _handleClickOrHover(
+ e: ChartEvent,
+ _elements: ActiveElement[],
+ chart: Chart
+ ) {
+ if (!this.clickForMoreInfo) {
+ return;
+ }
+
+ const points = chart.getElementsAtEventForMode(
+ e as any,
+ "nearest",
+ { intersect: true },
+ true
+ );
+
+ if (points.length) {
+ const firstPoint = points[0];
+ this._moreInfoEntityId = this._entityIds[firstPoint.datasetIndex];
+
+ // display More Info toottip button for non-mouse events
+ this._showMoreInfoTooltipButton ||= !(e.native instanceof MouseEvent);
+ }
+ }
+
+ private _moreInfoClick() {
+ fireEvent(this, "hass-more-info", {
+ entityId: this._moreInfoEntityId,
+ });
+ }
+
+ static get styles(): CSSResultGroup {
+ return css`
+ .more-info-button {
+ pointer-events: auto;
+ }
+ `;
+ }
}
customElements.define("state-history-chart-line", StateHistoryChartLine);
diff --git a/src/components/chart/state-history-chart-timeline.ts b/src/components/chart/state-history-chart-timeline.ts
index d1c535e23cc4..5f05ef4bad77 100644
--- a/src/components/chart/state-history-chart-timeline.ts
+++ b/src/components/chart/state-history-chart-timeline.ts
@@ -1,4 +1,12 @@
-import type { ChartData, ChartDataset, ChartOptions } from "chart.js";
+import type {
+ ChartData,
+ ChartDataset,
+ ChartOptions,
+ ChartEvent,
+ ActiveElement,
+ Chart,
+} from "chart.js";
+import { mdiInformationOutline } from "@mdi/js";
import { getRelativePosition } from "chart.js/helpers";
import { css, CSSResultGroup, html, LitElement, PropertyValues } from "lit";
import { customElement, property, query, state } from "lit/decorators";
@@ -16,6 +24,7 @@ import {
} from "./ha-chart-base";
import type { TimeLineData } from "./timeline-chart/const";
import { computeTimelineColor } from "./timeline-chart/timeline-color";
+import "../ha-svg-icon";
@customElement("state-history-chart-timeline")
export class StateHistoryChartTimeline extends LitElement {
@@ -51,6 +60,10 @@ export class StateHistoryChartTimeline extends LitElement {
@state() private _yWidth = 0;
+ @state() private _showMoreInfoTooltipButton = false;
+
+ @state() private _moreInfoEntityId = "";
+
private _chartTime: Date = new Date();
@query("ha-chart-base") private _chart?: HaChartBase;
@@ -68,7 +81,16 @@ export class StateHistoryChartTimeline extends LitElement {
.height=${this.data.length * 30 + 30}
.paddingYAxis=${this.paddingYAxis - this._yWidth}
chart-type="timeline"
- >
+ >
+ ${this._showMoreInfoTooltipButton
+ ? html``
+ : ""}
+
`;
}
@@ -223,23 +245,17 @@ export class StateHistoryChartTimeline extends LitElement {
},
// @ts-expect-error
locale: numberFormatToLocale(this.hass.locale),
- onClick: (e: any) => {
- if (!this.clickForMoreInfo) {
- return;
- }
-
- const chart = e.chart;
- const canvasPosition = getRelativePosition(e, chart);
+ onClick: (e, elements, chart) => {
+ this._handleClickOrHover(e, elements, chart);
- const index = Math.abs(
- chart.scales.y.getValueForPixel(canvasPosition.y)
- );
- fireEvent(this, "hass-more-info", {
- // @ts-ignore
- entityId: this._chartData?.datasets[index]?.label,
- });
- chart.canvas.dispatchEvent(new Event("mouseout")); // to hide tooltip
+ // if the More Info tooltip button is not shown, the pointer used to make
+ // this event was a mouse so open the more info dialog now
+ if (this.clickForMoreInfo && !this._showMoreInfoTooltipButton) {
+ this._moreInfoClick();
+ }
},
+ onHover: (e, elements, chart) =>
+ this._handleClickOrHover(e, elements, chart),
};
}
@@ -327,11 +343,41 @@ export class StateHistoryChartTimeline extends LitElement {
};
}
+ private _handleClickOrHover(
+ e: ChartEvent,
+ _elements: ActiveElement[],
+ chart: Chart
+ ) {
+ if (!this.clickForMoreInfo) {
+ return;
+ }
+
+ const canvasPosition = getRelativePosition(e, chart as any);
+
+ const index = chart.scales.y.getValueForPixel(canvasPosition.y);
+
+ if (index !== undefined && index >= 0) {
+ this._moreInfoEntityId = this._chartData?.datasets[index]?.label ?? "";
+
+ // display More Info toottip button for non-mouse events
+ this._showMoreInfoTooltipButton ||= !(e.native instanceof MouseEvent);
+ }
+ }
+
+ private _moreInfoClick() {
+ fireEvent(this, "hass-more-info", {
+ entityId: this._moreInfoEntityId,
+ });
+ }
+
static get styles(): CSSResultGroup {
return css`
ha-chart-base {
--chart-max-height: none;
}
+ .more-info-button {
+ pointer-events: auto;
+ }
`;
}
}