mirror of
https://github.com/home-assistant/frontend.git
synced 2025-07-21 00:06:35 +00:00
Add zoom & pan to charts (#23183)
Co-authored-by: Bram Kragten <mail@bramkragten.nl>
This commit is contained in:
parent
f4bf999ae2
commit
784b7e4d04
@ -102,6 +102,7 @@
|
||||
"app-datepicker": "5.1.1",
|
||||
"barcode-detector": "2.3.1",
|
||||
"chart.js": "4.4.7",
|
||||
"chartjs-plugin-zoom": "2.2.0",
|
||||
"color-name": "2.0.0",
|
||||
"comlink": "4.4.2",
|
||||
"core-js": "3.39.0",
|
||||
|
@ -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;
|
||||
|
||||
@ -64,6 +65,8 @@ export class HaChartBase extends LitElement {
|
||||
|
||||
@state() private _hiddenDatasets: Set<number> = new Set();
|
||||
|
||||
@state() private _showZoomHint = false;
|
||||
|
||||
private _paddingUpdateCount = 0;
|
||||
|
||||
private _paddingUpdateLock = false;
|
||||
@ -201,7 +204,9 @@ export class HaChartBase extends LitElement {
|
||||
}
|
||||
this.chart.data = this.data;
|
||||
}
|
||||
if (changedProps.has("options")) {
|
||||
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");
|
||||
@ -249,7 +254,7 @@ export class HaChartBase extends LitElement {
|
||||
})}
|
||||
>
|
||||
<div
|
||||
class="chartContainer"
|
||||
class="chart-container"
|
||||
style=${styleMap({
|
||||
height: `${
|
||||
this.height ?? this._chartHeight ?? this.clientWidth / 2
|
||||
@ -259,8 +264,22 @@ export class HaChartBase extends LitElement {
|
||||
"padding-inline-start": `${this._paddingYAxisInternal}px`,
|
||||
"padding-inline-end": 0,
|
||||
})}
|
||||
@wheel=${this._handleChartScroll}
|
||||
>
|
||||
<canvas></canvas>
|
||||
<div
|
||||
class="zoom-hint ${classMap({
|
||||
visible: this._showZoomHint,
|
||||
})}"
|
||||
>
|
||||
<div>
|
||||
${isMac
|
||||
? this.hass.localize(
|
||||
"ui.components.history_charts.zoom_hint_mac"
|
||||
)
|
||||
: this.hass.localize("ui.components.history_charts.zoom_hint")}
|
||||
</div>
|
||||
</div>
|
||||
${this._tooltip
|
||||
? html`<div
|
||||
class="chartTooltip ${classMap({
|
||||
@ -343,7 +362,8 @@ export class HaChartBase extends LitElement {
|
||||
}
|
||||
}
|
||||
|
||||
private _createOptions() {
|
||||
private _createOptions(): ChartOptions {
|
||||
const modifierKey = isMac ? "meta" : "ctrl";
|
||||
return {
|
||||
maintainAspectRatio: false,
|
||||
...this.options,
|
||||
@ -358,6 +378,36 @@ export class HaChartBase extends LitElement {
|
||||
...this.options?.plugins?.legend,
|
||||
display: false,
|
||||
},
|
||||
zoom: {
|
||||
...this.options?.plugins?.zoom,
|
||||
pan: {
|
||||
enabled: true,
|
||||
},
|
||||
zoom: {
|
||||
pinch: {
|
||||
enabled: true,
|
||||
},
|
||||
drag: {
|
||||
enabled: true,
|
||||
modifierKey,
|
||||
},
|
||||
wheel: {
|
||||
enabled: true,
|
||||
modifierKey,
|
||||
},
|
||||
mode: "x",
|
||||
},
|
||||
limits: {
|
||||
x: {
|
||||
min: "original",
|
||||
max: "original",
|
||||
},
|
||||
y: {
|
||||
min: "original",
|
||||
max: "original",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
@ -382,6 +432,16 @@ export class HaChartBase extends LitElement {
|
||||
];
|
||||
}
|
||||
|
||||
private _handleChartScroll(ev: MouseEvent) {
|
||||
const modifier = isMac ? "metaKey" : "ctrlKey";
|
||||
if (!ev[modifier] && !this._showZoomHint) {
|
||||
this._showZoomHint = true;
|
||||
setTimeout(() => {
|
||||
this._showZoomHint = false;
|
||||
}, 1000);
|
||||
}
|
||||
}
|
||||
|
||||
private _legendClick(ev) {
|
||||
if (!this.chart) {
|
||||
return;
|
||||
@ -450,6 +510,9 @@ export class HaChartBase extends LitElement {
|
||||
height: 0;
|
||||
transition: height 300ms cubic-bezier(0.4, 0, 0.2, 1);
|
||||
}
|
||||
.chart-container {
|
||||
position: relative;
|
||||
}
|
||||
canvas {
|
||||
max-height: var(--chart-max-height, 400px);
|
||||
}
|
||||
@ -539,6 +602,31 @@ export class HaChartBase extends LitElement {
|
||||
font-weight: 300;
|
||||
word-break: break-all;
|
||||
}
|
||||
.zoom-hint {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
opacity: 0;
|
||||
transition: opacity 500ms cubic-bezier(0.4, 0, 0.2, 1);
|
||||
pointer-events: none;
|
||||
}
|
||||
.zoom-hint.visible {
|
||||
opacity: 1;
|
||||
}
|
||||
.zoom-hint > div {
|
||||
color: white;
|
||||
font-size: 1.5em;
|
||||
font-weight: 500;
|
||||
padding: 8px;
|
||||
border-radius: 8px;
|
||||
background: rgba(0, 0, 0, 0.3);
|
||||
box-shadow: 0 0 32px 32px rgba(0, 0, 0, 0.3);
|
||||
}
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
@ -1,3 +1,4 @@
|
||||
import ZoomPlugin from "chartjs-plugin-zoom";
|
||||
import {
|
||||
LineController,
|
||||
TimeScale,
|
||||
@ -35,5 +36,6 @@ Chart.register(
|
||||
TextBarElement,
|
||||
TimelineController,
|
||||
CategoryScale,
|
||||
LogarithmicScale
|
||||
LogarithmicScale,
|
||||
ZoomPlugin
|
||||
);
|
||||
|
@ -828,7 +828,9 @@
|
||||
"error": "Unable to load history",
|
||||
"duration": "Duration",
|
||||
"source_history": "Source: History",
|
||||
"source_stats": "Source: Long term statistics"
|
||||
"source_stats": "Source: Long term statistics",
|
||||
"zoom_hint": "Use ctrl + scroll to zoom in/out",
|
||||
"zoom_hint_mac": "Use ⌘ + scroll to zoom in/out"
|
||||
},
|
||||
"map": {
|
||||
"error": "Unable to load map"
|
||||
|
1
src/util/is_mac.ts
Normal file
1
src/util/is_mac.ts
Normal file
@ -0,0 +1 @@
|
||||
export const isMac = /Mac/i.test(navigator.userAgent);
|
28
yarn.lock
28
yarn.lock
@ -4594,10 +4594,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
|
||||
|
||||
@ -6426,6 +6426,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"
|
||||
@ -9028,6 +9040,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"
|
||||
@ -9238,6 +9257,7 @@ __metadata:
|
||||
barcode-detector: "npm:2.3.1"
|
||||
browserslist-useragent-regexp: "npm:4.1.3"
|
||||
chart.js: "npm:4.4.7"
|
||||
chartjs-plugin-zoom: "npm:2.2.0"
|
||||
color-name: "npm:2.0.0"
|
||||
comlink: "npm:4.4.2"
|
||||
core-js: "npm:3.39.0"
|
||||
|
Loading…
x
Reference in New Issue
Block a user