From dc76a42aaa5a7ff616bdb31b964b52920eb67333 Mon Sep 17 00:00:00 2001 From: Petar Petrov Date: Mon, 10 Nov 2025 15:23:26 +0200 Subject: [PATCH] Smooth sensor card more when "Show more detail" is disabled (#27891) * Smooth sensor card more when "Show more detail" is disabled * Set minimum sample points to 10 --- src/components/chart/down-sample.ts | 70 +++++++++++++------ .../lovelace/common/graph/coordinates.ts | 11 +-- .../header-footer/hui-graph-header-footer.ts | 10 ++- 3 files changed, 61 insertions(+), 30 deletions(-) diff --git a/src/components/chart/down-sample.ts b/src/components/chart/down-sample.ts index 47855592c1..85ccf9c135 100644 --- a/src/components/chart/down-sample.ts +++ b/src/components/chart/down-sample.ts @@ -6,7 +6,8 @@ export function downSampleLineData< data: T[] | undefined, maxDetails: number, minX?: number, - maxX?: number + maxX?: number, + useMean = false ): T[] { if (!data) { return []; @@ -17,15 +18,13 @@ export function downSampleLineData< const min = minX ?? getPointData(data[0]!)[0]; const max = maxX ?? getPointData(data[data.length - 1]!)[0]; const step = Math.ceil((max - min) / Math.floor(maxDetails)); - 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 + const frames = new Map< + number, + { point: (typeof data)[number]; x: number; y: number }[] + >(); + for (const point of data) { const pointData = getPointData(point); if (!Array.isArray(pointData)) continue; @@ -36,28 +35,53 @@ export function downSampleLineData< 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 } }); + frames.set(frameIndex, [{ point, x, y }]); } else { - if (frame.min.y > y) { - frame.min = { point, x, y }; - } - if (frame.max.y < y) { - frame.max = { point, x, y }; - } + frame.push({ point, x, y }); } } // Convert frames back to points const result: T[] = []; - 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); + + if (useMean) { + // Use mean values for each frame + for (const [_i, framePoints] of frames) { + const sumY = framePoints.reduce((acc, p) => acc + p.y, 0); + const meanY = sumY / framePoints.length; + const sumX = framePoints.reduce((acc, p) => acc + p.x, 0); + const meanX = sumX / framePoints.length; + + const firstPoint = framePoints[0].point; + const pointData = getPointData(firstPoint); + const meanPoint = ( + Array.isArray(pointData) ? [meanX, meanY] : { value: [meanX, meanY] } + ) as T; + result.push(meanPoint); } - result.push(frame.min.point); - if (frame.min.x < frame.max.x) { - result.push(frame.max.point); + } else { + // Use min/max values for each frame + for (const [_i, framePoints] of frames) { + let minPoint = framePoints[0]; + let maxPoint = framePoints[0]; + + for (const p of framePoints) { + if (p.y < minPoint.y) { + minPoint = p; + } + if (p.y > maxPoint.y) { + maxPoint = p; + } + } + + // The order of the data must be preserved so max may be before min + if (minPoint.x > maxPoint.x) { + result.push(maxPoint.point); + } + result.push(minPoint.point); + if (minPoint.x < maxPoint.x) { + result.push(maxPoint.point); + } } } diff --git a/src/panels/lovelace/common/graph/coordinates.ts b/src/panels/lovelace/common/graph/coordinates.ts index 533bdd706b..73a1ffa43b 100644 --- a/src/panels/lovelace/common/graph/coordinates.ts +++ b/src/panels/lovelace/common/graph/coordinates.ts @@ -51,7 +51,8 @@ export const coordinates = ( width: number, height: number, maxDetails: number, - limits?: { minX?: number; maxX?: number; minY?: number; maxY?: number } + limits?: { minX?: number; maxX?: number; minY?: number; maxY?: number }, + useMean = false ) => { history = history.filter((item) => !Number.isNaN(item[1])); @@ -59,7 +60,8 @@ export const coordinates = ( history, maxDetails, limits?.minX, - limits?.maxX + limits?.maxX, + useMean ); return calcPoints(sampledData, width, height, limits); }; @@ -69,7 +71,8 @@ export const coordinatesMinimalResponseCompressedState = ( width: number, height: number, maxDetails: number, - limits?: { minX?: number; maxX?: number; minY?: number; maxY?: number } + limits?: { minX?: number; maxX?: number; minY?: number; maxY?: number }, + useMean = false ) => { if (!history?.length) { return { points: [], yAxisOrigin: 0 }; @@ -81,5 +84,5 @@ export const coordinatesMinimalResponseCompressedState = ( item.lu * 1000, Number(item.s), ]); - return coordinates(mappedHistory, width, height, maxDetails, limits); + return coordinates(mappedHistory, width, height, maxDetails, limits, useMean); }; diff --git a/src/panels/lovelace/header-footer/hui-graph-header-footer.ts b/src/panels/lovelace/header-footer/hui-graph-header-footer.ts index 9ce291f3d2..42125d7aec 100644 --- a/src/panels/lovelace/header-footer/hui-graph-header-footer.ts +++ b/src/panels/lovelace/header-footer/hui-graph-header-footer.ts @@ -155,16 +155,20 @@ export class HuiGraphHeaderFooter } const width = this.clientWidth || this.offsetWidth; // sample to 1 point per hour or 1 point per 5 pixels - const maxDetails = + const maxDetails = Math.max( + 10, this._config.detail! > 1 ? Math.max(width / 5, this._config.hours_to_show!) - : this._config.hours_to_show!; + : this._config.hours_to_show! + ); + const useMean = this._config.detail !== 2; const { points } = coordinatesMinimalResponseCompressedState( combinedHistory[this._config.entity], width, width / 5, maxDetails, - { minY: this._config.limits?.min, maxY: this._config.limits?.max } + { minY: this._config.limits?.min, maxY: this._config.limits?.max }, + useMean ); this._coordinates = points; },