dynamic react router
This commit is contained in:
13
client/dashboard/about/about.component.js
Normal file
13
client/dashboard/about/about.component.js
Normal file
@@ -0,0 +1,13 @@
|
||||
import React from 'react';
|
||||
import Templates from 'config/templates';
|
||||
|
||||
class AboutComponent extends React.Component {
|
||||
|
||||
render() {
|
||||
var aboutRt = Templates.forComponent('about');
|
||||
return aboutRt.call(this);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export default AboutComponent;
|
||||
18
client/dashboard/about/about.rt
Normal file
18
client/dashboard/about/about.rt
Normal file
@@ -0,0 +1,18 @@
|
||||
<div id="about">
|
||||
<div class="panel panel-default">
|
||||
<div class="panel-heading">About</div>
|
||||
<div class="panel-body">
|
||||
<p>This is a Spike bundle prototype using the following lirbaries:</p>
|
||||
<ul>
|
||||
<li>React</li>
|
||||
<li>React Templates</li>
|
||||
<li>React Router</li>
|
||||
<li>LokiJs - persisting API calls to indexedDb</li>
|
||||
<li>Webpack - hot mode developing and app bundling</li>
|
||||
<li>Babel - ES6 transpiler</li>
|
||||
</ul>
|
||||
<p>The demo app consists of a dataset of 10 houses and 10 years of randomly generated power consumption and production at 15 minute intervals. You can toggle between different houses and time periods to compare and contrast the dataset.</p>
|
||||
<p>Select a house below to get started.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
5
client/dashboard/about/about.rt.js
Normal file
5
client/dashboard/about/about.rt.js
Normal file
@@ -0,0 +1,5 @@
|
||||
import React from 'react';
|
||||
import _ from 'lodash';
|
||||
export default function () {
|
||||
return React.createElement('div', { 'id': 'about' }, React.createElement('div', { 'className': 'panel panel-default' }, React.createElement('div', { 'className': 'panel-heading' }, 'About'), React.createElement('div', { 'className': 'panel-body' }, React.createElement('p', {}, 'This is a Spike bundle prototype using the following lirbaries:'), React.createElement('ul', {}, React.createElement('li', {}, 'React'), React.createElement('li', {}, 'React Templates'), React.createElement('li', {}, 'React Router'), React.createElement('li', {}, 'LokiJs - persisting API calls to indexedDb'), React.createElement('li', {}, 'Webpack - hot mode developing and app bundling'), React.createElement('li', {}, 'Babel - ES6 transpiler')), React.createElement('p', {}, 'The demo app consists of a dataset of 10 houses and 10 years of randomly generated power consumption and production at 15 minute intervals. You can toggle between different houses and time periods to compare and contrast the dataset.'), React.createElement('p', {}, 'Select a house below to get started.'))));
|
||||
};
|
||||
77
client/dashboard/energy/energy.component.js
Normal file
77
client/dashboard/energy/energy.component.js
Normal file
@@ -0,0 +1,77 @@
|
||||
import React from 'react';
|
||||
import Templates from 'config/templates';
|
||||
import House from './../../models/house';
|
||||
import {RouteHelper} from './../routes';
|
||||
|
||||
class EnergyComponent extends React.Component {
|
||||
|
||||
constructor(props){
|
||||
super(props);
|
||||
var energy = this;
|
||||
energy.state = {
|
||||
loading_energy_data: true
|
||||
};
|
||||
}
|
||||
|
||||
componentDidMount(){
|
||||
var energy = this,
|
||||
house = energy.context.house;
|
||||
if (!house || energy.context.loading_energy_data) return false;
|
||||
house.setEnergyData()
|
||||
.then(()=>{
|
||||
energy.setState({loading_energy_data: false});
|
||||
});
|
||||
}
|
||||
|
||||
componentDidUpdate(prev_props, prev_state, prev_context){
|
||||
var energy = this,
|
||||
house = energy.context.house;
|
||||
if (!house) return false;
|
||||
if (!prev_context.house ||
|
||||
prev_context.house.data.id != energy.context.house.data.id ||
|
||||
!house.matchesYearState(prev_props.params)) {
|
||||
energy.setState({loading_energy_data: true});
|
||||
house.setEnergyData()
|
||||
.then(()=>{
|
||||
energy.setState({loading_energy_data: false}); // will update graph or table.
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
setParam(event){
|
||||
var energy = this,
|
||||
param = event.target.dataset.param,
|
||||
value = event.target.dataset.value,
|
||||
update = {}, route_helper;
|
||||
override[param] = value;
|
||||
route_helper = new RouteHelper(energy.context.house, energy.props);
|
||||
if (route_helper.routeUpdated()){
|
||||
route_helper.updateHouseState();
|
||||
energy.context.router.push(makeRoute(house, energy.props, override));
|
||||
}
|
||||
}
|
||||
|
||||
getChildContext(){
|
||||
var layout = this;
|
||||
return {
|
||||
loading_energy_data: layout.state.loading_energy_data
|
||||
};
|
||||
}
|
||||
|
||||
render() {
|
||||
var energyRt = Templates.forComponent('energy');
|
||||
return energyRt.call(this);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
EnergyComponent.childContextTypes = {
|
||||
loading_energy_data: React.PropTypes.bool.isRequired
|
||||
};
|
||||
|
||||
EnergyComponent.contextTypes = {
|
||||
house: React.PropTypes.instanceOf(House),
|
||||
router: React.PropTypes.object.isRequired
|
||||
};
|
||||
|
||||
export default EnergyComponent;
|
||||
@@ -1,130 +0,0 @@
|
||||
import React from 'react';
|
||||
import Templates from 'config/templates';
|
||||
import House from './../../models/house';
|
||||
import CalendarGridChart from './../../d3/grid/calendar_grid';
|
||||
|
||||
var Energy = React.createClass({
|
||||
|
||||
getInitialState: function(){
|
||||
var energy = this;
|
||||
return {
|
||||
graph_attr: 'production',
|
||||
loading_data: true
|
||||
};
|
||||
},
|
||||
|
||||
handleResize: function(e) {
|
||||
this.setState({windowWidth: window.innerWidth});
|
||||
},
|
||||
|
||||
componentDidMount: function() {
|
||||
// window.addEventListener('resize', this.handleResize);
|
||||
var energy = this,
|
||||
house = energy.props.house;
|
||||
energy.graph_title = 'Daily Consumption';
|
||||
house.setEnergyData().then(()=>{
|
||||
energy.setState({loading_data: false});
|
||||
if (energy.props.view === 'graph') energy.initGraph();
|
||||
});
|
||||
},
|
||||
|
||||
componentWillReceiveProps: function(new_props){
|
||||
var energy = this;
|
||||
if (new_props.house !== energy.props.house){
|
||||
energy.setState({loading_data: true});
|
||||
new_props.house.setEnergyData().then(()=>{
|
||||
energy.setState({loading_data: false});
|
||||
if (energy.props.view === 'graph') energy.initGraph();
|
||||
});
|
||||
}
|
||||
if (new_props.view !== 'graph' && energy.props.view === 'graph') energy.destroyGraph();
|
||||
},
|
||||
|
||||
componentDidUpdate: function(prev_props, _prev_state){
|
||||
var energy = this,
|
||||
house = energy.props.house;
|
||||
if (prev_props.view !== 'graph' && energy.props.view === 'graph') energy.initGraph();
|
||||
if (prev_props.year !== energy.props.year){
|
||||
energy.updateCurrentMonth();
|
||||
}
|
||||
},
|
||||
|
||||
setGraphAttr: function(event){
|
||||
var energy = this,
|
||||
graph_attr = event.target.dataset.value;
|
||||
if (graph_attr !== energy.state.graph_attr){
|
||||
energy.graph_title = 'Daily ' + event.target.innerText;
|
||||
energy.setState({
|
||||
graph_attr: graph_attr
|
||||
}, function(){
|
||||
if (energy.props.view === 'graph') energy.updateGraph();
|
||||
})
|
||||
}
|
||||
},
|
||||
|
||||
initGraph: function(){
|
||||
var energy = this;
|
||||
if (energy.graph === undefined){
|
||||
energy.graph = new CalendarGridChart({
|
||||
container: '#energy_graph',
|
||||
outer_width: 800,
|
||||
outer_height: 300,
|
||||
date_attr: 'day',
|
||||
color: '#0404B4',
|
||||
toDate: (energy_datum)=>{ return energy_datum.day_to_date; }
|
||||
});
|
||||
jQuery('#energy_graph').tooltip({
|
||||
selector: '.d3-chart-grid-unit',
|
||||
container: 'body',
|
||||
title: function(){
|
||||
var energy_datum = this.__data__,
|
||||
date_s = d3.time.format('%a %b %d, %Y')(energy_datum.day_to_date),
|
||||
range_value = `${Math.round(energy_datum.data[energy.state.graph_attr])} kWh`;
|
||||
return `${date_s}: ${range_value}`;
|
||||
}
|
||||
});
|
||||
}
|
||||
energy.updateGraph();
|
||||
},
|
||||
|
||||
updateGraph: function(){
|
||||
var energy = this,
|
||||
house = energy.props.house;
|
||||
energy.graph.rangeValue = (datum)=>{ return datum.data[energy.state.graph_attr]; }
|
||||
energy.graph.drawData({
|
||||
title: energy.graph_title,
|
||||
css_class: '',
|
||||
min_range: 0,
|
||||
max_range: 150,
|
||||
values: house.energy_data
|
||||
});
|
||||
},
|
||||
|
||||
destroyGraph: function(){
|
||||
var energy = this;
|
||||
document.getElementById('energy_graph').innerHTML = '';
|
||||
energy.graph = undefined;
|
||||
},
|
||||
|
||||
updateCurrentMonth: function(){
|
||||
var energy = this,
|
||||
house = energy.props.house;
|
||||
house.setEnergyData()
|
||||
.then(()=>{
|
||||
if (energy.props.view === 'graph'){
|
||||
// no update necessary since year already updated in layout.rt.
|
||||
energy.updateGraph();
|
||||
} else {
|
||||
// force update to render correct data in table.
|
||||
energy.forceUpdate();
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
render: function() {
|
||||
var energyRt = Templates.forComponent('energy');
|
||||
return energyRt.call(this);
|
||||
}
|
||||
});
|
||||
|
||||
export default Energy;
|
||||
@@ -1,39 +1,23 @@
|
||||
<div id="energy_view">
|
||||
<div class="alert alert-warning" rt-if="this.state.loading_data">
|
||||
Retrieving energy data for the {this.props.house.name} household...
|
||||
<div class="alert alert-warning" rt-if="this.state.loading_energy_data">
|
||||
Retrieving energy data...
|
||||
</div>
|
||||
<div rt-if="this.props.view === 'graph'">
|
||||
<h4>Select Data</h4>
|
||||
<div class="btn-group" role="group">
|
||||
<button
|
||||
data-param="graph_attr"
|
||||
data-value="consumption"
|
||||
rt-class="{active: this.state.graph_attr === 'consumption'}"
|
||||
onClick="{this.setGraphAttr}"
|
||||
onClick="{this.setAttr}"
|
||||
type="button" class="btn btn-primary">Consumption</button>
|
||||
<button
|
||||
data-param="graph_attr"
|
||||
data-value="production"
|
||||
rt-class="{active: this.state.graph_attr === 'production'}"
|
||||
onClick="{this.setGraphAttr}"
|
||||
type="button" class="btn btn-primary">Production</button>
|
||||
</div>
|
||||
</div>
|
||||
<table class="table" rt-if="this.props.view === 'table'">
|
||||
<thead>
|
||||
<tr>
|
||||
<th></th>
|
||||
<th>Day</th>
|
||||
<th>Consumption (kWh)</th>
|
||||
<th>Production (kWh)</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr rt-repeat="energy_datum in this.props.house.energy_data" key="{energy_datum.scoped_id}">
|
||||
<td></td>
|
||||
<td>{energy_datum.day_to_s}</td>
|
||||
<td>{energy_datum.consumption_to_s}</td>
|
||||
<td>{energy_datum.production_to_s}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<div rt-if="this.props.view === 'graph'" id="energy_graph"></div>
|
||||
{this.props.children}
|
||||
</div>
|
||||
|
||||
@@ -1,25 +1,20 @@
|
||||
import React from 'react';
|
||||
import _ from 'lodash';
|
||||
function repeatEnergy_datum1(energy_datum, energy_datumIndex) {
|
||||
return React.createElement('tr', { 'key': energy_datum.scoped_id }, React.createElement('td', {}), React.createElement('td', {}, energy_datum.day_to_s), React.createElement('td', {}, energy_datum.consumption_to_s), React.createElement('td', {}, energy_datum.production_to_s));
|
||||
}
|
||||
export default function () {
|
||||
return React.createElement('div', { 'id': 'energy_view' }, this.state.loading_data ? React.createElement('div', { 'className': 'alert alert-warning' }, '\n Retrieving energy data for the ', this.props.house.name, ' household...\n ') : null, this.props.view === 'graph' ? React.createElement('div', {}, React.createElement('h4', {}, 'Select Data'), React.createElement('div', {
|
||||
return React.createElement('div', { 'id': 'energy_view' }, this.state.loading_energy_data ? React.createElement('div', { 'className': 'alert alert-warning' }, '\n Retrieving energy data...\n ') : null, this.props.view === 'graph' ? React.createElement('div', {}, React.createElement('h4', {}, 'Select Data'), React.createElement('div', {
|
||||
'className': 'btn-group',
|
||||
'role': 'group'
|
||||
}, React.createElement('button', {
|
||||
'data-param': 'graph_attr',
|
||||
'data-value': 'consumption',
|
||||
'className': _.keys(_.pick({ active: this.state.graph_attr === 'consumption' }, _.identity)).join(' ') + ' ' + 'btn btn-primary',
|
||||
'onClick': this.setGraphAttr,
|
||||
'onClick': this.setAttr,
|
||||
'type': 'button'
|
||||
}, 'Consumption'), React.createElement('button', {
|
||||
'data-param': 'graph_attr',
|
||||
'data-value': 'production',
|
||||
'className': _.keys(_.pick({ active: this.state.graph_attr === 'production' }, _.identity)).join(' ') + ' ' + 'btn btn-primary',
|
||||
'onClick': this.setGraphAttr,
|
||||
'type': 'button'
|
||||
}, 'Production'))) : null, this.props.view === 'table' ? React.createElement('table', { 'className': 'table' }, React.createElement('thead', {}, React.createElement('tr', {}, React.createElement('th', {}), React.createElement('th', {}, 'Day'), React.createElement('th', {}, 'Consumption (kWh)'), React.createElement('th', {}, 'Production (kWh)'))), React.createElement.apply(this, [
|
||||
'tbody',
|
||||
{},
|
||||
_.map(this.props.house.energy_data, repeatEnergy_datum1.bind(this))
|
||||
])) : null, this.props.view === 'graph' ? React.createElement('div', { 'id': 'energy_graph' }) : null);
|
||||
}, 'Production'))) : null, '\n ', this.props.children, '\n');
|
||||
};
|
||||
76
client/dashboard/energy/graph/graph.component.js
Normal file
76
client/dashboard/energy/graph/graph.component.js
Normal file
@@ -0,0 +1,76 @@
|
||||
import React from 'react';
|
||||
|
||||
import Templates from 'config/templates';
|
||||
import CalendarGridChart from './../../../d3/grid/calendar_grid';
|
||||
import House from './../../../models/house';
|
||||
|
||||
class GraphComponent extends React.Component {
|
||||
|
||||
componentDidMount(){
|
||||
var energy_graph = this,
|
||||
house = energy_graph.context.house;
|
||||
if (!energy_graph.context.loading_energy_data) energy_graph.updateGraph();
|
||||
}
|
||||
|
||||
componentDidUpdate(prev_props, prev_state, prev_context){
|
||||
var energy_graph = this,
|
||||
house = energy_graph.context.house;
|
||||
if (energy_graph.context.loading_energy_data) {return false;}
|
||||
if (!prev_context.house ||
|
||||
prev_context.loading_energy_data ||
|
||||
prev_context.house.id != energy_graph.context.house.id) {
|
||||
energy_graph.updateGraph();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
updateGraph(){
|
||||
var energy_graph = this,
|
||||
house = energy_graph.context.house,
|
||||
graph_attr = energy_graph.props.params.graph_attr;
|
||||
|
||||
if (energy_graph.graph === undefined){
|
||||
energy_graph.graph = new CalendarGridChart({
|
||||
container: '#energy_graph',
|
||||
outer_width: 800,
|
||||
outer_height: 300,
|
||||
date_attr: 'day',
|
||||
color: '#0404B4',
|
||||
toDate: (energy_datum)=>{ return energy_datum.day_to_date; }
|
||||
});
|
||||
jQuery('#energy_graph').tooltip({
|
||||
selector: '.d3-chart-grid-unit',
|
||||
container: 'body',
|
||||
title: function(){
|
||||
var energy_datum = this.__data__,
|
||||
date_s = d3.time.format('%a %b %d, %Y')(energy_datum.day_to_date),
|
||||
range_value = `${Math.round(energy_datum.data[graph_attr])} kWh`;
|
||||
return `${date_s}: ${range_value}`;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
energy_graph.graph.rangeValue = (datum)=>{ return datum.data[graph_attr]; }
|
||||
energy_graph.graph.drawData({
|
||||
title: energy_graph.graph_title,
|
||||
css_class: '',
|
||||
min_range: 0,
|
||||
max_range: 150,
|
||||
values: house.energy_data
|
||||
});
|
||||
}
|
||||
|
||||
render(){
|
||||
var energyGraphRt = Templates.forComponent('energy_graph');
|
||||
return energyGraphRt.call(this);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
GraphComponent.contextTypes = {
|
||||
house: React.PropTypes.instanceOf(House),
|
||||
loading_energy_data: React.PropTypes.bool.isRequired,
|
||||
router: React.PropTypes.object.isRequired
|
||||
};
|
||||
|
||||
export default GraphComponent;
|
||||
1
client/dashboard/energy/graph/graph.rt
Normal file
1
client/dashboard/energy/graph/graph.rt
Normal file
@@ -0,0 +1 @@
|
||||
<div id="energy_graph"></div>
|
||||
5
client/dashboard/energy/graph/graph.rt.js
Normal file
5
client/dashboard/energy/graph/graph.rt.js
Normal file
@@ -0,0 +1,5 @@
|
||||
import React from 'react';
|
||||
import _ from 'lodash';
|
||||
export default function () {
|
||||
return React.createElement('div', { 'id': 'energy_graph' });
|
||||
};
|
||||
20
client/dashboard/energy/table/table.component.js
Normal file
20
client/dashboard/energy/table/table.component.js
Normal file
@@ -0,0 +1,20 @@
|
||||
import React from 'react';
|
||||
import Templates from 'config/templates';
|
||||
|
||||
import House from './../../../models/house';
|
||||
|
||||
class TableComponent extends React.Component {
|
||||
|
||||
render() {
|
||||
var tableRt = Templates.forComponent('energy_table');
|
||||
return tableRt.call(this);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
TableComponent.contextTypes = {
|
||||
house: React.PropTypes.instanceOf(House),
|
||||
router: React.PropTypes.object.isRequired
|
||||
};
|
||||
|
||||
export default TableComponent;
|
||||
18
client/dashboard/energy/table/table.rt
Normal file
18
client/dashboard/energy/table/table.rt
Normal file
@@ -0,0 +1,18 @@
|
||||
<table rt-if="this.context.house" class="table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th></th>
|
||||
<th>Day</th>
|
||||
<th>Consumption (kWh)</th>
|
||||
<th>Production (kWh)</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr rt-repeat="energy_datum in this.context.house.energy_data" key="{energy_datum.scoped_id}">
|
||||
<td></td>
|
||||
<td>{energy_datum.day_to_s}</td>
|
||||
<td>{energy_datum.consumption_to_s}</td>
|
||||
<td>{energy_datum.production_to_s}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
12
client/dashboard/energy/table/table.rt.js
Normal file
12
client/dashboard/energy/table/table.rt.js
Normal file
@@ -0,0 +1,12 @@
|
||||
import React from 'react';
|
||||
import _ from 'lodash';
|
||||
function repeatEnergy_datum1(energy_datum, energy_datumIndex) {
|
||||
return React.createElement('tr', { 'key': energy_datum.scoped_id }, React.createElement('td', {}), React.createElement('td', {}, energy_datum.day_to_s), React.createElement('td', {}, energy_datum.consumption_to_s), React.createElement('td', {}, energy_datum.production_to_s));
|
||||
}
|
||||
export default function () {
|
||||
return this.context.house ? React.createElement('table', { 'className': 'table' }, React.createElement('thead', {}, React.createElement('tr', {}, React.createElement('th', {}), React.createElement('th', {}, 'Day'), React.createElement('th', {}, 'Consumption (kWh)'), React.createElement('th', {}, 'Production (kWh)'))), React.createElement.apply(this, [
|
||||
'tbody',
|
||||
{},
|
||||
_.map(this.context.house.energy_data, repeatEnergy_datum1.bind(this))
|
||||
])) : null;
|
||||
};
|
||||
64
client/dashboard/house/house.component.js
Normal file
64
client/dashboard/house/house.component.js
Normal file
@@ -0,0 +1,64 @@
|
||||
import React from 'react';
|
||||
import Templates from 'config/templates';
|
||||
import House from './../../models/house';
|
||||
import {RouteHelper} from './../routes';
|
||||
import EnergyComponent from './../energy/energy.component';
|
||||
import PowerComponent from './../power/power.component';
|
||||
|
||||
class HouseComponent extends React.Component {
|
||||
|
||||
constructor(props){
|
||||
super(props);
|
||||
this.renders = 0;
|
||||
}
|
||||
|
||||
setParam(event){
|
||||
var house_component = this,
|
||||
house = house_component.context.house,
|
||||
param = event.target.dataset.param,
|
||||
value = event.target.dataset.value,
|
||||
update = {}, route_helper;
|
||||
update[param] = value;
|
||||
route_helper = new RouteHelper(house, house_component.props, update);
|
||||
if (route_helper.routeUpdated()){
|
||||
route_helper.updateHouseState();
|
||||
if (house_component.renders < 10){
|
||||
house_component.context.router.push(route_helper.newRoute());
|
||||
house_component.renders += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
graphSelected(){
|
||||
var house_component = this;
|
||||
return RouteHelper.graphSelected(house_component.props.routes);
|
||||
}
|
||||
|
||||
tableSelected(){
|
||||
var house_component = this;
|
||||
return RouteHelper.tableSelected(house_component.props.routes);
|
||||
}
|
||||
|
||||
energySelected(){
|
||||
var house_component = this;
|
||||
return RouteHelper.energySelected(house_component.props.routes);
|
||||
}
|
||||
|
||||
powerSelected(){
|
||||
var house_component = this;
|
||||
return RouteHelper.powerSelected(house_component.props.routes);
|
||||
}
|
||||
|
||||
render() {
|
||||
var houseRt = Templates.forComponent('house');
|
||||
return houseRt.call(this);
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
HouseComponent.contextTypes = {
|
||||
house: React.PropTypes.instanceOf(House),
|
||||
router: React.PropTypes.object.isRequired
|
||||
};
|
||||
|
||||
export default HouseComponent;
|
||||
49
client/dashboard/house/house.rt
Normal file
49
client/dashboard/house/house.rt
Normal file
@@ -0,0 +1,49 @@
|
||||
<div id="house">
|
||||
<h4>Select dataset:</h4>
|
||||
<div class="btn-group" role="group">
|
||||
<button
|
||||
data-param="dataset"
|
||||
data-value="energy"
|
||||
rt-class="{active: this.energySelected()}"
|
||||
onClick="{this.setParam.bind(this)}"
|
||||
type="button" class="btn btn-primary">Daily Energy Statistics</button>
|
||||
<button
|
||||
data-param="dataset"
|
||||
data-value="power"
|
||||
rt-class="{active: this.powerSelected()}"
|
||||
onClick="{this.setParam.bind(this)}"
|
||||
type="button" class="btn btn-primary">15-minute Power Statistics</button>
|
||||
</div>
|
||||
|
||||
<h4>View as:</h4>
|
||||
<div class="btn-group" role="group">
|
||||
<button
|
||||
data-param="view"
|
||||
data-value="graph"
|
||||
rt-class="{active: this.graphSelected()}"
|
||||
onClick="{this.setParam.bind(this)}"
|
||||
type="button" class="btn btn-primary">Graph</button>
|
||||
<button
|
||||
data-param="view"
|
||||
data-value="table"
|
||||
rt-class="{active: this.tableSelected()}"
|
||||
onClick="{this.setParam.bind(this)}"
|
||||
type="button" class="btn btn-primary">Table</button>
|
||||
</div>
|
||||
|
||||
<div rt-if="this.context.house">
|
||||
<h4>Select dates:</h4>
|
||||
<div class="btn-group">
|
||||
<button
|
||||
rt-repeat="year in this.context.house.years"
|
||||
data-param="year"
|
||||
data-value="{year}"
|
||||
key="data-year-{year}"
|
||||
class="btn-info btn btn-sm"
|
||||
rt-class="{active: year == this.context.house.state.year}"
|
||||
onClick="{this.setParam.bind(this)}">{year}</button>
|
||||
</div>
|
||||
</div><br/>
|
||||
|
||||
{this.props.children}
|
||||
</div>
|
||||
48
client/dashboard/house/house.rt.js
Normal file
48
client/dashboard/house/house.rt.js
Normal file
@@ -0,0 +1,48 @@
|
||||
import React from 'react';
|
||||
import _ from 'lodash';
|
||||
function repeatYear1(year, yearIndex) {
|
||||
return React.createElement('button', {
|
||||
'data-param': 'year',
|
||||
'data-value': year,
|
||||
'key': 'data-year-' + year,
|
||||
'className': 'btn-info btn btn-sm' + ' ' + _.keys(_.pick({ active: year == this.context.house.state.year }, _.identity)).join(' '),
|
||||
'onClick': this.setParam.bind(this)
|
||||
}, year);
|
||||
}
|
||||
export default function () {
|
||||
return React.createElement('div', { 'id': 'house' }, React.createElement('h4', {}, 'Select dataset:'), React.createElement('div', {
|
||||
'className': 'btn-group',
|
||||
'role': 'group'
|
||||
}, React.createElement('button', {
|
||||
'data-param': 'dataset',
|
||||
'data-value': 'energy',
|
||||
'className': _.keys(_.pick({ active: this.energySelected() }, _.identity)).join(' ') + ' ' + 'btn btn-primary',
|
||||
'onClick': this.setParam.bind(this),
|
||||
'type': 'button'
|
||||
}, 'Daily Energy Statistics'), React.createElement('button', {
|
||||
'data-param': 'dataset',
|
||||
'data-value': 'power',
|
||||
'className': _.keys(_.pick({ active: this.powerSelected() }, _.identity)).join(' ') + ' ' + 'btn btn-primary',
|
||||
'onClick': this.setParam.bind(this),
|
||||
'type': 'button'
|
||||
}, '15-minute Power Statistics')), React.createElement('h4', {}, 'View as:'), React.createElement('div', {
|
||||
'className': 'btn-group',
|
||||
'role': 'group'
|
||||
}, React.createElement('button', {
|
||||
'data-param': 'view',
|
||||
'data-value': 'graph',
|
||||
'className': _.keys(_.pick({ active: this.graphSelected() }, _.identity)).join(' ') + ' ' + 'btn btn-primary',
|
||||
'onClick': this.setParam.bind(this),
|
||||
'type': 'button'
|
||||
}, 'Graph'), React.createElement('button', {
|
||||
'data-param': 'view',
|
||||
'data-value': 'table',
|
||||
'className': _.keys(_.pick({ active: this.tableSelected() }, _.identity)).join(' ') + ' ' + 'btn btn-primary',
|
||||
'onClick': this.setParam.bind(this),
|
||||
'type': 'button'
|
||||
}, 'Table')), this.context.house ? React.createElement('div', {}, React.createElement('h4', {}, 'Select dates:'), React.createElement.apply(this, [
|
||||
'div',
|
||||
{ 'className': 'btn-group' },
|
||||
_.map(this.context.house.years, repeatYear1.bind(this))
|
||||
])) : null, React.createElement('br', {}), '\n\n ', this.props.children, '\n');
|
||||
};
|
||||
84
client/dashboard/layout/layout.component.js
Normal file
84
client/dashboard/layout/layout.component.js
Normal file
@@ -0,0 +1,84 @@
|
||||
import React from 'react';
|
||||
import Templates from 'config/templates';
|
||||
import House from './../../models/house';
|
||||
import PowerDatum from './../../models/power_datum';
|
||||
import {RouteHelper} from './../routes';
|
||||
|
||||
class LayoutComponent extends React.Component {
|
||||
|
||||
constructor(props, context){
|
||||
super(props, context);
|
||||
this.renders = 0;
|
||||
this.state = {
|
||||
houses: null,
|
||||
house: null,
|
||||
requesting_data: true
|
||||
};
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
var layout = this;
|
||||
House.ensureHouses().then((houses)=>{
|
||||
var house = null;
|
||||
if (layout.props.params.house_id != undefined){
|
||||
house = houses.find((h)=>{ return h.data.id == layout.props.params.house_id; });
|
||||
var route_helper = new RouteHelper(house, layout.props);
|
||||
if (route_helper.paramsHaveDateState()) route_helper.updateHouseToParams();
|
||||
}
|
||||
layout.setState({
|
||||
houses: houses,
|
||||
requesting_data: false,
|
||||
house: house });
|
||||
});
|
||||
}
|
||||
|
||||
setHouse(event){
|
||||
var layout = this,
|
||||
house_id = event.target.value,
|
||||
old_house = layout.state.house,
|
||||
house = layout.state.houses.find((house)=>{ return house.data.id == house_id });
|
||||
if (!old_house || old_house.id != house_id){
|
||||
var route_helper = new RouteHelper(house, layout.props);
|
||||
route_helper.updateHouseToParams();
|
||||
layout.setState({house: house}, ()=>{
|
||||
if (layout.renders < 10){
|
||||
layout.context.router.push(route_helper.newRoute());
|
||||
layout.renders += 1
|
||||
}
|
||||
if (old_house) old_house.closeDb();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
refreshData(){
|
||||
var layout = this,
|
||||
houses = layout.state.houses,
|
||||
all = [];
|
||||
houses.forEach((house)=>{
|
||||
all.push(house.clearData());
|
||||
});
|
||||
Promise.all(all)
|
||||
.then(()=>{
|
||||
window.location.reload();
|
||||
});
|
||||
}
|
||||
|
||||
getChildContext(){
|
||||
var layout = this;
|
||||
return {house: layout.state.house};
|
||||
}
|
||||
|
||||
render() {
|
||||
var layoutRt = Templates.forComponent('layout');
|
||||
return layoutRt.call(this);
|
||||
}
|
||||
}
|
||||
|
||||
LayoutComponent.contextTypes = {
|
||||
router: React.PropTypes.object.isRequired
|
||||
};
|
||||
|
||||
LayoutComponent.childContextTypes = {
|
||||
house: React.PropTypes.instanceOf(House)
|
||||
};
|
||||
export default LayoutComponent;
|
||||
@@ -1,89 +0,0 @@
|
||||
import React from 'react';
|
||||
import Templates from 'config/templates';
|
||||
import House from './../../models/house';
|
||||
import PowerDatum from './../../models/power_datum';
|
||||
|
||||
var Layout = React.createClass({
|
||||
|
||||
getInitialState: function(){
|
||||
var layout = this;
|
||||
return {
|
||||
houses: null,
|
||||
house: null,
|
||||
view: 'graph',
|
||||
dataset: 'power',
|
||||
requesting_data: true
|
||||
};
|
||||
},
|
||||
|
||||
handleResize: function(e) {
|
||||
this.setState({windowWidth: window.innerWidth});
|
||||
},
|
||||
|
||||
componentDidMount: function() {
|
||||
var layout = this;
|
||||
// window.addEventListener('resize', this.handleResize);
|
||||
House.ensureHouses().then((houses)=>{
|
||||
layout.setState({
|
||||
houses: houses,
|
||||
house: houses[0],
|
||||
requesting_data: false,
|
||||
month: houses[0].current_month,
|
||||
year: houses[0].current_year
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
setHouse: function(event){
|
||||
var layout = this,
|
||||
house_id = event.target.value,
|
||||
old_house = layout.state.house,
|
||||
house = layout.state.houses.find((house)=>{ return house.data.id == house_id });
|
||||
layout.setState({house: house}, ()=>{
|
||||
old_house.closeDb();
|
||||
});
|
||||
},
|
||||
|
||||
setView: function(event){
|
||||
var layout = this,
|
||||
view = event.target.dataset.value;
|
||||
layout.view_name = event.target.innerText;
|
||||
layout.setState({view: view});
|
||||
},
|
||||
|
||||
setDataset: function(event){
|
||||
var layout = this,
|
||||
dataset = event.target.dataset.value;
|
||||
layout.setState({dataset: dataset});
|
||||
},
|
||||
|
||||
setYear: function(event){
|
||||
var layout = this,
|
||||
year = event.target.dataset.value,
|
||||
house = layout.state.house;
|
||||
if (year != house.current_year){
|
||||
house.setYear(year);
|
||||
layout.setState({year: year});
|
||||
}
|
||||
},
|
||||
|
||||
refreshData: function(){
|
||||
var layout = this,
|
||||
houses = layout.state.houses,
|
||||
all = [];
|
||||
houses.forEach((house)=>{
|
||||
all.push(house.clearData());
|
||||
});
|
||||
Promise.all(all)
|
||||
.then(()=>{
|
||||
window.location.reload();
|
||||
});
|
||||
},
|
||||
|
||||
render: function() {
|
||||
var layoutRt = Templates.forComponent('layout');
|
||||
return layoutRt.call(this);
|
||||
}
|
||||
});
|
||||
|
||||
export default Layout;
|
||||
@@ -1,60 +1,11 @@
|
||||
<rt-require dependency="./../energy/energy" as="Energy"/>
|
||||
<rt-require dependency="./../power/power" as="Power"/>
|
||||
<div id="layout">
|
||||
<div class="alert alert-warning" rt-if="this.state.requesting_data">Retrieving houses...</div>
|
||||
|
||||
<h4>Select household:</h4>
|
||||
<select rt-if="this.state.houses" class="form-control" onChange="{this.setHouse}">
|
||||
<select id="houses_select" rt-if="this.state.houses" class="form-control" onChange="{this.setHouse.bind(this)}" value="{this.props.params.house_id}">
|
||||
<option rt-repeat="house in this.state.houses" value="{house.data.id}" key="{house.scoped_id}">{house.data.name}</option>
|
||||
</select>
|
||||
<button rt-if="this.state.house" onClick="{this.refreshData}" class="btn btn-xs btn-default">Refresh House Data</button>
|
||||
<button rt-if="this.state.house" onClick="{this.refreshData.bind(this)}" class="btn btn-xs btn-default">Refresh House Data</button>
|
||||
|
||||
<h4>Select dataset:</h4>
|
||||
<div class="btn-group" role="group">
|
||||
<button
|
||||
data-value="energy"
|
||||
rt-class="{active: this.state.dataset === 'energy'}"
|
||||
onClick="{this.setDataset}"
|
||||
type="button" class="btn btn-primary">Daily Energy Statistics</button>
|
||||
<button
|
||||
data-value="power"
|
||||
rt-class="{active: this.state.dataset === 'power'}"
|
||||
onClick="{this.setDataset}"
|
||||
type="button" class="btn btn-primary">15-minute Power Statistics</button>
|
||||
</div>
|
||||
|
||||
<h4>View as:</h4>
|
||||
<div class="btn-group" role="group">
|
||||
<button
|
||||
data-value="graph"
|
||||
rt-class="{active: this.state.view === 'graph'}"
|
||||
onClick="{this.setView}"
|
||||
type="button" class="btn btn-primary">Graph</button>
|
||||
<button
|
||||
data-value="table"
|
||||
rt-class="{active: this.state.view === 'table'}"
|
||||
onClick="{this.setView}"
|
||||
type="button" class="btn btn-primary">Table</button>
|
||||
</div>
|
||||
|
||||
<h4>Select dates:</h4>
|
||||
<div class="btn-group">
|
||||
<button
|
||||
rt-if="this.state.house"
|
||||
rt-repeat="year in this.state.house.years"
|
||||
data-value="{year}"
|
||||
key="data-year-{year}"
|
||||
class="btn-info btn btn-sm"
|
||||
rt-class="{active: year == this.state.house.current_year}"
|
||||
onClick="{this.setYear}">{year}</button>
|
||||
</div><br/>
|
||||
|
||||
<Energy rt-if="this.state.house && this.state.dataset === 'energy'"
|
||||
house="{this.state.house}"
|
||||
view="{this.state.view}"
|
||||
year="{this.state.year}"></Energy>
|
||||
<Power rt-if="this.state.house && this.state.dataset === 'power'"
|
||||
house="{this.state.house}"
|
||||
view="{this.state.view}"
|
||||
year="{this.state.year}"></Power>
|
||||
{this.props.children}
|
||||
</div>
|
||||
|
||||
@@ -1,69 +1,23 @@
|
||||
import React from 'react';
|
||||
import _ from 'lodash';
|
||||
import Energy from './../energy/energy';
|
||||
import Power from './../power/power';
|
||||
function repeatHouse1(house, houseIndex) {
|
||||
return React.createElement('option', {
|
||||
'value': house.data.id,
|
||||
'key': house.scoped_id
|
||||
}, house.data.name);
|
||||
}
|
||||
function repeatYear2(year, yearIndex) {
|
||||
return React.createElement('button', {
|
||||
'data-value': year,
|
||||
'key': 'data-year-' + year,
|
||||
'className': 'btn-info btn btn-sm' + ' ' + _.keys(_.pick({ active: year == this.state.house.current_year }, _.identity)).join(' '),
|
||||
'onClick': this.setYear
|
||||
}, year);
|
||||
}
|
||||
export default function () {
|
||||
return React.createElement('div', { 'id': 'layout' }, this.state.requesting_data ? React.createElement('div', { 'className': 'alert alert-warning' }, 'Retrieving houses...') : null, React.createElement('h4', {}, 'Select household:'), this.state.houses ? React.createElement.apply(this, [
|
||||
'select',
|
||||
{
|
||||
'id': 'houses_select',
|
||||
'className': 'form-control',
|
||||
'onChange': this.setHouse
|
||||
'onChange': this.setHouse.bind(this),
|
||||
'value': this.props.params.house_id
|
||||
},
|
||||
_.map(this.state.houses, repeatHouse1.bind(this))
|
||||
]) : null, this.state.house ? React.createElement('button', {
|
||||
'onClick': this.refreshData,
|
||||
'onClick': this.refreshData.bind(this),
|
||||
'className': 'btn btn-xs btn-default'
|
||||
}, 'Refresh House Data') : null, React.createElement('h4', {}, 'Select dataset:'), React.createElement('div', {
|
||||
'className': 'btn-group',
|
||||
'role': 'group'
|
||||
}, React.createElement('button', {
|
||||
'data-value': 'energy',
|
||||
'className': _.keys(_.pick({ active: this.state.dataset === 'energy' }, _.identity)).join(' ') + ' ' + 'btn btn-primary',
|
||||
'onClick': this.setDataset,
|
||||
'type': 'button'
|
||||
}, 'Daily Energy Statistics'), React.createElement('button', {
|
||||
'data-value': 'power',
|
||||
'className': _.keys(_.pick({ active: this.state.dataset === 'power' }, _.identity)).join(' ') + ' ' + 'btn btn-primary',
|
||||
'onClick': this.setDataset,
|
||||
'type': 'button'
|
||||
}, '15-minute Power Statistics')), React.createElement('h4', {}, 'View as:'), React.createElement('div', {
|
||||
'className': 'btn-group',
|
||||
'role': 'group'
|
||||
}, React.createElement('button', {
|
||||
'data-value': 'graph',
|
||||
'className': _.keys(_.pick({ active: this.state.view === 'graph' }, _.identity)).join(' ') + ' ' + 'btn btn-primary',
|
||||
'onClick': this.setView,
|
||||
'type': 'button'
|
||||
}, 'Graph'), React.createElement('button', {
|
||||
'data-value': 'table',
|
||||
'className': _.keys(_.pick({ active: this.state.view === 'table' }, _.identity)).join(' ') + ' ' + 'btn btn-primary',
|
||||
'onClick': this.setView,
|
||||
'type': 'button'
|
||||
}, 'Table')), React.createElement('h4', {}, 'Select dates:'), React.createElement.apply(this, [
|
||||
'div',
|
||||
{ 'className': 'btn-group' },
|
||||
this.state.house ? _.map(this.state.house.years, repeatYear2.bind(this)) : null
|
||||
]), React.createElement('br', {}), this.state.house && this.state.dataset === 'energy' ? React.createElement(Energy, {
|
||||
'house': this.state.house,
|
||||
'view': this.state.view,
|
||||
'year': this.state.year
|
||||
}) : null, this.state.house && this.state.dataset === 'power' ? React.createElement(Power, {
|
||||
'house': this.state.house,
|
||||
'view': this.state.view,
|
||||
'year': this.state.year
|
||||
}) : null);
|
||||
}, 'Refresh House Data') : null, '\n\n ', this.props.children, '\n');
|
||||
};
|
||||
91
client/dashboard/power/graph/graph.component.js
Normal file
91
client/dashboard/power/graph/graph.component.js
Normal file
@@ -0,0 +1,91 @@
|
||||
import React from 'react';
|
||||
import Templates from 'config/templates';
|
||||
|
||||
import House from './../../../models/house';
|
||||
import SplineStackChart from './../../../d3/line/spline_stack';
|
||||
|
||||
class GraphComponent extends React.Component {
|
||||
|
||||
componentDidMount(){
|
||||
var power_graph = this,
|
||||
house = power_graph.context.house;
|
||||
power_graph.graph_title = ' ';
|
||||
if (!power_graph.context.loading_power_data) power_graph.updateGraph();
|
||||
}
|
||||
|
||||
componentDidUpdate(prev_props, prev_state, prev_context){
|
||||
var power_graph = this,
|
||||
house = power_graph.context.house;
|
||||
if (power_graph.context.loading_power_data) {return false;}
|
||||
if (!prev_context.house ||
|
||||
prev_context.loading_power_data ||
|
||||
prev_context.house.id != power_graph.context.house.id) {
|
||||
power_graph.updateGraph();
|
||||
}
|
||||
}
|
||||
|
||||
updateGraph(){
|
||||
var power_graph = this,
|
||||
house = power_graph.context.house;
|
||||
if (power_graph.graph === undefined){
|
||||
power_graph.graph = new SplineStackChart({
|
||||
container: '#power_graph',
|
||||
outer_width: 800,
|
||||
outer_height: 200,
|
||||
color: '#0404B4',
|
||||
time_series: true,
|
||||
domain_attr: 'x',
|
||||
range_attr: 'y',
|
||||
include_dots: true,
|
||||
titleizeDatum: (series, d)=>{
|
||||
return series.title + '<br/>' + Math.round(d.y) + ' W<br/>' + house.formatDate(d.power_graph_datum.data.time, 'MMM D [at] HH:mm');
|
||||
}
|
||||
});
|
||||
jQuery('#power_graph').tooltip({
|
||||
selector: 'circle',
|
||||
container: 'body',
|
||||
html: true,
|
||||
title: function(){
|
||||
return this.__data__.title;
|
||||
}
|
||||
});
|
||||
}
|
||||
var net_power_graph = {
|
||||
title: 'Net Power Consumption',
|
||||
values: house.power_data.map((power_graph_datum)=>{
|
||||
return {
|
||||
power_graph_datum: power_graph_datum,
|
||||
x: power_graph_datum.time_to_date,
|
||||
y: Math.max(0, power_graph_datum.data.consumption - power_graph_datum.data.production) }
|
||||
})
|
||||
},
|
||||
savings = {
|
||||
title: 'Power Production',
|
||||
values: house.power_data.map((power_graph_datum)=>{
|
||||
return {
|
||||
power_graph_datum: power_graph_datum,
|
||||
x: power_graph_datum.time_to_date,
|
||||
y: power_graph_datum.data.production }
|
||||
})
|
||||
};
|
||||
power_graph.graph.drawData({
|
||||
title: power_graph.graph_title,
|
||||
css_class: '',
|
||||
series: [net_power_graph, savings]
|
||||
});
|
||||
}
|
||||
|
||||
render() {
|
||||
var powerGraphRt = Templates.forComponent('power_graph');
|
||||
return powerGraphRt.call(this);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
GraphComponent.contextTypes = {
|
||||
house: React.PropTypes.instanceOf(House),
|
||||
loading_power_data: React.PropTypes.bool.isRequired,
|
||||
router: React.PropTypes.object.isRequired
|
||||
};
|
||||
|
||||
export default GraphComponent;
|
||||
1
client/dashboard/power/graph/graph.rt
Normal file
1
client/dashboard/power/graph/graph.rt
Normal file
@@ -0,0 +1 @@
|
||||
<div id="power_graph"></div>
|
||||
5
client/dashboard/power/graph/graph.rt.js
Normal file
5
client/dashboard/power/graph/graph.rt.js
Normal file
@@ -0,0 +1,5 @@
|
||||
import React from 'react';
|
||||
import _ from 'lodash';
|
||||
export default function () {
|
||||
return React.createElement('div', { 'id': 'power_graph' });
|
||||
};
|
||||
120
client/dashboard/power/power.component.js
Normal file
120
client/dashboard/power/power.component.js
Normal file
@@ -0,0 +1,120 @@
|
||||
import React from 'react';
|
||||
import moment from 'moment-timezone';
|
||||
import _ from 'lodash';
|
||||
|
||||
import Templates from 'config/templates';
|
||||
import House from './../../models/house';
|
||||
import DateRangeSlider from './../../d3/sliders/date_range';
|
||||
import {RouteHelper} from './../routes';
|
||||
|
||||
class PowerComponent extends React.Component {
|
||||
|
||||
constructor(props){
|
||||
super(props);
|
||||
var power = this;
|
||||
power.state = {
|
||||
loading_power_data: true };
|
||||
power.updates = 0;
|
||||
}
|
||||
|
||||
componentDidMount(){
|
||||
var power = this,
|
||||
house = power.context.house;
|
||||
power.renders = 0;
|
||||
if (!house) return false;
|
||||
power.initDateRange();
|
||||
house.setPowerData()
|
||||
.then(()=>{
|
||||
power.setState({loading_power_data: false }); });
|
||||
}
|
||||
|
||||
componentDidUpdate(prev_props, prev_state, prev_context){
|
||||
var power = this,
|
||||
house = power.context.house;
|
||||
if (!house) return false;
|
||||
var route_helper = new RouteHelper(house, power.props);
|
||||
if ((!prev_context.house || prev_context.house.data.id != power.context.house.data.id) ||
|
||||
!house.matchesPowerRange(prev_props.location.query['dates[]'] || [])) {
|
||||
power.setState({loading_power_data: true});
|
||||
house.setPowerData()
|
||||
.then(()=>{
|
||||
power.initDateRange();
|
||||
power.setState({loading_power_data: false}); // will update graph or table.
|
||||
});
|
||||
} else if (!house.matchesMonthState(prev_props.params)) power.initDateRange();
|
||||
}
|
||||
|
||||
initDateRange(){
|
||||
var power = this,
|
||||
house = power.context.house;
|
||||
if (power.date_range_slider === undefined){
|
||||
power.date_range_slider = new DateRangeSlider({
|
||||
container: '#power_date_setter',
|
||||
outer_height: 100,
|
||||
maxDelta: function(changed_date, other_date){
|
||||
if (Math.abs(changed_date.getTime() - other_date.getTime()) > 3600 * 24 * 4 * 1000){
|
||||
if (changed_date > other_date){
|
||||
return new Date(changed_date.getTime() - 3600 * 24 * 4 * 1000);
|
||||
} else {
|
||||
return new Date(changed_date.getTime() + 3600 * 24 * 4 * 1000);
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
});
|
||||
}
|
||||
power.date_range_slider.onRangeUpdated = (min, max)=>{
|
||||
if (power.date_range_update) clearTimeout(power.date_range_update);
|
||||
power.date_range_update = setTimeout(()=>{
|
||||
var power_range = [Math.round(min.getTime() / 1000), Math.round(max.getTime() / 1000)],
|
||||
route_helper = new RouteHelper(house, power.props, {power_range: power_range});
|
||||
|
||||
route_helper.updateHouseState();
|
||||
power.context.router.push(route_helper.newRoute());
|
||||
}, 500);
|
||||
};
|
||||
power.date_range_slider.drawData({
|
||||
abs_min: house.state.current_month_moment.toDate(),
|
||||
abs_max: house.state.end_of_current_data_moment.toDate(),
|
||||
current_min: house.toDate(house.state.power_range[0]),
|
||||
current_max: house.toDate(house.state.power_range[1])
|
||||
});
|
||||
}
|
||||
|
||||
setParam(event){
|
||||
var power = this,
|
||||
house = power.context.house,
|
||||
param = event.target.dataset.param,
|
||||
value = event.target.dataset.value,
|
||||
update = {}, route_helper;
|
||||
update[param] = value;
|
||||
route_helper = new RouteHelper(house, power.props, update);
|
||||
if (route_helper.routeUpdated()){
|
||||
route_helper.updateHouseState();
|
||||
power.context.router.push(route_helper.newRoute());
|
||||
}
|
||||
}
|
||||
|
||||
getChildContext(){
|
||||
var layout = this;
|
||||
return {
|
||||
loading_power_data: layout.state.loading_power_data
|
||||
};
|
||||
}
|
||||
|
||||
render() {
|
||||
var powerRt = Templates.forComponent('power');
|
||||
return powerRt.call(this);
|
||||
}
|
||||
}
|
||||
|
||||
PowerComponent.childContextTypes = {
|
||||
loading_power_data: React.PropTypes.bool.isRequired
|
||||
};
|
||||
|
||||
PowerComponent.contextTypes = {
|
||||
house: React.PropTypes.instanceOf(House),
|
||||
router: React.PropTypes.object.isRequired
|
||||
};
|
||||
|
||||
export default PowerComponent;
|
||||
@@ -1,195 +0,0 @@
|
||||
import React from 'react';
|
||||
import moment from 'moment-timezone';
|
||||
import _ from 'lodash';
|
||||
|
||||
import Templates from 'config/templates';
|
||||
import House from './../../models/house';
|
||||
import SplineStackChart from './../../d3/line/spline_stack';
|
||||
import DateRangeSlider from './../../d3/sliders/date_range';
|
||||
|
||||
var Power = React.createClass({
|
||||
|
||||
getInitialState: function(){
|
||||
var power = this;
|
||||
return {
|
||||
loading_data: true
|
||||
};
|
||||
},
|
||||
|
||||
handleResize: function(e) {
|
||||
this.setState({windowWidth: window.innerWidth});
|
||||
},
|
||||
|
||||
componentDidMount: function() {
|
||||
// window.addEventListener('resize', this.handleResize);
|
||||
var power = this,
|
||||
house = power.props.house;
|
||||
power.graph_title = '';
|
||||
power.initDateRange();
|
||||
house.setPowerData().then(()=>{
|
||||
power.setState({loading_data: false});
|
||||
if (power.props.view === 'graph'){
|
||||
power.initGraph();
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
componentWillReceiveProps: function(new_props){
|
||||
var power = this;
|
||||
if (new_props.house !== power.props.house){
|
||||
// house will change.
|
||||
power.setState({loading_data: true});
|
||||
new_props.house.setPowerData().then(()=>{
|
||||
power.setState({loading_data: false});
|
||||
if (power.props.view === 'graph'){
|
||||
power.initGraph();
|
||||
}
|
||||
});
|
||||
}
|
||||
// view will change from graph to table.
|
||||
if (new_props.view !== 'graph' && power.props.view === 'graph') power.destroyGraph();
|
||||
},
|
||||
|
||||
componentDidUpdate: function(prev_props, _prev_state){
|
||||
var power = this,
|
||||
house = power.props.house;
|
||||
// view has changed from graph to table.
|
||||
if (prev_props.view !== 'graph' && power.props.view === 'graph'){
|
||||
power.initGraph();
|
||||
}
|
||||
if (prev_props.house !== house) power.initDateRange();
|
||||
var need_update = false;
|
||||
if (prev_props.year !== power.props.year){
|
||||
power.updateCurrentMonth();
|
||||
}
|
||||
},
|
||||
|
||||
initGraph: function(){
|
||||
var power = this,
|
||||
house = power.props.house;
|
||||
if (power.graph === undefined){
|
||||
power.graph = new SplineStackChart({
|
||||
container: '#power_graph',
|
||||
outer_width: 800,
|
||||
outer_height: 200,
|
||||
color: '#0404B4',
|
||||
time_series: true,
|
||||
domain_attr: 'x',
|
||||
range_attr: 'y',
|
||||
include_dots: true,
|
||||
titleizeDatum: (series, d)=>{
|
||||
return series.title + '<br/>' + Math.round(d.y) + ' W<br/>' + house.formatDate(d.power_datum.data.time, 'MMM D [at] HH:mm');
|
||||
}
|
||||
});
|
||||
jQuery('#power_graph').tooltip({
|
||||
selector: 'circle',
|
||||
container: 'body',
|
||||
html: true,
|
||||
title: function(){
|
||||
return this.__data__.title;
|
||||
}
|
||||
});
|
||||
}
|
||||
power.updateGraph();
|
||||
},
|
||||
|
||||
updateGraph: function(){
|
||||
var power = this,
|
||||
house = power.props.house,
|
||||
net_power = {
|
||||
title: 'Net Power Consumption',
|
||||
values: house.power_data.map((power_datum)=>{
|
||||
return {
|
||||
power_datum: power_datum,
|
||||
x: power_datum.time_to_date,
|
||||
y: Math.max(0, power_datum.data.consumption - power_datum.data.production) }
|
||||
})
|
||||
},
|
||||
savings = {
|
||||
title: 'Power Production',
|
||||
values: house.power_data.map((power_datum)=>{
|
||||
return {
|
||||
power_datum: power_datum,
|
||||
x: power_datum.time_to_date,
|
||||
y: power_datum.data.production }
|
||||
})
|
||||
};
|
||||
power.graph.drawData({
|
||||
title: power.graph_title,
|
||||
css_class: '',
|
||||
series: [net_power, savings]
|
||||
});
|
||||
},
|
||||
|
||||
initDateRange: function(){
|
||||
var power = this,
|
||||
house = power.props.house;
|
||||
if (power.date_range_slider === undefined){
|
||||
power.date_range_slider = new DateRangeSlider({
|
||||
container: '#power_date_setter',
|
||||
outer_height: 100,
|
||||
maxDelta: function(changed_date, other_date){
|
||||
if (Math.abs(changed_date.getTime() - other_date.getTime()) > 3600 * 24 * 4 * 1000){
|
||||
if (changed_date > other_date){
|
||||
return new Date(changed_date.getTime() - 3600 * 24 * 4 * 1000);
|
||||
} else {
|
||||
return new Date(changed_date.getTime() + 3600 * 24 * 4 * 1000);
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
});
|
||||
}
|
||||
power.date_range_slider.onRangeUpdated = (min, max)=>{
|
||||
if (power.date_range_update) clearTimeout(power.date_range_update)
|
||||
power.date_range_update = setTimeout(()=>{
|
||||
house.power_date_range = [Math.round(min.getTime() / 1000), Math.round(max.getTime() / 1000)]
|
||||
house.setPowerData()
|
||||
.then(()=>{
|
||||
if (power.props.view === 'graph') power.updateGraph();
|
||||
else power.forceUpdate();
|
||||
});
|
||||
}, 500);
|
||||
};
|
||||
power.date_range_slider.drawData({
|
||||
abs_min: house.current_month_moment.toDate(),
|
||||
abs_max: house.end_of_current_data_moment.toDate(),
|
||||
current_min: house.toDate(house.power_date_range[0]),
|
||||
current_max: house.toDate(house.power_date_range[1])
|
||||
});
|
||||
},
|
||||
|
||||
destroyGraph: function(){
|
||||
var power = this;
|
||||
document.getElementById('power_graph').innerHTML = '';
|
||||
power.graph = undefined;
|
||||
},
|
||||
|
||||
setMonth: function(event){
|
||||
var power = this,
|
||||
house = power.props.house,
|
||||
month = event.target.dataset.value;
|
||||
if (month !== house.current_month){
|
||||
var need_update = house.setMonth(month);
|
||||
if (need_update) power.updateCurrentMonth();
|
||||
}
|
||||
},
|
||||
|
||||
updateCurrentMonth: function(){
|
||||
var power = this,
|
||||
house = power.props.house;
|
||||
power.initDateRange();
|
||||
house.setPowerData()
|
||||
.then(()=>{
|
||||
power.forceUpdate();
|
||||
if (power.props.view === 'graph') power.updateGraph();
|
||||
});
|
||||
},
|
||||
|
||||
render: function() {
|
||||
var powerRt = Templates.forComponent('power');
|
||||
return powerRt.call(this);
|
||||
}
|
||||
});
|
||||
|
||||
export default Power;
|
||||
@@ -1,35 +1,18 @@
|
||||
<div id="power_view">
|
||||
<div class="btn-group">
|
||||
<button
|
||||
rt-if="this.props.house"
|
||||
rt-repeat="month in this.props.house.availableMonths()"
|
||||
rt-if="this.context.house"
|
||||
rt-repeat="month in this.context.house.availableMonths()"
|
||||
data-param="month"
|
||||
data-value="{month}"
|
||||
key="data-month-{month}"
|
||||
class="btn-warning btn btn-sm"
|
||||
rt-class="{active: month === this.props.house.current_month}"
|
||||
onClick="{this.setMonth}">{month}</button>
|
||||
rt-class="{active: month === this.context.house.state.month}"
|
||||
onClick="{this.setParam.bind(this)}">{month}</button>
|
||||
</div>
|
||||
<div class="alert alert-warning" rt-if="this.state.loading_data">
|
||||
Retrieving power data for the {this.props.house.name} household...
|
||||
<div class="alert alert-warning" rt-if="this.state.loading_power_data">
|
||||
Retrieving power data...
|
||||
</div>
|
||||
<div id="power_date_setter"></div>
|
||||
<table rt-if="this.props.view === 'table'" class="table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th></th>
|
||||
<th>Time</th>
|
||||
<th>Consumption (W)</th>
|
||||
<th>Production (W)</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr rt-repeat="power_datum in this.props.house.power_data" key="{power_datum.scoped_id}">
|
||||
<td></td>
|
||||
<td>{power_datum.time_to_s}</td>
|
||||
<td>{power_datum.consumption_to_s}</td>
|
||||
<td>{power_datum.production_to_s}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<div rt-if="this.props.view === 'graph'" id="power_graph"></div>
|
||||
{this.props.children}
|
||||
</div>
|
||||
|
||||
@@ -2,26 +2,17 @@ import React from 'react';
|
||||
import _ from 'lodash';
|
||||
function repeatMonth1(month, monthIndex) {
|
||||
return React.createElement('button', {
|
||||
'data-param': 'month',
|
||||
'data-value': month,
|
||||
'key': 'data-month-' + month,
|
||||
'className': 'btn-warning btn btn-sm' + ' ' + _.keys(_.pick({ active: month === this.props.house.current_month }, _.identity)).join(' '),
|
||||
'onClick': this.setMonth
|
||||
'className': 'btn-warning btn btn-sm' + ' ' + _.keys(_.pick({ active: month === this.context.house.state.month }, _.identity)).join(' '),
|
||||
'onClick': this.setParam.bind(this)
|
||||
}, month);
|
||||
}
|
||||
function repeatPower_datum2(power_datum, power_datumIndex) {
|
||||
return React.createElement('tr', {
|
||||
'className': 'fuck-you',
|
||||
'key': power_datum.scoped_id
|
||||
}, React.createElement('td', {}, power_datum.data.id), React.createElement('td', {}, power_datum.time_to_s), React.createElement('td', {}, power_datum.consumption_to_s), React.createElement('td', {}, power_datum.production_to_s));
|
||||
}
|
||||
export default function () {
|
||||
return React.createElement('div', { 'id': 'power_view' }, React.createElement.apply(this, [
|
||||
'div',
|
||||
{ 'className': 'btn-group' },
|
||||
this.props.house ? _.map(this.props.house.availableMonths(), repeatMonth1.bind(this)) : null
|
||||
]), this.state.loading_data ? React.createElement('div', { 'className': 'alert alert-warning' }, '\n Retrieving power data for the ', this.props.house.name, ' household...\n ') : null, React.createElement('div', { 'id': 'power_date_setter' }), this.props.view === 'table' ? React.createElement('table', { 'className': 'table' }, React.createElement('thead', {}, React.createElement('tr', {}, React.createElement('th', {}), React.createElement('th', {}, 'Time'), React.createElement('th', {}, 'Consumption (W)'), React.createElement('th', {}, 'Production (W)'))), React.createElement.apply(this, [
|
||||
'tbody',
|
||||
{},
|
||||
_.map(this.props.house.power_data, repeatPower_datum2.bind(this))
|
||||
])) : null, this.props.view === 'graph' ? React.createElement('div', { 'id': 'power_graph' }) : null);
|
||||
this.context.house ? _.map(this.context.house.availableMonths(), repeatMonth1.bind(this)) : null
|
||||
]), this.state.loading_power_data ? React.createElement('div', { 'className': 'alert alert-warning' }, '\n Retrieving power data...\n ') : null, React.createElement('div', { 'id': 'power_date_setter' }), '\n ', this.props.children, '\n');
|
||||
};
|
||||
19
client/dashboard/power/table/table.component.js
Normal file
19
client/dashboard/power/table/table.component.js
Normal file
@@ -0,0 +1,19 @@
|
||||
import React from 'react';
|
||||
import Templates from 'config/templates';
|
||||
|
||||
import House from './../../../models/house';
|
||||
|
||||
class TableComponent extends React.Component {
|
||||
|
||||
render() {
|
||||
var powerTableRt = Templates.forComponent('power_table');
|
||||
return powerTableRt.call(this);
|
||||
}
|
||||
}
|
||||
|
||||
TableComponent.contextTypes = {
|
||||
house: React.PropTypes.instanceOf(House),
|
||||
router: React.PropTypes.object.isRequired
|
||||
};
|
||||
|
||||
export default TableComponent;
|
||||
18
client/dashboard/power/table/table.rt
Normal file
18
client/dashboard/power/table/table.rt
Normal file
@@ -0,0 +1,18 @@
|
||||
<table class="table" rt-if="this.context.house">
|
||||
<thead>
|
||||
<tr>
|
||||
<th></th>
|
||||
<th>Time</th>
|
||||
<th>Consumption (W)</th>
|
||||
<th>Production (W)</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr rt-repeat="power_datum in this.context.house.power_data" key="{power_datum.scoped_id}">
|
||||
<td></td>
|
||||
<td>{power_datum.time_to_s}</td>
|
||||
<td>{power_datum.consumption_to_s}</td>
|
||||
<td>{power_datum.production_to_s}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
12
client/dashboard/power/table/table.rt.js
Normal file
12
client/dashboard/power/table/table.rt.js
Normal file
@@ -0,0 +1,12 @@
|
||||
import React from 'react';
|
||||
import _ from 'lodash';
|
||||
function repeatPower_datum1(power_datum, power_datumIndex) {
|
||||
return React.createElement('tr', { 'key': power_datum.scoped_id }, React.createElement('td', {}), React.createElement('td', {}, power_datum.time_to_s), React.createElement('td', {}, power_datum.consumption_to_s), React.createElement('td', {}, power_datum.production_to_s));
|
||||
}
|
||||
export default function () {
|
||||
return this.context.house ? React.createElement('table', { 'className': 'table' }, React.createElement('thead', {}, React.createElement('tr', {}, React.createElement('th', {}), React.createElement('th', {}, 'Time'), React.createElement('th', {}, 'Consumption (W)'), React.createElement('th', {}, 'Production (W)'))), React.createElement.apply(this, [
|
||||
'tbody',
|
||||
{},
|
||||
_.map(this.context.house.power_data, repeatPower_datum1.bind(this))
|
||||
])) : null;
|
||||
};
|
||||
166
client/dashboard/routes.js
Normal file
166
client/dashboard/routes.js
Normal file
@@ -0,0 +1,166 @@
|
||||
import House from './house/house.component'
|
||||
import Power from './power/power.component';
|
||||
import PowerGraph from './power/graph/graph.component';
|
||||
import PowerTable from './power/table/table.component';
|
||||
import Energy from './energy/energy.component';
|
||||
import EnergyGraph from './energy/graph/graph.component';
|
||||
import EnergyTable from './energy/table/table.component';
|
||||
import About from './about/about.component';
|
||||
import Layout from './layout/layout.component';
|
||||
import ArrayUtil from './../../shared/utils/array';
|
||||
|
||||
const POWER_ROUTES = {
|
||||
path: 'power',
|
||||
component: Power,
|
||||
childRoutes: [
|
||||
{path: ':month/:year', component: PowerGraph},
|
||||
{path: ':month/:year/graph', component: PowerGraph},
|
||||
{path: ':month/:year/table', component: PowerTable}
|
||||
]
|
||||
};
|
||||
|
||||
const ENERGY_ROUTES = {
|
||||
path: 'energy',
|
||||
component: Energy,
|
||||
childRoutes: [
|
||||
{path: ':year/:graph_attr', component: EnergyGraph},
|
||||
{path: ':year/:graph_attr/graph', component: EnergyGraph},
|
||||
{path: ':year/:graph_attr/table', component: EnergyTable}
|
||||
]
|
||||
};
|
||||
|
||||
export const ROUTES = [{
|
||||
path: '/',
|
||||
component: Layout,
|
||||
indexRoute: { component: About },
|
||||
childRoutes: [{
|
||||
path: 'houses/:house_id',
|
||||
component: House,
|
||||
childRoutes: [ENERGY_ROUTES, POWER_ROUTES]
|
||||
}]
|
||||
}];
|
||||
|
||||
export class RouteHelper {
|
||||
|
||||
constructor(house, props, update){
|
||||
var route_helper = this;
|
||||
route_helper.house = house;
|
||||
route_helper.props = props;
|
||||
route_helper.update = update || {};
|
||||
}
|
||||
|
||||
get view(){
|
||||
var route_helper = this;
|
||||
return route_helper.update.view || (route_helper.tableSelected() ? 'table' : 'graph');
|
||||
}
|
||||
|
||||
get graph_attr(){
|
||||
var route_helper = this;
|
||||
return route_helper.update.graph_attr || route_helper.props.params.graph_attr || 'consumption';
|
||||
}
|
||||
|
||||
get dataset(){
|
||||
var route_helper = this;
|
||||
return route_helper.update.dataset || (route_helper.energySelected() ? 'energy' : 'power');
|
||||
}
|
||||
|
||||
get power_range(){
|
||||
var route_helper = this;
|
||||
return route_helper.update.power_range || route_helper.props.location.query['dates[]'];
|
||||
}
|
||||
|
||||
get date_params(){
|
||||
var route_helper = this;
|
||||
return {
|
||||
month: route_helper.update.month || route_helper.house.month,
|
||||
year: route_helper.update.year || route_helper.house.year };
|
||||
}
|
||||
|
||||
routeUpdated(){
|
||||
var route_helper = this,
|
||||
house = route_helper.house;
|
||||
return (route_helper.energySelected() && !house.matchesYearState(route_helper.date_params)) ||
|
||||
(route_helper.powerSelected() && !house.matchesMonthState(route_helper.date_params) || !house.matchesPowerRange(route_helper.power_range));
|
||||
}
|
||||
|
||||
// This will update the house state acccording to passed update parameters.
|
||||
updateHouseState(){
|
||||
var route_helper = this,
|
||||
house = route_helper.house;
|
||||
house.setMonthState(route_helper.date_params, route_helper.update.power_range);
|
||||
}
|
||||
|
||||
paramsHaveDateState(){
|
||||
var route_helper = this;
|
||||
return !!route_helper.props.params.year;
|
||||
}
|
||||
|
||||
// This will update the house according to URL parameters.
|
||||
updateHouseToParams(){
|
||||
var route_helper = this,
|
||||
house = route_helper.house,
|
||||
power_range;
|
||||
if (route_helper.props.location.query['dates[]']){
|
||||
power_range = [];
|
||||
power_range[0] = +route_helper.props.location.query['dates[]'][0];
|
||||
power_range[1] = +route_helper.props.location.query['dates[]'][1];
|
||||
}
|
||||
house.setMonthState(route_helper.props.params, power_range);
|
||||
}
|
||||
|
||||
// should be run AFTER updateHouseState is called.
|
||||
newRoute(){
|
||||
var route_helper = this,
|
||||
house = route_helper.house;
|
||||
if (route_helper.dataset === 'energy'){
|
||||
return `/houses/${house.data.id}/energy/${house.state.year}/${route_helper.graph_attr}/${route_helper.view}`;
|
||||
} else {
|
||||
return `/houses/${house.data.id}/power/${house.state.month}/${house.state.year}/${route_helper.view}?${jQuery.param({dates: house.state.power_range})}`;
|
||||
}
|
||||
}
|
||||
|
||||
graphSelected(){
|
||||
return RouteHelper.graphSelected(this.props.routes);
|
||||
}
|
||||
|
||||
static graphSelected(routes){
|
||||
if (RouteHelper.energySelected(routes)){
|
||||
return ArrayUtil.any(routes, (route)=>{ return route.component === EnergyGraph; });
|
||||
} else if (RouteHelper.powerSelected(routes)){
|
||||
return ArrayUtil.any(routes, (route)=>{ return route.component === PowerGraph; });
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
tableSelected(){
|
||||
return RouteHelper.tableSelected(this.props.routes);
|
||||
}
|
||||
|
||||
static tableSelected(routes){
|
||||
if (RouteHelper.energySelected(routes)){
|
||||
return ArrayUtil.any(routes, (route)=>{ return route.component === EnergyTable; });
|
||||
} else if (RouteHelper.powerSelected(routes)){
|
||||
return ArrayUtil.any(routes, (route)=>{ return route.component === PowerTable; });
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
energySelected(){
|
||||
return RouteHelper.energySelected(this.props.routes);
|
||||
}
|
||||
|
||||
static energySelected(routes){
|
||||
return ArrayUtil.any(routes, (route)=>{ return route.component === Energy; });
|
||||
}
|
||||
|
||||
powerSelected(){
|
||||
return RouteHelper.powerSelected(this.props.routes);
|
||||
}
|
||||
|
||||
static powerSelected(routes){
|
||||
return ArrayUtil.any(routes, (route)=>{ return route.component === Power; });
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user