mirror of
https://github.com/home-assistant/core.git
synced 2025-07-24 13:47:35 +00:00
Polymer .9: Convert History
This commit is contained in:
parent
f517445d36
commit
33122701b9
@ -11,26 +11,27 @@
|
|||||||
"bower_components"
|
"bower_components"
|
||||||
],
|
],
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"webcomponentsjs": "Polymer/webcomponentsjs#~0.7",
|
"webcomponentsjs": "Polymer/webcomponentsjs#^0.7",
|
||||||
"font-roboto": "Polymer/font-roboto#~0.5.6",
|
"font-roboto": "Polymer/font-roboto#^0.5.6",
|
||||||
"paper-header-panel": "PolymerElements/paper-header-panel#~0.9",
|
"paper-header-panel": "PolymerElements/paper-header-panel#^0.9",
|
||||||
"paper-toolbar": "PolymerElements/paper-toolbar#~0.9",
|
"paper-toolbar": "PolymerElements/paper-toolbar#^0.9",
|
||||||
"paper-menu": "PolymerElements/paper-menu#~0.9",
|
"paper-menu": "PolymerElements/paper-menu#^0.9",
|
||||||
"iron-input": "PolymerElements/iron-input#~0.9",
|
"iron-input": "PolymerElements/iron-input#^0.9",
|
||||||
"iron-icons": "PolymerElements/iron-icons#~0.9",
|
"iron-icons": "PolymerElements/iron-icons#^0.9",
|
||||||
"iron-image": "PolymerElements/iron-image#~0.9",
|
"iron-image": "PolymerElements/iron-image#^0.9",
|
||||||
"paper-toast": "PolymerElements/paper-toast#~0.9",
|
"paper-toast": "PolymerElements/paper-toast#^0.9",
|
||||||
"paper-dialog": "PolymerElements/paper-dialog#~0.9",
|
"paper-dialog": "PolymerElements/paper-dialog#^0.9",
|
||||||
"paper-spinner": "PolymerElements/paper-spinner#~0.9",
|
"paper-spinner": "PolymerElements/paper-spinner#^0.9",
|
||||||
"paper-button": "PolymerElements/paper-button#~0.9",
|
"paper-button": "PolymerElements/paper-button#^0.9",
|
||||||
"paper-input": "PolymerElements/paper-input#~0.9",
|
"paper-input": "PolymerElements/paper-input#^0.9",
|
||||||
"paper-toggle-button": "PolymerElements/paper-toggle-button#~0.9",
|
"paper-toggle-button": "PolymerElements/paper-toggle-button#^0.9",
|
||||||
"paper-icon-button": "PolymerElements/paper-icon-button#~0.9",
|
"paper-icon-button": "PolymerElements/paper-icon-button#^0.9",
|
||||||
"paper-item": "PolymerElements/paper-item#~0.9",
|
"paper-item": "PolymerElements/paper-item#^0.9",
|
||||||
"paper-slider": "PolymerElements/paper-slider#~0.9",
|
"paper-slider": "PolymerElements/paper-slider#^0.9",
|
||||||
"paper-checkbox": "PolymerElements/paper-checkbox#~0.9",
|
"paper-checkbox": "PolymerElements/paper-checkbox#^0.9",
|
||||||
"paper-drawer-panel": "PolymerElements/paper-drawer-panel#~0.9",
|
"paper-drawer-panel": "PolymerElements/paper-drawer-panel#^0.9",
|
||||||
"moment": "~2.10.3",
|
"google-apis": "GoogleWebComponents/google-apis#0.8-preview",
|
||||||
|
"moment": "^2.10.3",
|
||||||
"layout": "Polymer/layout"
|
"layout": "Polymer/layout"
|
||||||
},
|
},
|
||||||
"stillUpgrading": {
|
"stillUpgrading": {
|
||||||
@ -44,6 +45,7 @@
|
|||||||
"paper-menu-button": "PolymerElements/paper-menu-button#~0.9"
|
"paper-menu-button": "PolymerElements/paper-menu-button#~0.9"
|
||||||
},
|
},
|
||||||
"resolutions": {
|
"resolutions": {
|
||||||
"polymer": "^0.9.0"
|
"polymer": "^0.9.0",
|
||||||
|
"webcomponentsjs": "^0.7.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,25 +1,27 @@
|
|||||||
<link rel="import" href="../bower_components/polymer/polymer.html">
|
<link rel="import" href="../bower_components/polymer/polymer.html">
|
||||||
<link rel="import" href="../bower_components/paper-spinner/paper-spinner.html">
|
<link rel="import" href="../bower_components/paper-spinner/paper-spinner.html">
|
||||||
|
|
||||||
<polymer-element name="loading-box" attributes="text">
|
<dom-module id="loading-box">
|
||||||
|
<style>
|
||||||
|
.text {
|
||||||
|
display: inline-block;
|
||||||
|
line-height: 28px;
|
||||||
|
vertical-align: top;
|
||||||
|
margin-left: 8px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
<template>
|
<template>
|
||||||
<style>
|
|
||||||
.text {
|
|
||||||
display: inline-block;
|
|
||||||
line-height: 28px;
|
|
||||||
vertical-align: top;
|
|
||||||
margin-left: 8px;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
<div layout='horizontal'>
|
<div layout='horizontal'>
|
||||||
<paper-spinner active="true"></paper-spinner>
|
<paper-spinner active="true"></paper-spinner>
|
||||||
<div class='text'>{{text}}…</div>
|
<div class='text'><content></content>…</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
</dom-module>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
(function() {
|
||||||
Polymer({
|
Polymer({
|
||||||
text: "Loading"
|
is: 'loading-box',
|
||||||
});
|
});
|
||||||
</script>
|
})();
|
||||||
</polymer-element>
|
</script>
|
||||||
|
@ -0,0 +1,196 @@
|
|||||||
|
<link rel="import" href="../bower_components/polymer/polymer.html">
|
||||||
|
|
||||||
|
<script>
|
||||||
|
(function() {
|
||||||
|
Polymer({
|
||||||
|
is: 'state-history-chart-line',
|
||||||
|
|
||||||
|
properties: {
|
||||||
|
data: {
|
||||||
|
type: Object,
|
||||||
|
observer: 'dataChanged',
|
||||||
|
},
|
||||||
|
|
||||||
|
unit: {
|
||||||
|
type: String,
|
||||||
|
},
|
||||||
|
|
||||||
|
isSingleDevice: {
|
||||||
|
type: Boolean,
|
||||||
|
value: false,
|
||||||
|
},
|
||||||
|
|
||||||
|
isAttached: {
|
||||||
|
type: Boolean,
|
||||||
|
value: false,
|
||||||
|
observer: 'dataChanged',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
created: function() {
|
||||||
|
this.style.display = 'block';
|
||||||
|
},
|
||||||
|
|
||||||
|
attached: function() {
|
||||||
|
this.isAttached = true;
|
||||||
|
},
|
||||||
|
|
||||||
|
dataChanged: function() {
|
||||||
|
this.drawChart();
|
||||||
|
},
|
||||||
|
|
||||||
|
/**************************************************
|
||||||
|
The following code gererates line 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: function() {
|
||||||
|
if (!this.isAttached) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var root = Polymer.dom(this);
|
||||||
|
var unit = this.unit;
|
||||||
|
var deviceStates = this.data;
|
||||||
|
|
||||||
|
while (root.lastChild) {
|
||||||
|
root.removeChild(root.lastChild);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (deviceStates.length === 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var chart = new google.visualization.LineChart(this);
|
||||||
|
var dataTable = new google.visualization.DataTable();
|
||||||
|
|
||||||
|
dataTable.addColumn({ type: 'datetime', id: 'Time' });
|
||||||
|
|
||||||
|
var options = {
|
||||||
|
legend: { position: 'top' },
|
||||||
|
titlePosition: 'none',
|
||||||
|
vAxes: {
|
||||||
|
// Adds units to the left hand side of the graph
|
||||||
|
0: {title: unit}
|
||||||
|
},
|
||||||
|
hAxis: {
|
||||||
|
format: 'H:mm'
|
||||||
|
},
|
||||||
|
lineWidth: 1,
|
||||||
|
chartArea:{left:'60',width:"95%"},
|
||||||
|
explorer: {
|
||||||
|
actions: ['dragToZoom', 'rightClickToReset', 'dragToPan'],
|
||||||
|
keepInBounds: true,
|
||||||
|
axis: 'horizontal',
|
||||||
|
maxZoomIn: 0.1
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if(this.isSingleDevice) {
|
||||||
|
options.legend.position = 'none';
|
||||||
|
options.vAxes[0].title = null;
|
||||||
|
options.chartArea.left = 40;
|
||||||
|
options.chartArea.height = '80%';
|
||||||
|
options.chartArea.top = 5;
|
||||||
|
options.enableInteractivity = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get a unique list of times of state changes for all the device
|
||||||
|
// for a particular unit of measureent.
|
||||||
|
var times = _.pluck(_.flatten(deviceStates), "lastChangedAsDate");
|
||||||
|
times = _.uniq(times, function(e) {
|
||||||
|
return e.getTime();
|
||||||
|
});
|
||||||
|
|
||||||
|
times = _.sortBy(times, function(o) { return o; });
|
||||||
|
|
||||||
|
var data = [];
|
||||||
|
var empty = new Array(deviceStates.length);
|
||||||
|
for(var i = 0; i < empty.length; i++) {
|
||||||
|
empty[i] = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
var timeIndex = 1;
|
||||||
|
var endDate = new Date();
|
||||||
|
var prevDate = times[0];
|
||||||
|
|
||||||
|
for(i = 0; i < times.length; i++) {
|
||||||
|
var currentDate = new Date(prevDate);
|
||||||
|
|
||||||
|
// because we only have state changes we add an extra point at the same time
|
||||||
|
// that holds the previous state which makes the line display correctly
|
||||||
|
var beforePoint = new Date(times[i]);
|
||||||
|
data.push([beforePoint].concat(empty));
|
||||||
|
|
||||||
|
data.push([times[i]].concat(empty));
|
||||||
|
prevDate = times[i];
|
||||||
|
timeIndex++;
|
||||||
|
}
|
||||||
|
data.push([endDate].concat(empty));
|
||||||
|
|
||||||
|
|
||||||
|
var deviceCount = 0;
|
||||||
|
deviceStates.forEach(function(device) {
|
||||||
|
var attributes = device[device.length - 1].attributes;
|
||||||
|
dataTable.addColumn('number', attributes.friendly_name);
|
||||||
|
|
||||||
|
var currentState = 0;
|
||||||
|
var previousState = 0;
|
||||||
|
var lastIndex = 0;
|
||||||
|
var count = 0;
|
||||||
|
var prevTime = data[0][0];
|
||||||
|
device.forEach(function(state) {
|
||||||
|
|
||||||
|
currentState = state.state;
|
||||||
|
var start = state.lastChangedAsDate;
|
||||||
|
if(state.state == 'None') {
|
||||||
|
currentState = previousState;
|
||||||
|
}
|
||||||
|
for(var i = lastIndex; i < data.length; i++) {
|
||||||
|
data[i][1 + deviceCount] = parseFloat(previousState);
|
||||||
|
// this is where data gets filled in for each time for the particular device
|
||||||
|
// because for each time two entries were create we fill the first one with the
|
||||||
|
// previous value and the second one with the new value
|
||||||
|
if(prevTime.getTime() == data[i][0].getTime() && data[i][0].getTime() == start.getTime()) {
|
||||||
|
data[i][1 + deviceCount] = parseFloat(currentState);
|
||||||
|
lastIndex = i;
|
||||||
|
prevTime = data[i][0];
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
prevTime = data[i][0];
|
||||||
|
}
|
||||||
|
|
||||||
|
previousState = currentState;
|
||||||
|
|
||||||
|
count++;
|
||||||
|
}.bind(this));
|
||||||
|
|
||||||
|
//fill in the rest of the Array
|
||||||
|
for(var i = lastIndex; i < data.length; i++) {
|
||||||
|
data[i][1 + deviceCount] = parseFloat(previousState);
|
||||||
|
}
|
||||||
|
|
||||||
|
deviceCount++;
|
||||||
|
}.bind(this));
|
||||||
|
|
||||||
|
dataTable.addRows(data);
|
||||||
|
chart.draw(dataTable, options);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
})();
|
||||||
|
|
||||||
|
</script>
|
@ -0,0 +1,115 @@
|
|||||||
|
<link rel="import" href="../bower_components/polymer/polymer.html">
|
||||||
|
|
||||||
|
<script>
|
||||||
|
(function() {
|
||||||
|
Polymer({
|
||||||
|
is: 'state-history-chart-timeline',
|
||||||
|
|
||||||
|
properties: {
|
||||||
|
data: {
|
||||||
|
type: Object,
|
||||||
|
observer: 'dataChanged',
|
||||||
|
},
|
||||||
|
|
||||||
|
isAttached: {
|
||||||
|
type: Boolean,
|
||||||
|
value: false,
|
||||||
|
observer: 'dataChanged',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
created: function() {
|
||||||
|
this.style.display = 'block';
|
||||||
|
},
|
||||||
|
|
||||||
|
attached: function() {
|
||||||
|
this.isAttached = true;
|
||||||
|
},
|
||||||
|
|
||||||
|
dataChanged: function() {
|
||||||
|
this.drawChart();
|
||||||
|
},
|
||||||
|
|
||||||
|
drawChart: function() {
|
||||||
|
if (!this.isAttached) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var root = Polymer.dom(this);
|
||||||
|
var data = this.data;
|
||||||
|
|
||||||
|
while (root.lastChild) {
|
||||||
|
root.removeChild(root.lastChild);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (data.length === 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var chart = new google.visualization.Timeline(this);
|
||||||
|
var dataTable = new google.visualization.DataTable();
|
||||||
|
|
||||||
|
dataTable.addColumn({ type: 'string', id: 'Entity' });
|
||||||
|
dataTable.addColumn({ type: 'string', id: 'State' });
|
||||||
|
dataTable.addColumn({ type: 'date', id: 'Start' });
|
||||||
|
dataTable.addColumn({ type: 'date', id: 'End' });
|
||||||
|
|
||||||
|
var addRow = function(entityDisplay, stateStr, start, end) {
|
||||||
|
stateStr = stateStr.replace(/_/g, ' ');
|
||||||
|
dataTable.addRow([entityDisplay, stateStr, start, end]);
|
||||||
|
};
|
||||||
|
|
||||||
|
// people can pass in history of 1 entityId or a collection.
|
||||||
|
var stateHistory;
|
||||||
|
if (_.isArray(data[0])) {
|
||||||
|
stateHistory = data;
|
||||||
|
} else {
|
||||||
|
stateHistory = [data];
|
||||||
|
isSingleDevice = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
var numTimelines = 0;
|
||||||
|
// stateHistory is a list of lists of sorted state objects
|
||||||
|
stateHistory.forEach(function(stateInfo) {
|
||||||
|
if(stateInfo.length === 0) return;
|
||||||
|
|
||||||
|
var entityDisplay = stateInfo[0].entityDisplay;
|
||||||
|
var newLastChanged, prevState = null, prevLastChanged = null;
|
||||||
|
|
||||||
|
stateInfo.forEach(function(state) {
|
||||||
|
if (prevState !== null && state.state !== prevState) {
|
||||||
|
newLastChanged = state.lastChangedAsDate;
|
||||||
|
|
||||||
|
addRow(entityDisplay, prevState, prevLastChanged, newLastChanged);
|
||||||
|
|
||||||
|
prevState = state.state;
|
||||||
|
prevLastChanged = newLastChanged;
|
||||||
|
} else if (prevState === null) {
|
||||||
|
prevState = state.state;
|
||||||
|
prevLastChanged = state.lastChangedAsDate;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
addRow(entityDisplay, prevState, prevLastChanged, new Date());
|
||||||
|
numTimelines++;
|
||||||
|
}.bind(this));
|
||||||
|
|
||||||
|
chart.draw(dataTable, {
|
||||||
|
height: 55 + numTimelines * 42,
|
||||||
|
|
||||||
|
// interactive properties require CSS, the JS api puts it on the document
|
||||||
|
// instead of inside our Shadow DOM.
|
||||||
|
enableInteractivity: false,
|
||||||
|
|
||||||
|
timeline: {
|
||||||
|
showRowLabels: stateHistory.length > 1
|
||||||
|
},
|
||||||
|
|
||||||
|
hAxis: {
|
||||||
|
format: 'H:mm'
|
||||||
|
},
|
||||||
|
});
|
||||||
|
},
|
||||||
|
});
|
||||||
|
})();
|
||||||
|
</script>
|
@ -0,0 +1,140 @@
|
|||||||
|
<link rel="import" href="../bower_components/polymer/polymer.html">
|
||||||
|
|
||||||
|
<link rel="import" href="../bower_components/google-apis/google-legacy-loader.html">
|
||||||
|
|
||||||
|
<link rel="import" href="./loading-box.html">
|
||||||
|
<link rel="import" href="./state-history-chart-timeline.html">
|
||||||
|
<link rel="import" href="./state-history-chart-line.html">
|
||||||
|
|
||||||
|
<dom-module id="state-history-charts">
|
||||||
|
<style>
|
||||||
|
:host {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
<template>
|
||||||
|
<google-legacy-loader on-api-load="googleApiLoaded"></google-legacy-loader>
|
||||||
|
|
||||||
|
<div hidden$="{{!isLoading}}" >
|
||||||
|
<div class='layout horizontal center'>
|
||||||
|
<div class='layout vertical center'>
|
||||||
|
<loading-box>Loading history data</loading-box>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<template is='dom-if' if='[[!isLoading]]'>
|
||||||
|
<template is='dom-if' if='[[groupedStateHistory.timeline]]'>
|
||||||
|
<state-history-chart-timeline data='[[groupedStateHistory.timeline]]'
|
||||||
|
is-single-device='[[isSingleDevice]]'>
|
||||||
|
</state-history-chart-timeline>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<template is='dom-if' if='[[groupedStateHistory.line]]'>
|
||||||
|
<template is='dom-repeat' items='[[groupedStateHistory.line]]'>
|
||||||
|
<state-history-chart-line unit='[[extractUnit(item)]]'
|
||||||
|
data='[[extractData(item)]]' is-single-device='[[isSingleDevice]]'>
|
||||||
|
</state-history-chart-line>
|
||||||
|
</template>
|
||||||
|
</template>
|
||||||
|
</template>
|
||||||
|
</template>
|
||||||
|
</dom-module>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
(function() {
|
||||||
|
Polymer({
|
||||||
|
is: 'state-history-charts',
|
||||||
|
|
||||||
|
properties: {
|
||||||
|
stateHistory: {
|
||||||
|
type: Object,
|
||||||
|
},
|
||||||
|
|
||||||
|
isLoadingData: {
|
||||||
|
type: Boolean,
|
||||||
|
value: false,
|
||||||
|
},
|
||||||
|
|
||||||
|
apiLoaded: {
|
||||||
|
type: Boolean,
|
||||||
|
value: false,
|
||||||
|
},
|
||||||
|
|
||||||
|
isLoading: {
|
||||||
|
type: Boolean,
|
||||||
|
computed: 'computeIsLoading(isLoadingData, apiLoaded)',
|
||||||
|
},
|
||||||
|
|
||||||
|
groupedStateHistory: {
|
||||||
|
type: Object,
|
||||||
|
computed: 'computeGroupedStateHistory(stateHistory)',
|
||||||
|
},
|
||||||
|
|
||||||
|
isSingleDevice: {
|
||||||
|
type: Boolean,
|
||||||
|
computed: 'computeIsSingleDevice(stateHistory)',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
computeIsSingleDevice: function(stateHistory) {
|
||||||
|
return stateHistory.length == 1;
|
||||||
|
},
|
||||||
|
|
||||||
|
computeGroupedStateHistory: function(stateHistory) {
|
||||||
|
var lineChartDevices = {};
|
||||||
|
var timelineDevices = [];
|
||||||
|
|
||||||
|
stateHistory.forEach(function(stateInfo) {
|
||||||
|
if (!stateInfo || stateInfo.length === 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var unit;
|
||||||
|
|
||||||
|
for (var i = 0; i < stateInfo.length && !unit; i++) {
|
||||||
|
unit = stateInfo[i].attributes.unit_of_measurement;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (unit) {
|
||||||
|
if (!(unit in lineChartDevices)) {
|
||||||
|
lineChartDevices[unit] = [stateInfo];
|
||||||
|
} else {
|
||||||
|
lineChartDevices[unit].push(stateInfo);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
timelineDevices.push(stateInfo);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
timelineDevices = timelineDevices.length > 0 && timelineDevices;
|
||||||
|
|
||||||
|
var unitStates = Object.keys(lineChartDevices).map(function(unit) {
|
||||||
|
return [unit, lineChartDevices[unit]]; });
|
||||||
|
|
||||||
|
return {line: unitStates, timeline: timelineDevices};
|
||||||
|
},
|
||||||
|
|
||||||
|
googleApiLoaded: function() {
|
||||||
|
google.load("visualization", "1", {
|
||||||
|
packages: ["timeline", "corechart"],
|
||||||
|
callback: function() {
|
||||||
|
this.apiLoaded = true;
|
||||||
|
}.bind(this)
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
computeIsLoading: function(isLoadingData, apiLoaded) {
|
||||||
|
return isLoadingData || !apiLoaded;
|
||||||
|
},
|
||||||
|
|
||||||
|
extractUnit: function(arr) {
|
||||||
|
return arr[0];
|
||||||
|
},
|
||||||
|
|
||||||
|
extractData: function(arr) {
|
||||||
|
return arr[1];
|
||||||
|
},
|
||||||
|
});
|
||||||
|
})();
|
||||||
|
</script>
|
@ -1,321 +0,0 @@
|
|||||||
<link rel="import" href="../bower_components/polymer/polymer.html">
|
|
||||||
|
|
||||||
<link rel="import" href="../bower_components/google-apis/google-jsapi.html">
|
|
||||||
|
|
||||||
<polymer-element name="state-timeline" attributes="stateHistory isLoadingData">
|
|
||||||
<template>
|
|
||||||
<style>
|
|
||||||
:host {
|
|
||||||
display: block;
|
|
||||||
}
|
|
||||||
|
|
||||||
#loadingbox {
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.loadingmessage {
|
|
||||||
margin-top: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.singlelinechart {
|
|
||||||
min-height:140px;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
|
||||||
<div style='width: 100%; height: auto;' hidden?="{{!isLoading}}" >
|
|
||||||
<div layout horizontal center id="splash">
|
|
||||||
<div layout vertical center flex>
|
|
||||||
<div id="loadingbox">
|
|
||||||
<paper-spinner active="true"></paper-spinner><br />
|
|
||||||
<div class="loadingmessage">{{spinnerMessage}}</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<google-jsapi on-api-load="{{googleApiLoaded}}"></google-jsapi>
|
|
||||||
<div id="timeline" style='width: 100%; height: auto;' class="{{ {singlelinechart: isSingleDevice && hasLineChart } | tokenList}}" hidden?="{{isLoadingData}}"></div>
|
|
||||||
<div id="line_graphs" style='width: 100%; height: auto;' hidden?="{{isLoadingData}}"></div>
|
|
||||||
|
|
||||||
</template>
|
|
||||||
<script>
|
|
||||||
Polymer({
|
|
||||||
apiLoaded: false,
|
|
||||||
stateHistory: null,
|
|
||||||
isLoading: true,
|
|
||||||
isLoadingData: false,
|
|
||||||
spinnerMessage: "Loading history data...",
|
|
||||||
isSingleDevice: false,
|
|
||||||
hasLineChart: false,
|
|
||||||
|
|
||||||
googleApiLoaded: function() {
|
|
||||||
google.load("visualization", "1", {
|
|
||||||
packages: ["timeline", "corechart"],
|
|
||||||
callback: function() {
|
|
||||||
this.apiLoaded = true;
|
|
||||||
this.drawChart();
|
|
||||||
}.bind(this)
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
stateHistoryChanged: function() {
|
|
||||||
this.drawChart();
|
|
||||||
},
|
|
||||||
|
|
||||||
isLoadingDataChanged: function() {
|
|
||||||
if(this.isLoadingData) {
|
|
||||||
isLoading = true;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
drawChart: function() {
|
|
||||||
if (!this.apiLoaded || !this.stateHistory) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
this.isLoading = true;
|
|
||||||
|
|
||||||
var container = this.$.timeline;
|
|
||||||
var chart = new google.visualization.Timeline(container);
|
|
||||||
var dataTable = new google.visualization.DataTable();
|
|
||||||
|
|
||||||
dataTable.addColumn({ type: 'string', id: 'Entity' });
|
|
||||||
dataTable.addColumn({ type: 'string', id: 'State' });
|
|
||||||
dataTable.addColumn({ type: 'date', id: 'Start' });
|
|
||||||
dataTable.addColumn({ type: 'date', id: 'End' });
|
|
||||||
|
|
||||||
var addRow = function(entityDisplay, stateStr, start, end) {
|
|
||||||
stateStr = stateStr.replace(/_/g, ' ');
|
|
||||||
dataTable.addRow([entityDisplay, stateStr, start, end]);
|
|
||||||
};
|
|
||||||
|
|
||||||
if (this.stateHistory.length === 0) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
this.hasLineChart = false;
|
|
||||||
this.isSingleDevice = false;
|
|
||||||
|
|
||||||
// people can pass in history of 1 entityId or a collection.
|
|
||||||
var stateHistory;
|
|
||||||
if (_.isArray(this.stateHistory[0])) {
|
|
||||||
stateHistory = this.stateHistory;
|
|
||||||
} else {
|
|
||||||
stateHistory = [this.stateHistory];
|
|
||||||
this.isSingleDevice = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
var lineChartDevices = {};
|
|
||||||
var numTimelines = 0;
|
|
||||||
// stateHistory is a list of lists of sorted state objects
|
|
||||||
stateHistory.forEach(function(stateInfo) {
|
|
||||||
if(stateInfo.length === 0) return;
|
|
||||||
|
|
||||||
var entityDisplay = stateInfo[0].entityDisplay;
|
|
||||||
var newLastChanged, prevState = null, prevLastChanged = null;
|
|
||||||
//get the latest update to get the graph type from the component attributes
|
|
||||||
var attributes = stateInfo[stateInfo.length - 1].attributes;
|
|
||||||
|
|
||||||
//if the device has a unit of meaurment it will be added as a line graph further down
|
|
||||||
if(attributes.unit_of_measurement) {
|
|
||||||
if(!lineChartDevices[attributes.unit_of_measurement]){
|
|
||||||
lineChartDevices[attributes.unit_of_measurement] = [];
|
|
||||||
}
|
|
||||||
lineChartDevices[attributes.unit_of_measurement].push(stateInfo);
|
|
||||||
this.hasLineChart = true;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
stateInfo.forEach(function(state) {
|
|
||||||
if (prevState !== null && state.state !== prevState) {
|
|
||||||
newLastChanged = state.lastChangedAsDate;
|
|
||||||
|
|
||||||
addRow(entityDisplay, prevState, prevLastChanged, newLastChanged);
|
|
||||||
|
|
||||||
prevState = state.state;
|
|
||||||
prevLastChanged = newLastChanged;
|
|
||||||
} else if (prevState === null) {
|
|
||||||
prevState = state.state;
|
|
||||||
prevLastChanged = state.lastChangedAsDate;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
addRow(entityDisplay, prevState, prevLastChanged, new Date());
|
|
||||||
numTimelines++;
|
|
||||||
}.bind(this));
|
|
||||||
|
|
||||||
chart.draw(dataTable, {
|
|
||||||
height: 55 + numTimelines * 42,
|
|
||||||
|
|
||||||
// interactive properties require CSS, the JS api puts it on the document
|
|
||||||
// instead of inside our Shadow DOM.
|
|
||||||
enableInteractivity: false,
|
|
||||||
|
|
||||||
timeline: {
|
|
||||||
showRowLabels: stateHistory.length > 1
|
|
||||||
},
|
|
||||||
|
|
||||||
hAxis: {
|
|
||||||
format: 'H:mm'
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
/**************************************************
|
|
||||||
The following code gererates line line graphs for devices with continuous
|
|
||||||
values(which are devices that have a unit_of_measurment 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.
|
|
||||||
|
|
||||||
**************************************************/
|
|
||||||
|
|
||||||
|
|
||||||
while (this.$.line_graphs.firstChild) {
|
|
||||||
this.$.line_graphs.removeChild(this.$.line_graphs.firstChild);
|
|
||||||
}
|
|
||||||
|
|
||||||
for (var key in lineChartDevices) {
|
|
||||||
var deviceStates = lineChartDevices[key];
|
|
||||||
|
|
||||||
if(this.isSingleDevice) {
|
|
||||||
container = this.$.timeline;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
container = document.createElement("DIV");
|
|
||||||
this.$.line_graphs.appendChild(container);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
var chart = new google.visualization.LineChart(container);
|
|
||||||
|
|
||||||
|
|
||||||
var dataTable = new google.visualization.DataTable();
|
|
||||||
dataTable.addColumn({ type: 'datetime', id: 'Time' });
|
|
||||||
|
|
||||||
var options = {
|
|
||||||
legend: { position: 'top' },
|
|
||||||
titlePosition: 'none',
|
|
||||||
vAxes: {
|
|
||||||
// Adds units to the left hand side of the graph
|
|
||||||
0: {title: key}
|
|
||||||
},
|
|
||||||
hAxis: {
|
|
||||||
format: 'H:mm'
|
|
||||||
},
|
|
||||||
lineWidth: 1,
|
|
||||||
chartArea:{left:'60',width:"95%"},
|
|
||||||
explorer: {
|
|
||||||
actions: ['dragToZoom', 'rightClickToReset', 'dragToPan'],
|
|
||||||
keepInBounds: true,
|
|
||||||
axis: 'horizontal',
|
|
||||||
maxZoomIn: 0.1
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
if(this.isSingleDevice) {
|
|
||||||
options.legend.position = 'none';
|
|
||||||
options.vAxes[0].title = null;
|
|
||||||
options.chartArea.left = 40;
|
|
||||||
options.chartArea.height = '80%';
|
|
||||||
options.chartArea.top = 5;
|
|
||||||
options.enableInteractivity = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get a unique list of times of state changes for all the device
|
|
||||||
// for a particular unit of measureent.
|
|
||||||
var times = _.pluck(_.flatten(deviceStates), "lastChangedAsDate");
|
|
||||||
times = _.uniq(times, function(e) {
|
|
||||||
return e.getTime();
|
|
||||||
});
|
|
||||||
|
|
||||||
times = _.sortBy(times, function(o) { return o; });
|
|
||||||
|
|
||||||
var data = [];
|
|
||||||
var empty = new Array(deviceStates.length);
|
|
||||||
for(var i = 0; i < empty.length; i++) {
|
|
||||||
empty[i] = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
var timeIndex = 1;
|
|
||||||
var endDate = new Date();
|
|
||||||
var prevDate = times[0];
|
|
||||||
|
|
||||||
for(var i = 0; i < times.length; i++) {
|
|
||||||
var currentDate = new Date(prevDate);
|
|
||||||
|
|
||||||
// because we only have state changes we add an extra point at the same time
|
|
||||||
// that holds the previous state which makes the line display correctly
|
|
||||||
var beforePoint = new Date(times[i]);
|
|
||||||
data.push([beforePoint].concat(empty));
|
|
||||||
|
|
||||||
data.push([times[i]].concat(empty));
|
|
||||||
prevDate = times[i];
|
|
||||||
timeIndex++;
|
|
||||||
}
|
|
||||||
data.push([endDate].concat(empty));
|
|
||||||
|
|
||||||
|
|
||||||
var deviceCount = 0;
|
|
||||||
deviceStates.forEach(function(device) {
|
|
||||||
var attributes = device[device.length - 1].attributes;
|
|
||||||
dataTable.addColumn('number', attributes.friendly_name);
|
|
||||||
|
|
||||||
var currentState = 0;
|
|
||||||
var previousState = 0;
|
|
||||||
var lastIndex = 0;
|
|
||||||
var count = 0;
|
|
||||||
var prevTime = data[0][0];
|
|
||||||
device.forEach(function(state) {
|
|
||||||
|
|
||||||
currentState = state.state;
|
|
||||||
var start = state.lastChangedAsDate;
|
|
||||||
if(state.state == 'None') {
|
|
||||||
currentState = previousState;
|
|
||||||
}
|
|
||||||
for(var i = lastIndex; i < data.length; i++) {
|
|
||||||
data[i][1 + deviceCount] = parseFloat(previousState);
|
|
||||||
// this is where data gets filled in for each time for the particular device
|
|
||||||
// because for each time two entires were create we fill the first one with the
|
|
||||||
// previous value and the second one with the new value
|
|
||||||
if(prevTime.getTime() == data[i][0].getTime() && data[i][0].getTime() == start.getTime()) {
|
|
||||||
data[i][1 + deviceCount] = parseFloat(currentState);
|
|
||||||
lastIndex = i;
|
|
||||||
prevTime = data[i][0];
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
prevTime = data[i][0];
|
|
||||||
}
|
|
||||||
|
|
||||||
previousState = currentState;
|
|
||||||
|
|
||||||
count++;
|
|
||||||
}.bind(this));
|
|
||||||
|
|
||||||
//fill in the rest of the Array
|
|
||||||
for(var i = lastIndex; i < data.length; i++) {
|
|
||||||
data[i][1 + deviceCount] = parseFloat(previousState);
|
|
||||||
}
|
|
||||||
|
|
||||||
deviceCount++;
|
|
||||||
}.bind(this));
|
|
||||||
|
|
||||||
dataTable.addRows(data);
|
|
||||||
chart.draw(dataTable, options);
|
|
||||||
}
|
|
||||||
this.isLoading = (!this.isLoadingData) ? false : true;
|
|
||||||
|
|
||||||
},
|
|
||||||
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
</polymer-element>
|
|
@ -11,12 +11,12 @@
|
|||||||
|
|
||||||
<link rel="import" href="../layouts/partial-states.html">
|
<link rel="import" href="../layouts/partial-states.html">
|
||||||
<link rel="import" href="../layouts/partial-logbook.html">
|
<link rel="import" href="../layouts/partial-logbook.html">
|
||||||
<!-- <link rel="import" href="../layouts/partial-history.html">
|
<link rel="import" href="../layouts/partial-history.html">
|
||||||
<link rel="import" href="../layouts/partial-dev-fire-event.html">
|
<!-- <link rel="import" href="../layouts/partial-dev-fire-event.html">
|
||||||
<link rel="import" href="../layouts/partial-dev-call-service.html">
|
<link rel="import" href="../layouts/partial-dev-call-service.html">
|
||||||
<link rel="import" href="../layouts/partial-dev-set-state.html"> -->
|
<link rel="import" href="../layouts/partial-dev-set-state.html">
|
||||||
|
|
||||||
<!-- <link rel="import" href="../components/ha-notifications.html">
|
<link rel="import" href="../components/ha-notifications.html">
|
||||||
<link rel="import" href="../components/ha-modals.html"> -->
|
<link rel="import" href="../components/ha-modals.html"> -->
|
||||||
<link rel="import" href="../components/stream-status.html">
|
<link rel="import" href="../components/stream-status.html">
|
||||||
|
|
||||||
@ -132,10 +132,10 @@
|
|||||||
<template is='dom-if' if="{{isSelectedLogbook}}">
|
<template is='dom-if' if="{{isSelectedLogbook}}">
|
||||||
<partial-logbook main narrow="{{narrow}}"></partial-logbook>
|
<partial-logbook main narrow="{{narrow}}"></partial-logbook>
|
||||||
</template>
|
</template>
|
||||||
<!--
|
<template is='dom-if' if="{{isSelectedHistory}}">
|
||||||
<template is='dom-if' if="{{selected == 'history'}}">
|
|
||||||
<partial-history main narrow="{{narrow}}"></partial-history>
|
<partial-history main narrow="{{narrow}}"></partial-history>
|
||||||
</template>
|
</template>
|
||||||
|
<!--
|
||||||
<template is='dom-if' if="{{selected == 'fire-event'}}">
|
<template is='dom-if' if="{{selected == 'fire-event'}}">
|
||||||
<partial-dev-fire-event main narrow="{{narrow}}"></partial-dev-fire-event>
|
<partial-dev-fire-event main narrow="{{narrow}}"></partial-dev-fire-event>
|
||||||
</template>
|
</template>
|
||||||
|
@ -1,13 +1,11 @@
|
|||||||
<link rel="import" href="../bower_components/polymer/polymer.html">
|
<link rel="import" href="../bower_components/polymer/polymer.html">
|
||||||
<link rel="import" href="../bower_components/core-style/core-style.html">
|
|
||||||
<link rel="import" href="../bower_components/paper-icon-button/paper-icon-button.html">
|
<link rel="import" href="../bower_components/paper-icon-button/paper-icon-button.html">
|
||||||
|
|
||||||
<link rel="import" href="./partial-base.html">
|
<link rel="import" href="./partial-base.html">
|
||||||
|
|
||||||
<link rel="import" href="../components/state-timeline.html">
|
<link rel="import" href="../components/state-history-charts.html">
|
||||||
|
|
||||||
<polymer-element name="partial-history" attributes="narrow togglePanel">
|
<dom-module id="partial-history">
|
||||||
<template>
|
|
||||||
<style>
|
<style>
|
||||||
.content {
|
.content {
|
||||||
background-color: white;
|
background-color: white;
|
||||||
@ -17,33 +15,38 @@
|
|||||||
padding: 8px;
|
padding: 8px;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
<partial-base narrow="{{narrow}}" togglePanel="{{togglePanel}}">
|
<template>
|
||||||
<span header-title>History</span>
|
<partial-base narrow="[[narrow]]">
|
||||||
|
<span header-title>History</span>
|
||||||
|
|
||||||
<span header-buttons>
|
<paper-icon-button icon="refresh" header-buttons
|
||||||
<paper-icon-button icon="refresh"
|
on-click="handleRefreshClick"></paper-icon-button>
|
||||||
on-click="{{handleRefreshClick}}"></paper-icon-button>
|
|
||||||
</span>
|
|
||||||
|
|
||||||
<div flex class="{{ {content: true, narrow: narrow, wide: !narrow} | tokenList }}">
|
<div flex class="[[computeContentClasses(narrow)]]">
|
||||||
<state-timeline stateHistory="{{stateHistory}}" isLoadingData="{{isLoadingData}}"></state-timeline>
|
<state-history-charts state-history="[[stateHistory]]"
|
||||||
</div>
|
is-loading-data="[[isLoadingData]]"></state-history-charts>
|
||||||
</partial-base>
|
</div>
|
||||||
</template>
|
</partial-base>
|
||||||
|
</template>
|
||||||
|
</dom-module>
|
||||||
<script>
|
<script>
|
||||||
var storeListenerMixIn = window.hass.storeListenerMixIn;
|
(function() {
|
||||||
var stateHistoryActions = window.hass.stateHistoryActions;
|
var stateHistoryActions = window.hass.stateHistoryActions;
|
||||||
|
|
||||||
Polymer(Polymer.mixin({
|
Polymer({
|
||||||
stateHistory: null,
|
is: 'partial-history',
|
||||||
isLoadingData: false,
|
|
||||||
|
|
||||||
attached: function() {
|
behaviors: [StoreListenerBehavior],
|
||||||
this.listenToStores(true);
|
|
||||||
},
|
|
||||||
|
|
||||||
detached: function() {
|
properties: {
|
||||||
this.stopListeningToStores();
|
stateHistory: {
|
||||||
|
type: Object,
|
||||||
|
},
|
||||||
|
|
||||||
|
isLoadingData: {
|
||||||
|
type: Boolean,
|
||||||
|
value: false,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
stateHistoryStoreChanged: function(stateHistoryStore) {
|
stateHistoryStoreChanged: function(stateHistoryStore) {
|
||||||
@ -62,6 +65,10 @@
|
|||||||
this.isLoadingData = true;
|
this.isLoadingData = true;
|
||||||
stateHistoryActions.fetchAll();
|
stateHistoryActions.fetchAll();
|
||||||
},
|
},
|
||||||
}, storeListenerMixIn));
|
|
||||||
</script>
|
computeContentClasses: function(narrow) {
|
||||||
</polymer>
|
return 'content ' + (this.narrow ? 'narrow' : 'wide');
|
||||||
|
},
|
||||||
|
});
|
||||||
|
})();
|
||||||
|
</script>
|
Loading…
x
Reference in New Issue
Block a user