Improve sensor graph algorithm (#2069)

This commit is contained in:
Karl Kihlström 2018-11-20 12:41:47 +01:00 committed by Paulus Schoutsen
parent ef2aa2ea6f
commit 07cf1141c5

121
src/panels/lovelace/cards/hui-sensor-card.js Normal file → Executable file
View 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) {