Make part of the chart rendering async for large datasets (#24260)

This commit is contained in:
Petar Petrov 2025-02-18 08:57:07 +02:00 committed by GitHub
parent cdd17eed2e
commit c52217c1ce
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

View File

@ -6,7 +6,6 @@ import type { DataZoomComponentOption } from "echarts/components";
import type { EChartsType } from "echarts/core";
import type {
ECElementEvent,
SetOptionOpts,
XAXisOption,
YAXisOption,
} from "echarts/types/dist/shared";
@ -25,6 +24,7 @@ import type { HomeAssistant } from "../../types";
import { isMac } from "../../util/is_mac";
import "../ha-icon-button";
import { formatTimeLabel } from "./axis-label";
import { ensureArray } from "../../common/array/ensure-array";
export const MIN_TIME_BETWEEN_UPDATES = 60 * 5 * 1000;
@ -68,12 +68,16 @@ export class HaChartBase extends LitElement {
private _listeners: (() => void)[] = [];
private _originalZrFlush?: () => void;
public disconnectedCallback() {
super.disconnectedCallback();
while (this._listeners.length) {
this._listeners.pop()!();
}
this.chart?.dispose();
this.chart = undefined;
this._originalZrFlush = undefined;
}
public connectedCallback() {
@ -86,7 +90,7 @@ export class HaChartBase extends LitElement {
listenMediaQuery("(prefers-reduced-motion)", (matches) => {
if (this._reducedMotion !== matches) {
this._reducedMotion = matches;
this.chart?.setOption({ animation: !this._reducedMotion });
this._setChartOptions({ animation: !this._reducedMotion });
}
})
);
@ -96,7 +100,7 @@ export class HaChartBase extends LitElement {
if ((isMac && ev.key === "Meta") || (!isMac && ev.key === "Control")) {
this._modifierPressed = true;
if (!this.options?.dataZoom) {
this.chart?.setOption({ dataZoom: this._getDataZoomConfig() });
this._setChartOptions({ dataZoom: this._getDataZoomConfig() });
}
}
};
@ -105,7 +109,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._setChartOptions({ dataZoom: this._getDataZoomConfig() });
}
}
};
@ -131,10 +135,8 @@ export class HaChartBase extends LitElement {
return;
}
let chartOptions: ECOption = {};
const chartUpdateParams: SetOptionOpts = { lazyUpdate: true };
if (changedProps.has("data")) {
chartOptions.series = this.data;
chartUpdateParams.replaceMerge = ["series"];
}
if (changedProps.has("options")) {
chartOptions = { ...chartOptions, ...this._createOptions() };
@ -142,7 +144,7 @@ export class HaChartBase extends LitElement {
chartOptions.dataZoom = this._getDataZoomConfig();
}
if (Object.keys(chartOptions).length > 0) {
this.chart.setOption(chartOptions, chartUpdateParams);
this._setChartOptions(chartOptions);
}
}
@ -509,6 +511,31 @@ export class HaChartBase extends LitElement {
return Math.max(this.clientWidth / 2, 200);
}
private _setChartOptions(options: ECOption) {
if (!this.chart) {
return;
}
if (!this._originalZrFlush) {
const dataSize = ensureArray(this.data).reduce(
(acc, series) => acc + (series.data as any[]).length,
0
);
if (dataSize > 10000) {
// for large datasets zr.flush takes 30-40% of the render time
// so we delay it a bit to avoid blocking the main thread
const zr = this.chart.getZr();
this._originalZrFlush = zr.flush.bind(zr);
zr.flush = () => {
setTimeout(() => {
this._originalZrFlush?.();
}, 10);
};
}
}
const replaceMerge = options.series ? ["series"] : [];
this.chart.setOption(options, { replaceMerge });
}
private _handleZoomReset() {
this.chart?.dispatchAction({ type: "dataZoom", start: 0, end: 100 });
}