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; + } `; } }