mirror of
https://github.com/home-assistant/frontend.git
synced 2025-07-28 11:46:42 +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 { formatTimeLabel } from "./axis-label";
|
||||||
import { ensureArray } from "../../common/array/ensure-array";
|
import { ensureArray } from "../../common/array/ensure-array";
|
||||||
import "../chips/ha-assist-chip";
|
import "../chips/ha-assist-chip";
|
||||||
|
import { downSampleLineData } from "./down-sample";
|
||||||
|
|
||||||
export const MIN_TIME_BETWEEN_UPDATES = 60 * 5 * 1000;
|
export const MIN_TIME_BETWEEN_UPDATES = 60 * 5 * 1000;
|
||||||
const LEGEND_OVERFLOW_LIMIT = 10;
|
const LEGEND_OVERFLOW_LIMIT = 10;
|
||||||
@ -613,19 +614,21 @@ export class HaChartBase extends LitElement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private _getSeries() {
|
private _getSeries() {
|
||||||
const series = ensureArray(this.data).filter(
|
const xAxis = (this.options?.xAxis?.[0] ?? this.options?.xAxis) as
|
||||||
(d) => !this._hiddenDatasets.has(String(d.name ?? d.id))
|
| XAXisOption
|
||||||
);
|
| undefined;
|
||||||
const yAxis = (this.options?.yAxis?.[0] ?? this.options?.yAxis) as
|
const yAxis = (this.options?.yAxis?.[0] ?? this.options?.yAxis) as
|
||||||
| YAXisOption
|
| YAXisOption
|
||||||
| undefined;
|
| undefined;
|
||||||
if (yAxis?.type === "log") {
|
const series = ensureArray(this.data)
|
||||||
// set <=0 values to null so they render as gaps on a log graph
|
.filter((d) => !this._hiddenDatasets.has(String(d.name ?? d.id)))
|
||||||
return series.map((d) =>
|
.map((s) => {
|
||||||
d.type === "line"
|
if (s.type === "line") {
|
||||||
? {
|
if (yAxis?.type === "log") {
|
||||||
...d,
|
// set <=0 values to null so they render as gaps on a log graph
|
||||||
data: d.data?.map((v) =>
|
return {
|
||||||
|
...s,
|
||||||
|
data: s.data?.map((v) =>
|
||||||
Array.isArray(v)
|
Array.isArray(v)
|
||||||
? [
|
? [
|
||||||
v[0],
|
v[0],
|
||||||
@ -634,10 +637,26 @@ export class HaChartBase extends LitElement {
|
|||||||
]
|
]
|
||||||
: v
|
: 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;
|
return series;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user