mirror of
https://github.com/home-assistant/frontend.git
synced 2025-07-21 16:26:43 +00:00
Improve sensor graph algorithm (#2069)
This commit is contained in:
parent
ef2aa2ea6f
commit
07cf1141c5
121
src/panels/lovelace/cards/hui-sensor-card.js
Normal file → Executable file
121
src/panels/lovelace/cards/hui-sensor-card.js
Normal file → Executable file
@ -29,6 +29,8 @@ class HuiSensorCard extends EventsMixin(LitElement) {
|
|||||||
_config: {},
|
_config: {},
|
||||||
_entity: {},
|
_entity: {},
|
||||||
_line: String,
|
_line: String,
|
||||||
|
_min: Number,
|
||||||
|
_max: Number,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -38,18 +40,21 @@ class HuiSensorCard extends EventsMixin(LitElement) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const cardConfig = {
|
const cardConfig = {
|
||||||
|
detail: 1,
|
||||||
icon: false,
|
icon: false,
|
||||||
hours_to_show: 24,
|
|
||||||
accuracy: 10,
|
|
||||||
height: 100,
|
height: 100,
|
||||||
line_width: 5,
|
hours_to_show: 24,
|
||||||
line_color: "var(--accent-color)",
|
line_color: "var(--accent-color)",
|
||||||
|
line_width: 5,
|
||||||
...config,
|
...config,
|
||||||
};
|
};
|
||||||
cardConfig.hours_to_show = Number(cardConfig.hours_to_show);
|
cardConfig.hours_to_show = Number(cardConfig.hours_to_show);
|
||||||
cardConfig.accuracy = Number(cardConfig.accuracy);
|
|
||||||
cardConfig.height = Number(cardConfig.height);
|
cardConfig.height = Number(cardConfig.height);
|
||||||
cardConfig.line_width = Number(cardConfig.line_width);
|
cardConfig.line_width = Number(cardConfig.line_width);
|
||||||
|
cardConfig.detail =
|
||||||
|
cardConfig.detail === 1 || cardConfig.detail === 2
|
||||||
|
? cardConfig.detail
|
||||||
|
: 1;
|
||||||
|
|
||||||
this._config = cardConfig;
|
this._config = cardConfig;
|
||||||
}
|
}
|
||||||
@ -109,53 +114,79 @@ class HuiSensorCard extends EventsMixin(LitElement) {
|
|||||||
return this._config.unit || item.attributes.unit_of_measurement;
|
return this._config.unit || item.attributes.unit_of_measurement;
|
||||||
}
|
}
|
||||||
|
|
||||||
_getGraph(items, width, height) {
|
_coordinates(history, hours, width, detail = 1) {
|
||||||
const values = this._getValueArr(items);
|
history = history.filter((item) => !Number.isNaN(Number(item.state)));
|
||||||
const coords = this._calcCoordinates(values, width, height);
|
this._min = Math.min.apply(Math, history.map((item) => Number(item.state)));
|
||||||
return this._getPath(coords);
|
this._max = Math.max.apply(Math, history.map((item) => Number(item.state)));
|
||||||
|
const now = new Date().getTime();
|
||||||
|
|
||||||
|
const reduce = (res, item, min = false) => {
|
||||||
|
const age = now - new Date(item.last_changed).getTime();
|
||||||
|
let key = Math.abs(age / (1000 * 3600) - hours);
|
||||||
|
if (min) {
|
||||||
|
key = (key - Math.floor(key)) * 60;
|
||||||
|
key = (Math.round(key / 10) * 10).toString()[0];
|
||||||
|
} else {
|
||||||
|
key = Math.floor(key);
|
||||||
|
}
|
||||||
|
if (!res[key]) res[key] = [];
|
||||||
|
res[key].push(item);
|
||||||
|
return res;
|
||||||
|
};
|
||||||
|
history = history.reduce((res, item) => reduce(res, item), []);
|
||||||
|
if (detail > 1) {
|
||||||
|
history = history.map((entry) =>
|
||||||
|
entry.reduce((res, item) => reduce(res, item, true), [])
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return this._calcPoints(history, hours, width, detail);
|
||||||
}
|
}
|
||||||
|
|
||||||
_getValueArr(items) {
|
_calcPoints(history, hours, width, detail = 1) {
|
||||||
return items
|
const coords = [];
|
||||||
.map((item) => Number(item.state))
|
|
||||||
.filter((val) => !Number.isNaN(val));
|
|
||||||
}
|
|
||||||
|
|
||||||
_calcCoordinates(values, width, height) {
|
|
||||||
const margin = this._config.line_width;
|
const margin = this._config.line_width;
|
||||||
|
const height = this._config.height - margin * 4;
|
||||||
width -= margin * 2;
|
width -= margin * 2;
|
||||||
height -= margin * 2;
|
let yRatio = (this._max - this._min) / height;
|
||||||
const min = Math.floor(Math.min.apply(null, values) * 0.95);
|
yRatio = yRatio !== 0 ? yRatio : height;
|
||||||
const max = Math.ceil(Math.max.apply(null, values) * 1.05);
|
let xRatio = width / (hours - (detail === 1 ? 1 : 0));
|
||||||
|
xRatio = isFinite(xRatio) ? xRatio : width;
|
||||||
|
const getCoords = (item, i, offset = 0, depth = 1) => {
|
||||||
|
if (depth > 1)
|
||||||
|
return item.forEach((subItem, index) =>
|
||||||
|
getCoords(subItem, i, index, depth - 1)
|
||||||
|
);
|
||||||
|
const average =
|
||||||
|
item.reduce((sum, entry) => sum + parseFloat(entry.state), 0) /
|
||||||
|
item.length;
|
||||||
|
|
||||||
if (values.length === 1) values.push(values[0]);
|
const x = xRatio * (i + offset / 6) + margin;
|
||||||
|
const y = height - (average - this._min) / yRatio + margin * 2;
|
||||||
|
return coords.push([x, y]);
|
||||||
|
};
|
||||||
|
|
||||||
const yRatio = (max - min) / height;
|
history.forEach((item, i) => getCoords(item, i, 0, detail));
|
||||||
const xRatio = width / (values.length - 1);
|
if (coords.length === 1) coords[1] = [width + margin, coords[0][1]];
|
||||||
|
coords.push([width + margin, coords[coords.length - 1][1]]);
|
||||||
return values.map((value, i) => {
|
return coords;
|
||||||
const y = height - (value - min) / yRatio || 0;
|
|
||||||
const x = xRatio * i + margin;
|
|
||||||
return [x, y];
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
_getPath(points) {
|
_getPath(coords) {
|
||||||
let next;
|
let next;
|
||||||
let Z;
|
let Z;
|
||||||
const X = 0;
|
const X = 0;
|
||||||
const Y = 1;
|
const Y = 1;
|
||||||
let path = "";
|
let path = "";
|
||||||
let point = points[0];
|
let last = coords.filter(Boolean)[0];
|
||||||
|
|
||||||
path += `M ${point[X]},${point[Y]}`;
|
path += `M ${last[X]},${last[Y]}`;
|
||||||
|
|
||||||
for (let i = 0; i < points.length; i++) {
|
for (let i = 0; i < coords.length; i++) {
|
||||||
next = points[i];
|
next = coords[i];
|
||||||
Z = this._midPoint(point[X], point[Y], next[X], next[Y]);
|
Z = this._midPoint(last[X], last[Y], next[X], next[Y]);
|
||||||
path += ` ${Z[X]},${Z[Y]}`;
|
path += ` ${Z[X]},${Z[Y]}`;
|
||||||
path += ` Q${next[X]},${next[Y]}`;
|
path += ` Q${next[X]},${next[Y]}`;
|
||||||
point = next;
|
last = next;
|
||||||
}
|
}
|
||||||
|
|
||||||
path += ` ${next[X]},${next[Y]}`;
|
path += ` ${next[X]},${next[Y]}`;
|
||||||
@ -177,21 +208,15 @@ class HuiSensorCard extends EventsMixin(LitElement) {
|
|||||||
startTime,
|
startTime,
|
||||||
endTime
|
endTime
|
||||||
);
|
);
|
||||||
const history = stateHistory[0];
|
|
||||||
const valArray = [history[history.length - 1]];
|
|
||||||
|
|
||||||
const accuracy =
|
if (stateHistory[0].length < 1) return;
|
||||||
this._config.accuracy <= history.length
|
const coords = this._coordinates(
|
||||||
? this._config.accuracy
|
stateHistory[0],
|
||||||
: history.length;
|
this._config.hours_to_show,
|
||||||
let increment = Math.ceil(history.length / accuracy);
|
500,
|
||||||
increment = increment <= 0 ? 1 : increment;
|
this._config.detail
|
||||||
let pos = history.length - 1;
|
);
|
||||||
for (let i = accuracy; i >= 1; i--) {
|
this._line = this._getPath(coords);
|
||||||
pos -= increment;
|
|
||||||
if (pos >= 0) valArray.unshift(history[pos]);
|
|
||||||
}
|
|
||||||
this._line = this._getGraph(valArray, 500, this._config.height);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async _fetchRecent(entityId, startTime, endTime) {
|
async _fetchRecent(entityId, startTime, endTime) {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user