Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improve sensor graph algorithm #2069

Merged
merged 1 commit into from
Nov 20, 2018
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
123 changes: 74 additions & 49 deletions src/panels/lovelace/cards/hui-sensor-card.js
100644 → 100755
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@ class HuiSensorCard extends EventsMixin(LitElement) {
_config: {},
_entity: {},
_line: String,
_min: Number,
_max: Number,
};
}

Expand All @@ -38,18 +40,21 @@ class HuiSensorCard extends EventsMixin(LitElement) {
}

const cardConfig = {
detail: 1,
icon: false,
hours_to_show: 24,
accuracy: 10,
height: 100,
line_width: 5,
hours_to_show: 24,
line_color: "var(--accent-color)",
line_width: 5,
...config,
};
cardConfig.hours_to_show = Number(cardConfig.hours_to_show);
cardConfig.accuracy = Number(cardConfig.accuracy);
cardConfig.height = Number(cardConfig.height);
cardConfig.line_width = Number(cardConfig.line_width);
cardConfig.detail =
cardConfig.detail === 1 || cardConfig.detail === 2
? cardConfig.detail
: 1;

this._config = cardConfig;
}
Expand Down Expand Up @@ -109,53 +114,79 @@ class HuiSensorCard extends EventsMixin(LitElement) {
return this._config.unit || item.attributes.unit_of_measurement;
}

_getGraph(items, width, height) {
const values = this._getValueArr(items);
const coords = this._calcCoordinates(values, width, height);
return this._getPath(coords);
}

_getValueArr(items) {
return items
.map((item) => Number(item.state))
.filter((val) => !Number.isNaN(val));
_coordinates(history, hours, width, detail = 1) {
history = history.filter((item) => !Number.isNaN(Number(item.state)));
this._min = Math.min.apply(Math, history.map((item) => Number(item.state)));
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);
}

_calcCoordinates(values, width, height) {
_calcPoints(history, hours, width, detail = 1) {
const coords = [];
const margin = this._config.line_width;
const height = this._config.height - margin * 4;
width -= margin * 2;
height -= margin * 2;
const min = Math.floor(Math.min.apply(null, values) * 0.95);
const max = Math.ceil(Math.max.apply(null, values) * 1.05);

if (values.length === 1) values.push(values[0]);

const yRatio = (max - min) / height;
const xRatio = width / (values.length - 1);
let yRatio = (this._max - this._min) / height;
yRatio = yRatio !== 0 ? yRatio : height;
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;

const x = xRatio * (i + offset / 6) + margin;
const y = height - (average - this._min) / yRatio + margin * 2;
return coords.push([x, y]);
};

return values.map((value, i) => {
const y = height - (value - min) / yRatio || 0;
const x = xRatio * i + margin;
return [x, y];
});
history.forEach((item, i) => getCoords(item, i, 0, detail));
if (coords.length === 1) coords[1] = [width + margin, coords[0][1]];
coords.push([width + margin, coords[coords.length - 1][1]]);
return coords;
}

_getPath(points) {
_getPath(coords) {
let next;
let Z;
const X = 0;
const Y = 1;
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++) {
next = points[i];
Z = this._midPoint(point[X], point[Y], next[X], next[Y]);
for (let i = 0; i < coords.length; i++) {
next = coords[i];
Z = this._midPoint(last[X], last[Y], next[X], next[Y]);
path += ` ${Z[X]},${Z[Y]}`;
path += ` Q${next[X]},${next[Y]}`;
point = next;
last = next;
}

path += ` ${next[X]},${next[Y]}`;
Expand All @@ -177,21 +208,15 @@ class HuiSensorCard extends EventsMixin(LitElement) {
startTime,
endTime
);
const history = stateHistory[0];
const valArray = [history[history.length - 1]];

const accuracy =
this._config.accuracy <= history.length
? this._config.accuracy
: history.length;
let increment = Math.ceil(history.length / accuracy);
increment = increment <= 0 ? 1 : increment;
let pos = history.length - 1;
for (let i = accuracy; i >= 1; i--) {
pos -= increment;
if (pos >= 0) valArray.unshift(history[pos]);
}
this._line = this._getGraph(valArray, 500, this._config.height);
if (stateHistory[0].length < 1) return;
const coords = this._coordinates(
stateHistory[0],
this._config.hours_to_show,
500,
this._config.detail
);
this._line = this._getPath(coords);
}

async _fetchRecent(entityId, startTime, endTime) {
Expand Down