From fe9ed8eac4cce839d839613a294fe67eb4490bfd Mon Sep 17 00:00:00 2001 From: Petar Petrov Date: Wed, 4 Dec 2024 14:25:09 +0200 Subject: [PATCH 1/8] Add zoom & pan to all Chart.js charts --- package.json | 1 + src/components/chart/ha-chart-base.ts | 18 ++++++++++++++++- src/resources/chartjs.ts | 4 +++- yarn.lock | 28 +++++++++++++++++++++++---- 4 files changed, 45 insertions(+), 6 deletions(-) diff --git a/package.json b/package.json index 522bd52a3b43..358b632b93c1 100644 --- a/package.json +++ b/package.json @@ -102,6 +102,7 @@ "app-datepicker": "5.1.1", "barcode-detector": "2.3.1", "chart.js": "4.4.6", + "chartjs-plugin-zoom": "2.2.0", "color-name": "2.0.0", "comlink": "4.4.2", "core-js": "3.39.0", diff --git a/src/components/chart/ha-chart-base.ts b/src/components/chart/ha-chart-base.ts index 049f4b6091ed..29266c93bac0 100644 --- a/src/components/chart/ha-chart-base.ts +++ b/src/components/chart/ha-chart-base.ts @@ -341,7 +341,7 @@ export class HaChartBase extends LitElement { } } - private _createOptions() { + private _createOptions(): ChartOptions { return { maintainAspectRatio: false, ...this.options, @@ -356,6 +356,22 @@ export class HaChartBase extends LitElement { ...this.options?.plugins?.legend, display: false, }, + zoom: { + ...this.options?.plugins?.zoom, + pan: { + enabled: true, + modifierKey: "shift", + }, + zoom: { + pinch: { + enabled: true, + }, + drag: { + enabled: true, + }, + mode: "x", + }, + }, }, }; } diff --git a/src/resources/chartjs.ts b/src/resources/chartjs.ts index fc5b44411c04..383763797a1b 100644 --- a/src/resources/chartjs.ts +++ b/src/resources/chartjs.ts @@ -1,3 +1,4 @@ +import ZoomPlugin from "chartjs-plugin-zoom"; import { LineController, TimeScale, @@ -37,5 +38,6 @@ Chart.register( TimeLineScale, TimelineController, CategoryScale, - LogarithmicScale + LogarithmicScale, + ZoomPlugin ); diff --git a/yarn.lock b/yarn.lock index b2e352b6baa9..b056668210de 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4698,10 +4698,10 @@ __metadata: languageName: node linkType: hard -"@types/hammerjs@npm:^2.0.36": - version: 2.0.45 - resolution: "@types/hammerjs@npm:2.0.45" - checksum: 10/8d7f8791789853a9461f6445e625f18922a823a61042161dde5513f4a2c15ecd6361fa6f9b457ce13bfb6b518489b892fedb9e2cebb4420523cb45f1cbb4ee88 +"@types/hammerjs@npm:^2.0.36, @types/hammerjs@npm:^2.0.45": + version: 2.0.46 + resolution: "@types/hammerjs@npm:2.0.46" + checksum: 10/1b6502d668f45ca49fb488c01f7938d3aa75e989d70c64801c8feded7d659ca1a118f745c1b604d220efe344c93231767d5cc68c05e00e069c14539b6143cfd9 languageName: node linkType: hard @@ -6736,6 +6736,18 @@ __metadata: languageName: node linkType: hard +"chartjs-plugin-zoom@npm:2.2.0": + version: 2.2.0 + resolution: "chartjs-plugin-zoom@npm:2.2.0" + dependencies: + "@types/hammerjs": "npm:^2.0.45" + hammerjs: "npm:^2.0.8" + peerDependencies: + chart.js: ">=3.2.0" + checksum: 10/4a549b1b21ed5433f9ba67038d6176ed545b2881521e12d6b8024cd2ab08fb008c36fe388ab2ac7ee2ac334bf44a8d785703570388fa0e0b4c22c18602536f9c + languageName: node + linkType: hard + "check-error@npm:^2.1.1": version: 2.1.1 resolution: "check-error@npm:2.1.1" @@ -9394,6 +9406,13 @@ __metadata: languageName: node linkType: hard +"hammerjs@npm:^2.0.8": + version: 2.0.8 + resolution: "hammerjs@npm:2.0.8" + checksum: 10/9155d056f252ef35e8ca258dbb5ee2c9d8794f6805d083da7d1d9763d185e3e149459ecc2b36ccce584e3cd5f099fd9fa55056e3bcc7724046390f2e5ae25815 + languageName: node + linkType: hard + "handle-thing@npm:^2.0.0": version: 2.0.1 resolution: "handle-thing@npm:2.0.1" @@ -9607,6 +9626,7 @@ __metadata: barcode-detector: "npm:2.3.1" browserslist-useragent-regexp: "npm:4.1.3" chart.js: "npm:4.4.6" + chartjs-plugin-zoom: "npm:2.2.0" color-name: "npm:2.0.0" comlink: "npm:4.4.2" core-js: "npm:3.39.0" From baac132927fe2486971d16bc226002efdcaba88e Mon Sep 17 00:00:00 2001 From: Petar Petrov Date: Fri, 6 Dec 2024 14:13:22 +0200 Subject: [PATCH 2/8] limit pan to original chart --- src/components/chart/ha-chart-base.ts | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/components/chart/ha-chart-base.ts b/src/components/chart/ha-chart-base.ts index 4f02423e4fda..2ecf425aed77 100644 --- a/src/components/chart/ha-chart-base.ts +++ b/src/components/chart/ha-chart-base.ts @@ -372,6 +372,16 @@ export class HaChartBase extends LitElement { }, mode: "x", }, + limits: { + x: { + min: "original", + max: "original", + }, + y: { + min: "original", + max: "original", + }, + }, }, }, }; From baa3ad3a453d5ce34e4f11d212f1f8cf0b7fe61d Mon Sep 17 00:00:00 2001 From: Petar Petrov Date: Fri, 6 Dec 2024 15:32:27 +0200 Subject: [PATCH 3/8] add comment for later --- src/components/chart/ha-chart-base.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/components/chart/ha-chart-base.ts b/src/components/chart/ha-chart-base.ts index 2ecf425aed77..717b3c5f168f 100644 --- a/src/components/chart/ha-chart-base.ts +++ b/src/components/chart/ha-chart-base.ts @@ -201,6 +201,7 @@ export class HaChartBase extends LitElement { this.chart.data = this.data; } if (changedProps.has("options")) { + // @FIXME: this resets the chart zoom because min/max changed this.chart.options = this._createOptions(); } this.chart.update("none"); From 4e3d3fb32e6cbcac5cda5b1e47d9b22a4a5ee131 Mon Sep 17 00:00:00 2001 From: Petar Petrov Date: Fri, 6 Dec 2024 16:55:18 +0200 Subject: [PATCH 4/8] don't update the chart if zoomed/panned --- src/components/chart/ha-chart-base.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/components/chart/ha-chart-base.ts b/src/components/chart/ha-chart-base.ts index 717b3c5f168f..a9425bcedcbf 100644 --- a/src/components/chart/ha-chart-base.ts +++ b/src/components/chart/ha-chart-base.ts @@ -200,8 +200,9 @@ export class HaChartBase extends LitElement { } this.chart.data = this.data; } - if (changedProps.has("options")) { - // @FIXME: this resets the chart zoom because min/max changed + if (changedProps.has("options") && !this.chart.isZoomedOrPanned()) { + // this resets the chart zoom because min/max scales changed + // so we only do it if the user is not zooming or panning this.chart.options = this._createOptions(); } this.chart.update("none"); From 65ca8d318ed8944e61f99123bb8bbfeee44e9742 Mon Sep 17 00:00:00 2001 From: Petar Petrov Date: Fri, 6 Dec 2024 17:03:55 +0200 Subject: [PATCH 5/8] zoom out on double click --- src/components/chart/ha-chart-base.ts | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/components/chart/ha-chart-base.ts b/src/components/chart/ha-chart-base.ts index a9425bcedcbf..2367590d8369 100644 --- a/src/components/chart/ha-chart-base.ts +++ b/src/components/chart/ha-chart-base.ts @@ -260,6 +260,7 @@ export class HaChartBase extends LitElement { "padding-inline-start": `${this._paddingYAxisInternal}px`, "padding-inline-end": 0, })} + @click=${this._handleChartClick} > ${this._tooltip @@ -409,6 +410,13 @@ export class HaChartBase extends LitElement { ]; } + private _handleChartClick(ev: MouseEvent) { + if (ev.detail === 2) { + // reset zoom with double click + this.chart?.resetZoom(); + } + } + private _legendClick(ev) { if (!this.chart) { return; From 3165e0b96db866e82a9669b19be2131ee21e8945 Mon Sep 17 00:00:00 2001 From: Petar Petrov Date: Wed, 11 Dec 2024 15:34:22 +0200 Subject: [PATCH 6/8] bare drag to pan, ctrl to zoom --- src/components/chart/ha-chart-base.ts | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/src/components/chart/ha-chart-base.ts b/src/components/chart/ha-chart-base.ts index cfdbeb8a7389..4445005b209e 100644 --- a/src/components/chart/ha-chart-base.ts +++ b/src/components/chart/ha-chart-base.ts @@ -261,7 +261,6 @@ export class HaChartBase extends LitElement { "padding-inline-start": `${this._paddingYAxisInternal}px`, "padding-inline-end": 0, })} - @click=${this._handleChartClick} > ${this._tooltip @@ -365,7 +364,6 @@ export class HaChartBase extends LitElement { ...this.options?.plugins?.zoom, pan: { enabled: true, - modifierKey: "shift", }, zoom: { pinch: { @@ -373,6 +371,11 @@ export class HaChartBase extends LitElement { }, drag: { enabled: true, + modifierKey: "ctrl", + }, + wheel: { + enabled: true, + modifierKey: "ctrl", }, mode: "x", }, @@ -411,13 +414,6 @@ export class HaChartBase extends LitElement { ]; } - private _handleChartClick(ev: MouseEvent) { - if (ev.detail === 2) { - // reset zoom with double click - this.chart?.resetZoom(); - } - } - private _legendClick(ev) { if (!this.chart) { return; From 6620099e5080cf1c1dc2c111a46598e3af952229 Mon Sep 17 00:00:00 2001 From: Petar Petrov Date: Wed, 11 Dec 2024 16:23:28 +0200 Subject: [PATCH 7/8] add overlay hint for zooming --- src/components/chart/ha-chart-base.ts | 51 ++++++++++++++++++++++++++- src/translations/en.json | 3 +- 2 files changed, 52 insertions(+), 2 deletions(-) diff --git a/src/components/chart/ha-chart-base.ts b/src/components/chart/ha-chart-base.ts index 4445005b209e..eef2bddb51c6 100644 --- a/src/components/chart/ha-chart-base.ts +++ b/src/components/chart/ha-chart-base.ts @@ -64,6 +64,8 @@ export class HaChartBase extends LitElement { @state() private _hiddenDatasets: Set = new Set(); + @state() private _showZoomHint = false; + private _paddingUpdateCount = 0; private _paddingUpdateLock = false; @@ -251,7 +253,7 @@ export class HaChartBase extends LitElement { })} >
+
+
+ ${this.hass.localize("ui.components.history_charts.zoom_hint")} +
+
${this._tooltip ? html`
Date: Thu, 12 Dec 2024 09:50:28 +0200 Subject: [PATCH 8/8] =?UTF-8?q?use=20=E2=8C=98=20+=20scroll=20to=20zoom=20?= =?UTF-8?q?on=20mac?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/chart/ha-chart-base.ts | 15 +++++++++++---- src/translations/en.json | 3 ++- src/util/is_mac.ts | 1 + 3 files changed, 14 insertions(+), 5 deletions(-) create mode 100644 src/util/is_mac.ts diff --git a/src/components/chart/ha-chart-base.ts b/src/components/chart/ha-chart-base.ts index eef2bddb51c6..06f7a52f36c0 100644 --- a/src/components/chart/ha-chart-base.ts +++ b/src/components/chart/ha-chart-base.ts @@ -14,6 +14,7 @@ import { fireEvent } from "../../common/dom/fire_event"; import { clamp } from "../../common/number/clamp"; import type { HomeAssistant } from "../../types"; import { debounce } from "../../common/util/debounce"; +import { isMac } from "../../util/is_mac"; export const MIN_TIME_BETWEEN_UPDATES = 60 * 5 * 1000; @@ -272,7 +273,11 @@ export class HaChartBase extends LitElement { })}" >
- ${this.hass.localize("ui.components.history_charts.zoom_hint")} + ${isMac + ? this.hass.localize( + "ui.components.history_charts.zoom_hint_mac" + ) + : this.hass.localize("ui.components.history_charts.zoom_hint")}
${this._tooltip @@ -358,6 +363,7 @@ export class HaChartBase extends LitElement { } private _createOptions(): ChartOptions { + const modifierKey = isMac ? "meta" : "ctrl"; return { maintainAspectRatio: false, ...this.options, @@ -383,11 +389,11 @@ export class HaChartBase extends LitElement { }, drag: { enabled: true, - modifierKey: "ctrl", + modifierKey, }, wheel: { enabled: true, - modifierKey: "ctrl", + modifierKey, }, mode: "x", }, @@ -427,7 +433,8 @@ export class HaChartBase extends LitElement { } private _handleChartScroll(ev: MouseEvent) { - if (!ev.ctrlKey && !this._showZoomHint) { + const modifier = isMac ? "metaKey" : "ctrlKey"; + if (!ev[modifier] && !this._showZoomHint) { this._showZoomHint = true; setTimeout(() => { this._showZoomHint = false; diff --git a/src/translations/en.json b/src/translations/en.json index aca53263df81..675e0ef796bb 100644 --- a/src/translations/en.json +++ b/src/translations/en.json @@ -829,7 +829,8 @@ "duration": "Duration", "source_history": "Source: History", "source_stats": "Source: Long term statistics", - "zoom_hint": "Use ctrl + scroll to zoom in/out" + "zoom_hint": "Use ctrl + scroll to zoom in/out", + "zoom_hint_mac": "Use ⌘ + scroll to zoom in/out" }, "map": { "error": "Unable to load map" diff --git a/src/util/is_mac.ts b/src/util/is_mac.ts new file mode 100644 index 000000000000..0a6c2f52124d --- /dev/null +++ b/src/util/is_mac.ts @@ -0,0 +1 @@ +export const isMac = /Mac/i.test(navigator.userAgent);