mirror of
https://github.com/home-assistant/frontend.git
synced 2025-07-24 09:46:36 +00:00
Optimize chart performance (#24215)
* Stop listening to chart scroll events to improve performance * only set visualmap when needed * Reduce statistics detail for long periods * reduce calls to `setOption` * tweak zoom modifier code * always replace series * revert statistics detail change
This commit is contained in:
parent
d8e36894a0
commit
043e8d6e2e
@ -6,6 +6,7 @@ import type { DataZoomComponentOption } from "echarts/components";
|
||||
import type { EChartsType } from "echarts/core";
|
||||
import type {
|
||||
ECElementEvent,
|
||||
SetOptionOpts,
|
||||
XAXisOption,
|
||||
YAXisOption,
|
||||
} from "echarts/types/dist/shared";
|
||||
@ -83,19 +84,19 @@ export class HaChartBase extends LitElement {
|
||||
|
||||
this._listeners.push(
|
||||
listenMediaQuery("(prefers-reduced-motion)", (matches) => {
|
||||
this._reducedMotion = matches;
|
||||
this.chart?.setOption({ animation: !this._reducedMotion });
|
||||
if (this._reducedMotion !== matches) {
|
||||
this._reducedMotion = matches;
|
||||
this.chart?.setOption({ animation: !this._reducedMotion });
|
||||
}
|
||||
})
|
||||
);
|
||||
|
||||
// Add keyboard event listeners
|
||||
const handleKeyDown = (ev: KeyboardEvent) => {
|
||||
if ((isMac && ev.metaKey) || (!isMac && ev.ctrlKey)) {
|
||||
if ((isMac && ev.key === "Meta") || (!isMac && ev.key === "Control")) {
|
||||
this._modifierPressed = true;
|
||||
if (!this.options?.dataZoom) {
|
||||
this.chart?.setOption({
|
||||
dataZoom: this._getDataZoomConfig(),
|
||||
});
|
||||
this.chart?.setOption({ dataZoom: this._getDataZoomConfig() });
|
||||
}
|
||||
}
|
||||
};
|
||||
@ -104,9 +105,7 @@ export class HaChartBase extends LitElement {
|
||||
if ((isMac && ev.key === "Meta") || (!isMac && ev.key === "Control")) {
|
||||
this._modifierPressed = false;
|
||||
if (!this.options?.dataZoom) {
|
||||
this.chart?.setOption({
|
||||
dataZoom: this._getDataZoomConfig(),
|
||||
});
|
||||
this.chart?.setOption({ dataZoom: this._getDataZoomConfig() });
|
||||
}
|
||||
}
|
||||
};
|
||||
@ -124,27 +123,26 @@ export class HaChartBase extends LitElement {
|
||||
}
|
||||
|
||||
public willUpdate(changedProps: PropertyValues): void {
|
||||
super.willUpdate(changedProps);
|
||||
|
||||
if (!this.hasUpdated || !this.chart) {
|
||||
if (!this.chart) {
|
||||
return;
|
||||
}
|
||||
if (changedProps.has("_themes")) {
|
||||
this._setupChart();
|
||||
return;
|
||||
}
|
||||
let chartOptions: ECOption = {};
|
||||
const chartUpdateParams: SetOptionOpts = { lazyUpdate: true };
|
||||
if (changedProps.has("data")) {
|
||||
this.chart.setOption(
|
||||
{ series: this.data },
|
||||
{ lazyUpdate: true, replaceMerge: ["series"] }
|
||||
);
|
||||
chartOptions.series = this.data;
|
||||
chartUpdateParams.replaceMerge = ["series"];
|
||||
}
|
||||
if (changedProps.has("options") || changedProps.has("_isZoomed")) {
|
||||
this.chart.setOption(this._createOptions(), {
|
||||
lazyUpdate: true,
|
||||
// if we replace the whole object, it will reset the dataZoom
|
||||
replaceMerge: ["grid"],
|
||||
});
|
||||
if (changedProps.has("options")) {
|
||||
chartOptions = { ...chartOptions, ...this._createOptions() };
|
||||
} else if (this._isTouchDevice && changedProps.has("_isZoomed")) {
|
||||
chartOptions.dataZoom = this._getDataZoomConfig();
|
||||
}
|
||||
if (Object.keys(chartOptions).length > 0) {
|
||||
this.chart.setOption(chartOptions, chartUpdateParams);
|
||||
}
|
||||
}
|
||||
|
||||
@ -158,7 +156,6 @@ export class HaChartBase extends LitElement {
|
||||
style=${styleMap({
|
||||
height: this.height ?? `${this._getDefaultHeight()}px`,
|
||||
})}
|
||||
@wheel=${this._handleWheel}
|
||||
>
|
||||
<div class="chart"></div>
|
||||
${this._isZoomed
|
||||
@ -240,8 +237,8 @@ export class HaChartBase extends LitElement {
|
||||
type: "inside",
|
||||
orient: "horizontal",
|
||||
filterMode: "none",
|
||||
moveOnMouseMove: this._isZoomed,
|
||||
preventDefaultMouseMove: this._isZoomed,
|
||||
moveOnMouseMove: !this._isTouchDevice || this._isZoomed,
|
||||
preventDefaultMouseMove: !this._isTouchDevice || this._isZoomed,
|
||||
zoomLock: !this._isTouchDevice && !this._modifierPressed,
|
||||
};
|
||||
}
|
||||
@ -514,23 +511,6 @@ export class HaChartBase extends LitElement {
|
||||
|
||||
private _handleZoomReset() {
|
||||
this.chart?.dispatchAction({ type: "dataZoom", start: 0, end: 100 });
|
||||
this._modifierPressed = false;
|
||||
}
|
||||
|
||||
private _handleWheel(e: WheelEvent) {
|
||||
// if the window is not focused, we don't receive the keydown events but scroll still works
|
||||
if (!this.options?.dataZoom) {
|
||||
const modifierPressed = (isMac && e.metaKey) || (!isMac && e.ctrlKey);
|
||||
if (modifierPressed) {
|
||||
e.preventDefault();
|
||||
}
|
||||
if (modifierPressed !== this._modifierPressed) {
|
||||
this._modifierPressed = modifierPressed;
|
||||
this.chart?.setOption({
|
||||
dataZoom: this._getDataZoomConfig(),
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static styles = css`
|
||||
|
@ -75,6 +75,8 @@ export class StateHistoryChartLine extends LitElement {
|
||||
|
||||
@state() private _yWidth = 25;
|
||||
|
||||
@state() private _visualMap?: VisualMapComponentOption[];
|
||||
|
||||
private _chartTime: Date = new Date();
|
||||
|
||||
protected render() {
|
||||
@ -92,7 +94,7 @@ export class StateHistoryChartLine extends LitElement {
|
||||
`;
|
||||
}
|
||||
|
||||
private _renderTooltip(params: any) {
|
||||
private _renderTooltip = (params: any) => {
|
||||
const time = params[0].axisValue;
|
||||
const title =
|
||||
formatDateTimeWithSeconds(
|
||||
@ -115,7 +117,7 @@ export class StateHistoryChartLine extends LitElement {
|
||||
return;
|
||||
}
|
||||
// If the datapoint is not found, we need to find the last datapoint before the current time
|
||||
let lastData;
|
||||
let lastData: any;
|
||||
const data = dataset.data || [];
|
||||
for (let i = data.length - 1; i >= 0; i--) {
|
||||
const point = data[i];
|
||||
@ -175,7 +177,7 @@ export class StateHistoryChartLine extends LitElement {
|
||||
})
|
||||
.join("<br>")
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
private _datasetHidden(ev: CustomEvent) {
|
||||
this._hiddenStats.add(ev.detail.name);
|
||||
@ -208,8 +210,8 @@ export class StateHistoryChartLine extends LitElement {
|
||||
changedProps.has("minYAxis") ||
|
||||
changedProps.has("maxYAxis") ||
|
||||
changedProps.has("fitYData") ||
|
||||
changedProps.has("_chartData") ||
|
||||
changedProps.has("paddingYAxis") ||
|
||||
changedProps.has("_visualMap") ||
|
||||
changedProps.has("_yWidth")
|
||||
) {
|
||||
const rtl = computeRTL(this.hass);
|
||||
@ -280,37 +282,11 @@ export class StateHistoryChartLine extends LitElement {
|
||||
right: rtl ? Math.max(this.paddingYAxis, this._yWidth) : 1,
|
||||
bottom: 30,
|
||||
},
|
||||
visualMap: this._chartData
|
||||
.map((_, seriesIndex) => {
|
||||
const dataIndex = this._datasetToDataIndex[seriesIndex];
|
||||
const data = this.data[dataIndex];
|
||||
if (!data.statistics || data.statistics.length === 0) {
|
||||
return false;
|
||||
}
|
||||
// render stat data with a slightly transparent line
|
||||
const firstStateTS =
|
||||
data.states[0]?.last_changed ?? this.endTime.getTime();
|
||||
return {
|
||||
show: false,
|
||||
seriesIndex,
|
||||
dimension: 0,
|
||||
pieces: [
|
||||
{
|
||||
max: firstStateTS - 0.01,
|
||||
colorAlpha: 0.5,
|
||||
},
|
||||
{
|
||||
min: firstStateTS,
|
||||
colorAlpha: 1,
|
||||
},
|
||||
],
|
||||
};
|
||||
})
|
||||
.filter(Boolean) as VisualMapComponentOption[],
|
||||
visualMap: this._visualMap,
|
||||
tooltip: {
|
||||
trigger: "axis",
|
||||
appendTo: document.body,
|
||||
formatter: this._renderTooltip.bind(this),
|
||||
formatter: this._renderTooltip,
|
||||
},
|
||||
};
|
||||
}
|
||||
@ -725,6 +701,33 @@ export class StateHistoryChartLine extends LitElement {
|
||||
this._chartData = datasets;
|
||||
this._entityIds = entityIds;
|
||||
this._datasetToDataIndex = datasetToDataIndex;
|
||||
const visualMap: VisualMapComponentOption[] = [];
|
||||
this._chartData.forEach((_, seriesIndex) => {
|
||||
const dataIndex = this._datasetToDataIndex[seriesIndex];
|
||||
const data = this.data[dataIndex];
|
||||
if (!data.statistics || data.statistics.length === 0) {
|
||||
return;
|
||||
}
|
||||
// render stat data with a slightly transparent line
|
||||
const firstStateTS =
|
||||
data.states[0]?.last_changed ?? this.endTime.getTime();
|
||||
visualMap.push({
|
||||
show: false,
|
||||
seriesIndex,
|
||||
dimension: 0,
|
||||
pieces: [
|
||||
{
|
||||
max: firstStateTS - 0.01,
|
||||
colorAlpha: 0.5,
|
||||
},
|
||||
{
|
||||
min: firstStateTS,
|
||||
colorAlpha: 1,
|
||||
},
|
||||
],
|
||||
});
|
||||
});
|
||||
this._visualMap = visualMap.length > 0 ? visualMap : undefined;
|
||||
}
|
||||
|
||||
private _clampYAxis(value?: number | ((values: any) => number)) {
|
||||
|
Loading…
x
Reference in New Issue
Block a user