Cleanup line chart

This commit is contained in:
Paulus Schoutsen 2015-10-16 00:12:46 -07:00
parent 21c67a38d6
commit 816b335cb0

View File

@ -1,7 +1,4 @@
import pluck from 'lodash/collection/pluck';
import flatten from 'lodash/array/flatten';
import uniq from 'lodash/array/uniq'; import uniq from 'lodash/array/uniq';
import sortBy from 'lodash/collection/sortBy';
import Polymer from '../polymer'; import Polymer from '../polymer';
@ -28,6 +25,10 @@ export default new Polymer({
value: false, value: false,
observer: 'dataChanged', observer: 'dataChanged',
}, },
chartEngine: {
type: Object,
},
}, },
created() { created() {
@ -42,44 +43,23 @@ export default new Polymer({
this.drawChart(); this.drawChart();
}, },
/* *************************************************
The following code gererates line graphs for devices with continuous
values(which are devices that have a unit_of_measurement values defined).
On each graph the devices are grouped by their unit of measurement, eg. all
sensors measuring MB will be a separate line on single graph. The google
chart API takes data as a 2 dimensional array in the format:
DateTime, device1, device2, device3
2015-04-01, 1, 2, 0
2015-04-01, 0, 1, 0
2015-04-01, 2, 1, 1
NOTE: the first column is a javascript date objects.
The first thing we do is build up the data with rows for each time of a state
change and initialise the values to 0. THen we loop through each device and
fill in its data.
**************************************************/
drawChart() { drawChart() {
if (!this.isAttached) { if (!this.isAttached) {
return; return;
} }
const root = Polymer.dom(this); if (!this.chartEngine) {
this.chartEngine = new window.google.visualization.LineChart(this);
}
const unit = this.unit; const unit = this.unit;
const deviceStates = this.data; const deviceStates = this.data;
while (root.lastChild) {
root.removeChild(root.lastChild);
}
if (deviceStates.length === 0) { if (deviceStates.length === 0) {
return; return;
} }
const chart = new google.visualization.LineChart(this); const dataTable = new window.google.visualization.DataTable();
const dataTable = new google.visualization.DataTable();
dataTable.addColumn({ type: 'datetime', id: 'Time' }); dataTable.addColumn({ type: 'datetime', id: 'Time' });
@ -112,75 +92,79 @@ export default new Polymer({
options.enableInteractivity = false; options.enableInteractivity = false;
} }
// Get a unique list of times of state changes for all the device // Get a unique list of times of state changes for all the devices
// for a particular unit of measureent. let times = uniq(deviceStates.map(
let times = pluck(flatten(deviceStates), 'lastChangedAsDate'); states => states.map(
times = sortBy(uniq(times, (e) => e.getTime())); state => state.lastChangedAsDate)).reduce(
(tot, cur) => tot.concat(cur), [])).sort();
// end time is Math.min(curTime, start time + 1 day)
let endTime = new Date(times[0]);
endTime.setDate(endTime.getDate() + 1);
if (endTime > new Date()) {
endTime = new Date();
}
times = times.concat(endTime);
// This is going to be an array of arrays. Each array contains:
// [Time, valueSensor1, valueSensor2, etc]
// Google Graph requires each data series to have an entry for each point.
// Since not all sensors have a value for each time, we'll put in the last
// known value at that point in time.
// Because we put in last known value, the 'average' line shown between
// times is incorrect. To fix this, we add each time twice and have
// transitions shown as a vertical line :-(
const data = []; const data = [];
const empty = new Array(deviceStates.length); times.forEach(time => {
for (let i = 0; i < empty.length; i++) { data.push([time]);
empty[i] = 0; data.push([time]);
} });
let timeIndex = 1; deviceStates.forEach(states => {
const endDate = new Date(); let startIndex = 0;
let curTime;
let curValue;
for (let i = 0; i < times.length; i++) { const nextState = function nextState() {
// because we only have state changes we add an extra point at the same time if (startIndex === null) {
// that holds the previous state which makes the line display correctly return;
const beforePoint = new Date(times[i]);
data.push([beforePoint].concat(empty));
data.push([times[i]].concat(empty));
timeIndex++;
}
data.push([endDate].concat(empty));
let deviceCount = 0;
deviceStates.forEach((device) => {
const attributes = device[device.length - 1].attributes;
dataTable.addColumn('number', attributes.friendly_name);
let currentState = 0;
let previousState = 0;
let lastIndex = 0;
let count = 0;
let prevTime = data[0][0];
device.forEach((state) => {
currentState = state.state;
const start = state.lastChangedAsDate;
if (state.state === 'None') {
currentState = previousState;
} }
for (let i = lastIndex; i < data.length; i++) { let value;
data[i][1 + deviceCount] = parseFloat(previousState); for (let ind = startIndex; ind < states.length; ind++) {
// this is where data gets filled in for each time for the particular device value = parseFloat(states[ind].state);
// because for each time two entries were create we fill the first one with the if (!isNaN(value) && isFinite(value)) {
// previous value and the second one with the new value startIndex = ind + 1;
if (prevTime.getTime() === data[i][0].getTime() && data[i][0].getTime() === start.getTime()) { curValue = value;
data[i][1 + deviceCount] = parseFloat(currentState); curTime = states[ind].lastChangedAsDate;
lastIndex = i; return;
prevTime = data[i][0];
break;
} }
prevTime = data[i][0];
} }
startIndex = null;
};
previousState = currentState; nextState();
count++; // no usable states found.
}); if (startIndex === null) {
return;
// fill in the rest of the Array
for (let i = lastIndex; i < data.length; i++) {
data[i][1 + deviceCount] = parseFloat(previousState);
} }
deviceCount++; dataTable.addColumn('number', states[states.length - 1].entityDisplay);
times.forEach((time, index) => {
data[index * 2].push(curValue);
if (curTime === time) {
nextState();
}
data[index * 2 + 1].push(curValue);
});
}); });
dataTable.addRows(data); dataTable.addRows(data);
chart.draw(dataTable, options); this.chartEngine.draw(dataTable, options);
}, },
}); });