mirror of
https://github.com/home-assistant/frontend.git
synced 2025-07-28 03:36:44 +00:00
Custom variable down sampling for line charts (#25561)
This commit is contained in:
parent
4d8176ad6e
commit
81ba2db93a
72
src/components/chart/down-sample.ts
Normal file
72
src/components/chart/down-sample.ts
Normal file
@ -0,0 +1,72 @@
|
||||
import type { LineSeriesOption } from "echarts";
|
||||
|
||||
export function downSampleLineData(
|
||||
data: LineSeriesOption["data"],
|
||||
chartWidth: number,
|
||||
minX?: number,
|
||||
maxX?: number
|
||||
) {
|
||||
if (!data || data.length < 10) {
|
||||
return data;
|
||||
}
|
||||
const width = chartWidth * window.devicePixelRatio;
|
||||
if (data.length <= width) {
|
||||
return data;
|
||||
}
|
||||
const min = minX ?? getPointData(data[0]!)[0];
|
||||
const max = maxX ?? getPointData(data[data.length - 1]!)[0];
|
||||
const step = Math.floor((max - min) / width);
|
||||
const frames = new Map<
|
||||
number,
|
||||
{
|
||||
min: { point: (typeof data)[number]; x: number; y: number };
|
||||
max: { point: (typeof data)[number]; x: number; y: number };
|
||||
}
|
||||
>();
|
||||
|
||||
// Group points into frames
|
||||
for (const point of data) {
|
||||
const pointData = getPointData(point);
|
||||
if (!Array.isArray(pointData)) continue;
|
||||
const x = Number(pointData[0]);
|
||||
const y = Number(pointData[1]);
|
||||
if (isNaN(x) || isNaN(y)) continue;
|
||||
|
||||
const frameIndex = Math.floor((x - min) / step);
|
||||
const frame = frames.get(frameIndex);
|
||||
if (!frame) {
|
||||
frames.set(frameIndex, { min: { point, x, y }, max: { point, x, y } });
|
||||
} else {
|
||||
if (frame.min.y > y) {
|
||||
frame.min = { point, x, y };
|
||||
}
|
||||
if (frame.max.y < y) {
|
||||
frame.max = { point, x, y };
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Convert frames back to points
|
||||
const result: typeof data = [];
|
||||
for (const [_i, frame] of frames) {
|
||||
// Use min/max points to preserve visual accuracy
|
||||
// The order of the data must be preserved so max may be before min
|
||||
if (frame.min.x > frame.max.x) {
|
||||
result.push(frame.max.point);
|
||||
}
|
||||
result.push(frame.min.point);
|
||||
if (frame.min.x < frame.max.x) {
|
||||
result.push(frame.max.point);
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
function getPointData(point: NonNullable<LineSeriesOption["data"]>[number]) {
|
||||
const pointData =
|
||||
point && typeof point === "object" && "value" in point
|
||||
? point.value
|
||||
: point;
|
||||
return pointData as number[];
|
||||
}
|
@ -27,6 +27,7 @@ import "../ha-icon-button";
|
||||
import { formatTimeLabel } from "./axis-label";
|
||||
import { ensureArray } from "../../common/array/ensure-array";
|
||||
import "../chips/ha-assist-chip";
|
||||
import { downSampleLineData } from "./down-sample";
|
||||
|
||||
export const MIN_TIME_BETWEEN_UPDATES = 60 * 5 * 1000;
|
||||
const LEGEND_OVERFLOW_LIMIT = 10;
|
||||
@ -613,19 +614,21 @@ export class HaChartBase extends LitElement {
|
||||
}
|
||||
|
||||
private _getSeries() {
|
||||
const series = ensureArray(this.data).filter(
|
||||
(d) => !this._hiddenDatasets.has(String(d.name ?? d.id))
|
||||
);
|
||||
const xAxis = (this.options?.xAxis?.[0] ?? this.options?.xAxis) as
|
||||
| XAXisOption
|
||||
| undefined;
|
||||
const yAxis = (this.options?.yAxis?.[0] ?? this.options?.yAxis) as
|
||||
| YAXisOption
|
||||
| undefined;
|
||||
if (yAxis?.type === "log") {
|
||||
// set <=0 values to null so they render as gaps on a log graph
|
||||
return series.map((d) =>
|
||||
d.type === "line"
|
||||
? {
|
||||
...d,
|
||||
data: d.data?.map((v) =>
|
||||
const series = ensureArray(this.data)
|
||||
.filter((d) => !this._hiddenDatasets.has(String(d.name ?? d.id)))
|
||||
.map((s) => {
|
||||
if (s.type === "line") {
|
||||
if (yAxis?.type === "log") {
|
||||
// set <=0 values to null so they render as gaps on a log graph
|
||||
return {
|
||||
...s,
|
||||
data: s.data?.map((v) =>
|
||||
Array.isArray(v)
|
||||
? [
|
||||
v[0],
|
||||
@ -634,10 +637,26 @@ export class HaChartBase extends LitElement {
|
||||
]
|
||||
: v
|
||||
),
|
||||
}
|
||||
: d
|
||||
);
|
||||
}
|
||||
};
|
||||
}
|
||||
if (s.sampling === "minmax") {
|
||||
const minX =
|
||||
xAxis?.min && typeof xAxis.min === "number"
|
||||
? xAxis.min
|
||||
: undefined;
|
||||
const maxX =
|
||||
xAxis?.max && typeof xAxis.max === "number"
|
||||
? xAxis.max
|
||||
: undefined;
|
||||
return {
|
||||
...s,
|
||||
sampling: undefined,
|
||||
data: downSampleLineData(s.data, this.clientWidth, minX, maxX),
|
||||
};
|
||||
}
|
||||
}
|
||||
return s;
|
||||
});
|
||||
return series;
|
||||
}
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user