create irradiance composite graph
This commit is contained in:
@@ -1,9 +1,6 @@
|
||||
<rt-require dependency="./graph/graph.component" as="EnergyGraph"/>
|
||||
<rt-require dependency="./table/table.component" as="EnergyTable"/>
|
||||
<div id="energy_view">
|
||||
<div class="alert alert-warning" rt-if="this.loading_energy_data">
|
||||
Retrieving energy data...
|
||||
</div>
|
||||
<div rt-if="this.props.view === 'graph'">
|
||||
<h4>Select Data</h4>
|
||||
<div class="btn-group" role="group">
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import React from 'react';
|
||||
import c3 from 'c3';
|
||||
|
||||
import Templates from 'config/templates';
|
||||
import CalendarGridChart from './../../../d3/grid/calendar_grid';
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
<th></th>
|
||||
<th>Day</th>
|
||||
<th>Consumption (kWh)</th>
|
||||
<th>Daily Mean Irradiance (W/m<sup>2</sup>)</th>
|
||||
<th>Production (kWh)</th>
|
||||
</tr>
|
||||
</thead>
|
||||
@@ -11,8 +12,9 @@
|
||||
<tr rt-repeat="energy_datum in this.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>
|
||||
<td>{energy_datum.consumption}</td>
|
||||
<td>{energy_datum.irradiance}</td>
|
||||
<td>{energy_datum.production}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
152
client/dashboard/irradiance/graph/graph.component.js
Normal file
152
client/dashboard/irradiance/graph/graph.component.js
Normal file
@@ -0,0 +1,152 @@
|
||||
import React from 'react';
|
||||
import Templates from 'config/templates';
|
||||
import c3 from 'c3';
|
||||
|
||||
class GraphComponent extends React.Component {
|
||||
|
||||
constructor(props){
|
||||
super(props);
|
||||
}
|
||||
|
||||
get state_manager(){
|
||||
return this.props.state_manager;
|
||||
}
|
||||
|
||||
get houses(){
|
||||
return this.state_manager.houses;
|
||||
}
|
||||
|
||||
get chart_data(){
|
||||
var irradiance_graph = this;
|
||||
return Object.keys(irradiance_graph.state_manager.irradiance_data).map((day)=>{
|
||||
var day_data = irradiance_graph.state_manager.irradiance_data[day],
|
||||
day_datum = {date: day};
|
||||
day_data.forEach((energy_datum)=>{
|
||||
day_datum['irradiance'+energy_datum.house.data.id] = energy_datum.irradiance;
|
||||
day_datum['production'+energy_datum.house.data.id] = energy_datum.production;
|
||||
});
|
||||
return day_datum;
|
||||
}).filter((day_datum)=>{
|
||||
// due to timezone offsets, some houses might not have an energy_datum point,
|
||||
// where others do. Just filter those dates out to avoid UI confusion.
|
||||
return Object.keys(day_datum).length === irradiance_graph.value_keys.length;
|
||||
});
|
||||
}
|
||||
|
||||
get value_keys(){
|
||||
var irradiance_graph = this;
|
||||
return ['date'].concat(Object.keys(irradiance_graph.names));
|
||||
}
|
||||
|
||||
get irradiance_keys(){
|
||||
return this.houses.map((house)=>{
|
||||
return 'irradiance' + house.data.id;
|
||||
});
|
||||
}
|
||||
|
||||
get production_keys(){
|
||||
return this.houses.map((house)=>{
|
||||
return 'production' + house.data.id;
|
||||
});
|
||||
}
|
||||
|
||||
get colors(){
|
||||
var fnColor = d3.scale.category20(),
|
||||
irradiance_graph = this;
|
||||
return Object.keys(irradiance_graph.names).reduce((colors, key)=>{
|
||||
colors[key] = fnColor(key);
|
||||
return colors;
|
||||
}, {});
|
||||
}
|
||||
|
||||
get names(){
|
||||
var names = {};
|
||||
this.houses.forEach((house)=>{
|
||||
names['irradiance' + house.data.id] = house.data.name + ' Irradiance';
|
||||
names['production' + house.data.id] = house.data.name + ' Production';
|
||||
});
|
||||
return names;
|
||||
}
|
||||
|
||||
get axes(){
|
||||
var irradiance_graph = this,
|
||||
axes = {};
|
||||
irradiance_graph.production_keys.forEach((production_key)=>{
|
||||
axes[production_key] = 'y';
|
||||
});
|
||||
irradiance_graph.irradiance_keys.forEach((irradiance_key)=>{
|
||||
axes[irradiance_key] = 'y2';
|
||||
});
|
||||
return axes;
|
||||
}
|
||||
|
||||
get types(){
|
||||
var irradiance_graph = this;
|
||||
return irradiance_graph.production_keys.reduce((types, production_key)=>{
|
||||
types[production_key] = 'bar';
|
||||
return types;
|
||||
}, {});
|
||||
}
|
||||
|
||||
componentDidMount(){
|
||||
var irradiance_graph = this;
|
||||
irradiance_graph.updateGraph();
|
||||
}
|
||||
|
||||
componentDidUpdate(prev_props, prev_state){
|
||||
var irradiance_graph = this;
|
||||
if (irradiance_graph.props.date_interval[0] != prev_props.date_interval[0] ||
|
||||
irradiance_graph.props.date_interval[1] != prev_props.date_interval[1]){
|
||||
irradiance_graph.updateGraph();
|
||||
}
|
||||
}
|
||||
|
||||
updateGraph(){
|
||||
var irradiance_graph = this,
|
||||
data = {
|
||||
json: irradiance_graph.chart_data,
|
||||
keys: {
|
||||
x: 'date', // it's possible to specify 'x' when category axis
|
||||
value: irradiance_graph.value_keys,
|
||||
},
|
||||
types: irradiance_graph.types,
|
||||
names: irradiance_graph.names,
|
||||
groups: [irradiance_graph.production_keys],
|
||||
axes: irradiance_graph.axes,
|
||||
colors: irradiance_graph.colors
|
||||
};
|
||||
if (!irradiance_graph.chart){
|
||||
irradiance_graph.chart = c3.generate({
|
||||
bindto: '#irradiance_graph',
|
||||
data: data,
|
||||
axis: {
|
||||
x: {
|
||||
type: 'timeseries',
|
||||
tick: { format: d3.time.format('%d %B %y') }
|
||||
},
|
||||
y: {
|
||||
label: 'Production'
|
||||
},
|
||||
y2: {
|
||||
show: true,
|
||||
label: 'Irradiance'
|
||||
}
|
||||
}
|
||||
});
|
||||
} else {
|
||||
console.log('reloading data')
|
||||
console.log(data)
|
||||
data.unload = irradiance_graph.chart.data;
|
||||
irradiance_graph.chart.load(data);
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
var irradianceGraphRt = Templates.forComponent('irradiance_graph');
|
||||
return irradianceGraphRt.call(this);
|
||||
}
|
||||
}
|
||||
GraphComponent.NAME = 'IrradianceGraph';
|
||||
|
||||
module.exports = GraphComponent;
|
||||
|
||||
1
client/dashboard/irradiance/graph/graph.rt
Normal file
1
client/dashboard/irradiance/graph/graph.rt
Normal file
@@ -0,0 +1 @@
|
||||
<div id="irradiance_graph"></div>
|
||||
18
client/dashboard/irradiance/irradiance.component.js
Normal file
18
client/dashboard/irradiance/irradiance.component.js
Normal file
@@ -0,0 +1,18 @@
|
||||
import React from 'react';
|
||||
import Templates from 'config/templates';
|
||||
|
||||
class IrradianceComponent extends React.Component {
|
||||
|
||||
get state_manager(){
|
||||
return this.props.state_manager;
|
||||
}
|
||||
|
||||
render() {
|
||||
var irradianceRt = Templates.forComponent('irradiance');
|
||||
return irradianceRt.call(this);
|
||||
}
|
||||
|
||||
}
|
||||
IrradianceComponent.NAME = 'Irradiance';
|
||||
|
||||
module.exports = IrradianceComponent;
|
||||
13
client/dashboard/irradiance/irradiance.rt
Normal file
13
client/dashboard/irradiance/irradiance.rt
Normal file
@@ -0,0 +1,13 @@
|
||||
<rt-require dependency="./graph/graph.component" as="IrradianceGraph"/>
|
||||
<rt-require dependency="./table/table.component" as="IrradianceTable"/>
|
||||
<div id="irradiance_view">
|
||||
<h4>Irradiance</h4>
|
||||
<IrradianceGraph
|
||||
rt-if="this.props.view === 'graph'"
|
||||
state_manager="{this.state_manager}"
|
||||
date_interval="{this.props.date_interval}" />
|
||||
<IrradianceTable
|
||||
rt-if="this.props.view === 'table'"
|
||||
state_manager="{this.state_manager}"
|
||||
date_interval="{this.props.date_interval}" />
|
||||
</div>
|
||||
3
client/dashboard/irradiance/irradiance.scss
Normal file
3
client/dashboard/irradiance/irradiance.scss
Normal file
@@ -0,0 +1,3 @@
|
||||
#irradiance_component {
|
||||
|
||||
}
|
||||
18
client/dashboard/irradiance/table/table.component.js
Normal file
18
client/dashboard/irradiance/table/table.component.js
Normal file
@@ -0,0 +1,18 @@
|
||||
import React from 'react';
|
||||
import Templates from 'config/templates';
|
||||
|
||||
class TableComponent extends React.Component {
|
||||
|
||||
get state_manager(){
|
||||
return this.props.state_manager;
|
||||
}
|
||||
|
||||
render() {
|
||||
var irradianceTableRt = Templates.forComponent('irradiance_table');
|
||||
return irradianceTableRt.call(this);
|
||||
}
|
||||
}
|
||||
|
||||
TableComponent.NAME = 'IrradianceTable';
|
||||
|
||||
module.exports = TableComponent;
|
||||
18
client/dashboard/irradiance/table/table.rt
Normal file
18
client/dashboard/irradiance/table/table.rt
Normal file
@@ -0,0 +1,18 @@
|
||||
<table id="irradiance_table" class="table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Day</th>
|
||||
<th>House</th>
|
||||
<th>Daily Mean Irradiance (W/m<sup>2</sup>)</th>
|
||||
<th>Production (kWh)</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody rt-repeat="day in Object.keys(this.state_manager.irradiance_data)" key="irradiance-date-{day}">
|
||||
<tr rt-repeat="energy_datum in this.state_manager.irradiance_data[day]" key="{energy_datum.scoped_id}">
|
||||
<td>{energy_datum.day_to_s}</td>
|
||||
<td>{energy_datum.house.data.name}</td>
|
||||
<td>{energy_datum.irradiance}</td>
|
||||
<td>{energy_datum.production}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
@@ -6,6 +6,7 @@ import Templates from 'config/templates';
|
||||
import House from './../../models/house';
|
||||
import PowerDatum from './../../models/power_datum';
|
||||
import StateManager from './../state_manager';
|
||||
import DateRangeSlider from './../../d3/sliders/date_range';
|
||||
|
||||
class LayoutComponent extends React.Component {
|
||||
|
||||
@@ -18,6 +19,8 @@ class LayoutComponent extends React.Component {
|
||||
house: null,
|
||||
dataset: null,
|
||||
year: null,
|
||||
month: null,
|
||||
date_interval: null,
|
||||
view: null
|
||||
}
|
||||
}
|
||||
@@ -50,9 +53,35 @@ class LayoutComponent extends React.Component {
|
||||
});
|
||||
}
|
||||
|
||||
componentDidUpdate(prev_props, prev_state){
|
||||
var layout = this;
|
||||
if (layout.shouldShowDateRange() && !layout.datesMatch(prev_state)){
|
||||
layout.updateDateRange();
|
||||
} else if (!layout.shouldShowDateRange()){
|
||||
layout.destroyDateRange();
|
||||
}
|
||||
}
|
||||
|
||||
datesMatch(prev_state){
|
||||
var layout = this;
|
||||
return layout.state.month == prev_state.month &&
|
||||
layout.state.year == prev_state.year &&
|
||||
!layout.shouldShowDateRange() ||
|
||||
layout.state.date_interval && prev_state.date_interval &&
|
||||
layout.state.date_interval[0] == prev_state.date_interval[0] &&
|
||||
layout.state.date_interval[1] == prev_state.date_interval[1];
|
||||
}
|
||||
|
||||
shouldShowDateRange(){
|
||||
var layout = this;
|
||||
return layout.state.house && layout.state.dataset === 'power' || layout.state.dataset === 'irradiance';
|
||||
}
|
||||
|
||||
syncFromStateManager(fnStateSet){
|
||||
var layout = this;
|
||||
layout.setState(layout.state_manager.state, fnStateSet);
|
||||
layout.setState(layout.state_manager.state, ()=>{
|
||||
fnStateSet()
|
||||
});
|
||||
}
|
||||
|
||||
setHouse(event){
|
||||
@@ -72,6 +101,50 @@ class LayoutComponent extends React.Component {
|
||||
layout.state_manager.setParams(update, layout);
|
||||
}
|
||||
|
||||
destroyDateRange(){
|
||||
var layout = this,
|
||||
container = document.getElementById('date_interval');
|
||||
if (container) container.innerHTML = '';
|
||||
layout.date_interval_slider = undefined;
|
||||
}
|
||||
|
||||
updateDateRange(){
|
||||
var layout = this,
|
||||
house = layout.house,
|
||||
state_manager = layout.state_manager;
|
||||
if (layout.date_interval_slider === undefined){
|
||||
layout.date_interval_slider = new DateRangeSlider({
|
||||
container: '#date_interval',
|
||||
outer_height: 100,
|
||||
maxDelta: function(changed_date, other_date){
|
||||
if (Math.abs(changed_date.getTime() - other_date.getTime()) > House.MAX_POWER_RANGE * 1000){
|
||||
if (changed_date > other_date){
|
||||
return new Date(changed_date.getTime() - House.MAX_POWER_RANGE * 1000);
|
||||
} else {
|
||||
return new Date(changed_date.getTime() + House.MAX_POWER_RANGE * 1000);
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
});
|
||||
}
|
||||
layout.date_interval_slider.onRangeUpdated = (min, max)=>{
|
||||
if (layout.date_interval_update) clearTimeout(layout.date_interval_update);
|
||||
// This will update the URL -> state_manager.state -> component states.
|
||||
layout.date_interval_update = setTimeout(()=>{
|
||||
var date_interval = [Math.round(min.getTime() / 1000), Math.round(max.getTime() / 1000)];
|
||||
layout.state_manager.setParams({date_interval: date_interval}, layout);
|
||||
}, 500);
|
||||
};
|
||||
var month_range = state_manager.month_range;
|
||||
layout.date_interval_slider.drawData({
|
||||
abs_min: house.toDate(month_range[0]),
|
||||
abs_max: house.toDate(month_range[1]),
|
||||
current_min: house.toDate(state_manager.state.date_interval[0]),
|
||||
current_max: house.toDate(state_manager.state.date_interval[1])
|
||||
});
|
||||
}
|
||||
|
||||
refreshData(){
|
||||
var layout = this,
|
||||
houses = layout.state.houses,
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
<rt-require dependency="./../energy/energy.component" as="Energy"/>
|
||||
<rt-require dependency="./../irradiance/irradiance.component" as="Irradiance"/>
|
||||
<rt-require dependency="./../power/power.component" as="Power"/>
|
||||
<div id="layout">
|
||||
<div rt-if="!this.house" id="about">
|
||||
@@ -9,7 +10,7 @@
|
||||
<ul>
|
||||
<li>React</li>
|
||||
<li>React Templates</li>
|
||||
<li>React Router</li>
|
||||
<li>ReactJs History</li>
|
||||
<li>LokiJs - persisting API calls to indexedDb</li>
|
||||
<li>Webpack - hot mode developing and app bundling</li>
|
||||
<li>Babel - ES6 transpiler</li>
|
||||
@@ -22,15 +23,23 @@
|
||||
|
||||
<div class="alert alert-warning" rt-if="this.state.loading_houses">Retrieving houses...</div>
|
||||
|
||||
<h4>Select household:</h4>
|
||||
<select id="houses_select" rt-if="this.state.houses && this.state_manager" class="form-control" onChange="{this.setHouse.bind(this)}" value="{this.house_id}">
|
||||
<option rt-repeat="house in this.state.houses" value="{house.data.id}" key="{house.scoped_id}">{house.data.name}</option>
|
||||
</select>
|
||||
<div rt-if="this.dataset !== 'irradiance'">
|
||||
<h4>Select household:</h4>
|
||||
<select id="houses_select" rt-if="this.state.houses && this.state_manager" class="form-control" onChange="{this.setHouse.bind(this)}" value="{this.house_id}">
|
||||
<option rt-repeat="house in this.state.houses" value="{house.data.id}" key="{house.scoped_id}">{house.data.name}</option>
|
||||
</select>
|
||||
</div>
|
||||
<button rt-if="this.house" onClick="{this.refreshData.bind(this)}" class="btn btn-xs btn-default">Refresh House Data</button>
|
||||
|
||||
<div rt-if="this.house">
|
||||
<div>
|
||||
<h4>Select dataset:</h4>
|
||||
<div class="btn-group" role="group">
|
||||
<button
|
||||
data-param="dataset"
|
||||
data-value="power"
|
||||
rt-class="{active: this.state.dataset === 'power'}"
|
||||
onClick="{this.setParam.bind(this)}"
|
||||
type="button" class="btn btn-primary">15-minute Power Statistics</button>
|
||||
<button
|
||||
data-param="dataset"
|
||||
data-value="energy"
|
||||
@@ -39,10 +48,10 @@
|
||||
type="button" class="btn btn-primary">Daily Energy Statistics</button>
|
||||
<button
|
||||
data-param="dataset"
|
||||
data-value="power"
|
||||
rt-class="{active: this.state.dataset === 'power'}"
|
||||
data-value="irradiance"
|
||||
rt-class="{active: this.state.dataset === 'irradiance'}"
|
||||
onClick="{this.setParam.bind(this)}"
|
||||
type="button" class="btn btn-primary">15-minute Power Statistics</button>
|
||||
type="button" class="btn btn-primary">Daily Mean Irradiance</button>
|
||||
</div>
|
||||
|
||||
<h4>View as:</h4>
|
||||
@@ -72,26 +81,44 @@
|
||||
class="btn-info btn btn-sm"
|
||||
rt-class="{active: year == this.state.year}"
|
||||
onClick="{this.setParam.bind(this)}">{year}</button>
|
||||
</div><br/>
|
||||
<div class="btn-group" rt-if="this.state.dataset === 'power' || this.state.dataset === 'irradiance'">
|
||||
<button
|
||||
rt-repeat="month in this.house.availableMonths(this.state.year)"
|
||||
data-param="month"
|
||||
data-value="{month}"
|
||||
key="data-month-{month}"
|
||||
class="btn-warning btn btn-sm"
|
||||
rt-class="{active: month === this.state.month}"
|
||||
onClick="{this.setParam.bind(this)}">{month}</button>
|
||||
</div><br/>
|
||||
<div id="date_interval"></div>
|
||||
|
||||
<div class="alert alert-warning" rt-if="this.state.loading_data">
|
||||
Retrieving {this.state.loading_data} data...
|
||||
</div>
|
||||
</div><br/>
|
||||
|
||||
<Energy
|
||||
rt-if="this.should_show_energy_data"
|
||||
house="{this.state.house}"
|
||||
loading_energy_data="{this.state.loading_energy_data}"
|
||||
state_manager="{this.state_manager}"
|
||||
view="{this.state.view}"
|
||||
graph_attr="{this.state.graph_attr}"
|
||||
year="{this.state.year}" />
|
||||
<Irradiance
|
||||
rt-if="this.state.dataset === 'irradiance'"
|
||||
view="{this.state.view}"
|
||||
date_interval="{this.state.date_interval}"
|
||||
state_manager="{this.state_manager}" />
|
||||
<Power
|
||||
rt-if="this.should_show_power_data"
|
||||
house="{this.state.house}"
|
||||
loading_power_data="{this.state.loading_power_data}"
|
||||
state_manager="{this.state_manager}"
|
||||
view="{this.state.view}"
|
||||
month="{this.state.month}"
|
||||
year="{this.state.year}"
|
||||
power_range="{this.state.power_range}" />
|
||||
date_interval="{this.state.date_interval}" />
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import React from 'react';
|
||||
import Templates from 'config/templates';
|
||||
import c3 from 'c3';
|
||||
|
||||
import House from './../../../models/house';
|
||||
import c3 from 'c3';
|
||||
|
||||
class GraphComponent extends React.Component {
|
||||
|
||||
@@ -21,7 +21,7 @@ class GraphComponent extends React.Component {
|
||||
|
||||
componentDidUpdate(prev_props, prev_state){
|
||||
var power_graph = this;
|
||||
if (prev_props.house != power_graph.props.house || prev_props.power_range != power_graph.props.power_range){
|
||||
if (prev_props.house != power_graph.props.house || prev_props.date_interval != power_graph.props.date_interval){
|
||||
power_graph.updateGraph();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,7 +4,6 @@ import _ from 'lodash';
|
||||
|
||||
import Templates from 'config/templates';
|
||||
import House from './../../models/house';
|
||||
import DateRangeSlider from './../../d3/sliders/date_range';
|
||||
|
||||
class PowerComponent extends React.Component {
|
||||
|
||||
@@ -25,75 +24,11 @@ class PowerComponent extends React.Component {
|
||||
return this.props.state_manager;
|
||||
}
|
||||
|
||||
get loading_power_data(){
|
||||
return this.props.loading_power_data || this.state.loading_power_data;
|
||||
}
|
||||
|
||||
componentDidMount(){
|
||||
var power = this;
|
||||
power.initDateRange();
|
||||
}
|
||||
|
||||
componentDidUpdate(prev_props, prev_state){
|
||||
var power = this,
|
||||
state_manager = power.state_manager;
|
||||
if (prev_props.power_range[0] != power.props.power_range[0] ||
|
||||
prev_props.power_range[1] != power.props.power_range[1] ||
|
||||
prev_props.house != power.props.house){
|
||||
power.initDateRange();
|
||||
state_manager.powerDataRendered();
|
||||
}
|
||||
}
|
||||
|
||||
syncFromStateManager(fnStateSet){
|
||||
var power = this;
|
||||
power.setState(power.state_manager.state, fnStateSet);
|
||||
}
|
||||
|
||||
initDateRange(){
|
||||
var power = this,
|
||||
house = power.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)];
|
||||
power.state_manager.setParams({power_range: power_range}, power);
|
||||
}, 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,
|
||||
param = event.target.dataset.param,
|
||||
value = event.target.dataset.value,
|
||||
update = {}, route_helper;
|
||||
update[param] = value;
|
||||
if (value == power.state_manager.state[param]) return false;
|
||||
power.state_manager.setParams(update, power);
|
||||
}
|
||||
|
||||
render() {
|
||||
var powerRt = Templates.forComponent('power');
|
||||
return powerRt.call(this);
|
||||
|
||||
@@ -1,33 +1,18 @@
|
||||
<rt-require dependency="./graph/graph.component" as="PowerGraph"/>
|
||||
<rt-require dependency="./table/table.component" as="PowerTable"/>
|
||||
<div id="power_view">
|
||||
<div class="btn-group">
|
||||
<button
|
||||
rt-if="this.house"
|
||||
rt-repeat="month in this.house.availableMonths()"
|
||||
data-param="month"
|
||||
data-value="{month}"
|
||||
key="data-month-{month}"
|
||||
class="btn-warning btn btn-sm"
|
||||
rt-class="{active: month === this.house.state.month}"
|
||||
onClick="{this.setParam.bind(this)}">{month}</button>
|
||||
</div>
|
||||
<div class="alert alert-warning" rt-if="this.loading_power_data">
|
||||
Retrieving power data...
|
||||
</div>
|
||||
<div id="power_date_setter"></div>
|
||||
<PowerGraph
|
||||
rt-if="this.props.view === 'graph'"
|
||||
state_manager="{this.props.state_manager}"
|
||||
house="{this.props.house}"
|
||||
month="{this.props.month}"
|
||||
year="{this.props.year}"
|
||||
power_range="{this.props.power_range}" ></PowerGraph>
|
||||
date_interval="{this.props.date_interval}" ></PowerGraph>
|
||||
<PowerTable
|
||||
rt-if="this.props.view === 'table'"
|
||||
state_manager="{this.props.state_manager}"
|
||||
house="{this.props.house}"
|
||||
month="{this.props.month}"
|
||||
year="{this.props.year}"
|
||||
power_range="{this.props.power_range}" ></PowerTable>
|
||||
date_interval="{this.props.date_interval}" ></PowerTable>
|
||||
</div>
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
import query_string from 'query-string';
|
||||
import moment from 'moment-timezone';
|
||||
|
||||
import EnergyDatum from './../models/energy_datum';
|
||||
import ObjectUtil from './../../shared/utils/object';
|
||||
import ArrayUtil from './../../shared/utils/array';
|
||||
|
||||
@@ -13,6 +15,9 @@ const ROUTES = [
|
||||
}, {
|
||||
path: /houses\/(\d+)\/(power)\/([^\/]+)\/(\d+)\/([^\/]+)\/?$/,
|
||||
parameters: { 1: 'house_id', 2: 'dataset', 3: 'month', 4: 'year', 5: 'view' }
|
||||
}, {
|
||||
path: /(irradiance)\/([^\/]+)\/(\d+)\/([^\/]+)\/?$/,
|
||||
parameters: { 1: 'dataset', 2: 'month', 3: 'year', 4: 'view' }
|
||||
}
|
||||
];
|
||||
|
||||
@@ -24,8 +29,7 @@ class StateManager {
|
||||
state_manager.houses = houses;
|
||||
|
||||
state_manager.state = {
|
||||
loading_energy_data: false,
|
||||
loading_power_data: false,
|
||||
loading_data: false,
|
||||
graph_attr: 'consumption',
|
||||
view: 'graph',
|
||||
dataset: 'power',
|
||||
@@ -33,55 +37,94 @@ class StateManager {
|
||||
house: null,
|
||||
month: null,
|
||||
year: null,
|
||||
power_range: null };
|
||||
date_interval: null };
|
||||
|
||||
state_manager.history = createHistory();
|
||||
state_manager.update_in_progress = false;
|
||||
}
|
||||
|
||||
get month_i(){
|
||||
return moment.monthsShort().indexOf(this.state.month);
|
||||
}
|
||||
|
||||
get date_params(){
|
||||
return ObjectUtil.filterKeys(this.state, ['year', 'month', 'power_range']);
|
||||
return ObjectUtil.filterKeys(this.state, ['year', 'month', 'date_interval']);
|
||||
}
|
||||
|
||||
get month_range(){
|
||||
var state_manager = this,
|
||||
house = state_manager.state.house,
|
||||
start_time = house.parseMoment(`${state_manager.state.year}-${state_manager.month_i + 1}-01`, 'YYYY-M-DD'),
|
||||
end_time = start_time.clone().endOf('month').unix();
|
||||
|
||||
start_time = start_time.unix();
|
||||
if (start_time < house.data.data_from) start_time = house.data.data_from;
|
||||
if (end_time > house.data.data_until) end_time = house.data.data_until;
|
||||
return [start_time, end_time];
|
||||
}
|
||||
|
||||
get year_range(){
|
||||
var state_manager = this,
|
||||
house = state_manager.state.house,
|
||||
start_time = house.parseMoment(`${state_manager.state.year}-01-01`, 'YYYY-MM-DD'),
|
||||
end_time = start_time.clone().endOf('year').unix();
|
||||
|
||||
start_time = start_time.unix();
|
||||
if (start_time < house.data.data_from) start_time = house.data.data_from;
|
||||
if (end_time > house.data.data_until) end_time = house.data.data_until;
|
||||
return [start_time, end_time];
|
||||
}
|
||||
|
||||
matchesEnergyState(){
|
||||
var state_manager = this,
|
||||
house = state_manager.state.house,
|
||||
energy_range = state_manager.state.graph_attr === 'irradiance' ? state_manager.state.date_interval : state_manager.year_range;
|
||||
if (!house.state.energy_range) return false;
|
||||
return energy_range[0] === house.state.energy_range[0] && energy_range[1] === house.state.energy_range[1];
|
||||
}
|
||||
|
||||
matchesPowerState(){
|
||||
var state_manager = this,
|
||||
house = state_manager.state.house,
|
||||
month_range = state_manager.month_range;
|
||||
if (!house.state.power_range) return false;
|
||||
return month_range[0] === house.state.power_range[0] && month_range[1] === house.state.power_range[1];
|
||||
}
|
||||
|
||||
// This will update the house state acccording to passed update parameters.
|
||||
updateHouseFromState(component, fnResolve){
|
||||
updateHouseFromState(component){
|
||||
var state_manager = this,
|
||||
house = state_manager.state.house,
|
||||
promise;
|
||||
if (!house) {
|
||||
promise = Promise.resolve();
|
||||
} else if (state_manager.state.dataset === 'energy' && (!house.energy_data || !house.matchesEnergyState(state_manager.state))){
|
||||
house.setMonthState(state_manager.state);
|
||||
} else if (state_manager.state.dataset === 'energy' && !state_manager.matchesEnergyState()){
|
||||
promise = state_manager.setHouseEnergyFromState(component);
|
||||
} else if (state_manager.state.dataset === 'power' && !house.power_data || !house.matchesPowerState(state_manager.state)){
|
||||
house.setMonthState(state_manager.state);
|
||||
} else if (state_manager.state.dataset === 'power' && !state_manager.matchesPowerState()){
|
||||
promise = state_manager.setHousePowerFromState(component);
|
||||
} else if (state_manager.state.dataset === 'irradiance'){
|
||||
promise = state_manager.setIrradianceData(component);
|
||||
} else {
|
||||
promise = new Promise((fnResolve, fnReject)=>{
|
||||
component.syncFromStateManager(fnResolve);
|
||||
});
|
||||
promise = Promise.resolve();
|
||||
}
|
||||
return promise.then(()=>{ state_manager.update_in_progress = false; })
|
||||
}
|
||||
|
||||
setHouseEnergyFromState(component){
|
||||
var state_manager = this;
|
||||
state_manager.power_data_updated = true;
|
||||
return new Promise((fnResolve, fnReject)=>{
|
||||
component.setState({
|
||||
loading_energy_data: true
|
||||
}, ()=>{
|
||||
state_manager.state.house.setEnergyData()
|
||||
.then(()=>{
|
||||
component.syncFromStateManager(fnResolve);
|
||||
});
|
||||
return promise.then(()=>{
|
||||
state_manager.update_in_progress = false;
|
||||
return new Promise((fnResolve, fnReject)=>{
|
||||
component.syncFromStateManager(fnResolve);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
powerDataRendered(){
|
||||
setHouseEnergyFromState(component){
|
||||
var state_manager = this;
|
||||
state_manager.power_data_updated = false;
|
||||
return new Promise((fnResolve, fnReject)=>{
|
||||
component.setState({
|
||||
loading_data: 'power'
|
||||
}, ()=>{
|
||||
state_manager.state.house.setEnergyData(state_manager.year_range)
|
||||
.then(fnResolve);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
setHousePowerFromState(component){
|
||||
@@ -89,11 +132,49 @@ class StateManager {
|
||||
house = state_manager.state.house;
|
||||
return new Promise((fnResolve, fnReject)=>{
|
||||
component.setState({
|
||||
loading_power_data: true
|
||||
loading_data: 'energy'
|
||||
}, ()=>{
|
||||
house.setPowerData()
|
||||
.then(()=>{
|
||||
component.syncFromStateManager(fnResolve);
|
||||
house.setPowerData(state_manager.state.date_interval)
|
||||
.then(fnResolve);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
setIrradianceData(component){
|
||||
var state_manager = this,
|
||||
houses = state_manager.houses,
|
||||
date_interval = state_manager.state.date_interval;
|
||||
return new Promise((fnResolve, fnReject)=>{
|
||||
component.setState({
|
||||
loading_data: 'irradiance'
|
||||
}, ()=>{
|
||||
EnergyDatum.ensureEnergyDataForHouses(houses, date_interval)
|
||||
.then((res)=>{
|
||||
if (res instanceof Promise){
|
||||
throw new Error('promise returned promise')
|
||||
}
|
||||
var promises = [],
|
||||
data = {};
|
||||
houses.forEach((house)=>{
|
||||
var promise = house.setEnergyData(date_interval)
|
||||
.then(()=>{
|
||||
house.energy_data.forEach((energy_datum)=>{
|
||||
var date_data = data[energy_datum.day_to_s];
|
||||
if (!date_data){
|
||||
date_data = [];
|
||||
data[energy_datum.day_to_s] = date_data;
|
||||
}
|
||||
date_data.push(energy_datum);
|
||||
});
|
||||
house.closeDb();
|
||||
});
|
||||
promises.push(promise);
|
||||
});
|
||||
Promise.all(promises)
|
||||
.then(()=>{
|
||||
state_manager.irradiance_data = data;
|
||||
fnResolve();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -105,25 +186,29 @@ class StateManager {
|
||||
|
||||
setParams(params){
|
||||
var state_manager = this,
|
||||
url;
|
||||
url, house, params;
|
||||
if (state_manager.update_in_progress) return false;
|
||||
state_manager.update_in_progress = true;
|
||||
params = Object.assign({}, state_manager.state, params);
|
||||
if (!params.house_id){
|
||||
url = '/';
|
||||
} else {
|
||||
var house = state_manager.houses.find((h)=>{ return h.data.id == params.house_id; })
|
||||
|
||||
house.verifyMonthState(params);
|
||||
if (params.dataset === 'energy'){
|
||||
url = `/houses/${params.house_id}/energy/${params.year}/${params.graph_attr}/${params.view}`;
|
||||
} else if (params.dataset === 'power'){
|
||||
house.verifyPowerRange(params);
|
||||
url = `/houses/${params.house_id}/power/${params.month}/${params.year}/${params.view}?${query_string.stringify({dates: params.power_range})}`;
|
||||
} else {
|
||||
url = `/houses/${house.house_id}`;
|
||||
}
|
||||
params = Object.assign({}, state_manager.state, params);
|
||||
if (params.house_id){
|
||||
house = state_manager.houses.find((h)=>{ return h.data.id == params.house_id; });
|
||||
} else {
|
||||
house = state_manager.state.house || state_manager.houses[0];
|
||||
params.house_id = house.data.id;
|
||||
}
|
||||
|
||||
house.verifyMonthState(params);
|
||||
if (params.dataset === 'irradiance'){
|
||||
params.date_interval = house.verifyPowerRange(params.date_interval || [], params);
|
||||
url = `/irradiance/${params.month}/${params.year}/${params.view}?${query_string.stringify({dates: params.date_interval})}`;
|
||||
} else if (params.dataset === 'energy'){
|
||||
url = `/houses/${params.house_id}/energy/${params.year}/${params.graph_attr}/${params.view}`;
|
||||
} else {
|
||||
params.date_interval = house.verifyPowerRange(params.date_interval || [], params);
|
||||
url = `/houses/${params.house_id}/power/${params.month}/${params.year}/${params.view}?${query_string.stringify({dates: params.date_interval})}`;
|
||||
}
|
||||
|
||||
state_manager.history.push(url);
|
||||
}
|
||||
|
||||
@@ -132,16 +217,28 @@ class StateManager {
|
||||
*/
|
||||
|
||||
updateStateFromUrl(location, component){
|
||||
var state_manager = this;
|
||||
var params = state_manager.parseUrl(location.pathname),
|
||||
var state_manager = this,
|
||||
params = state_manager.parseUrl(location.pathname),
|
||||
house = null;
|
||||
if (params.dataset === 'power' && location.query.dates) {
|
||||
params.power_range = [+location.query.dates[0], +location.query.dates[1]];
|
||||
}
|
||||
if (params.house_id || params.house_id != state_manager.state.house_id){
|
||||
if (params.house_id){
|
||||
house = state_manager.houses.find((h)=>{ return h.data.id == params.house_id; });
|
||||
} else if (params.dataset === 'irradiance'){
|
||||
// Irradiance needs a house to verify params and
|
||||
house = state_manager.state.house || state_manager.houses[0];
|
||||
}
|
||||
state_manager.state.house = house;
|
||||
|
||||
if (house){
|
||||
// params should already be verified if set through StateManager#setParams, but
|
||||
// verify here again before setting state in case URL manually loaded.
|
||||
house.verifyMonthState(params);
|
||||
if (params.dataset === 'power' || params.dataset === 'irradiance') {
|
||||
var date_interval = location.query.dates || [];
|
||||
params.date_interval = house.verifyPowerRange([+date_interval[0], +date_interval[1]], params);
|
||||
}
|
||||
state_manager.state.house = house;
|
||||
state_manager.state.house_id = house.data.id;
|
||||
}
|
||||
|
||||
Object.assign(state_manager.state, params);
|
||||
if (state_manager.state.house_id) {
|
||||
state_manager.updateHouseFromState(component);
|
||||
|
||||
Reference in New Issue
Block a user