polish energy/power data view interactions
This commit is contained in:
@@ -2,19 +2,9 @@ import extend from 'extend';
|
||||
|
||||
const ENDPOINT = '/data/v1/energy';
|
||||
|
||||
// send all date parameters as unix timestamps;
|
||||
class EnergyDataApi {
|
||||
|
||||
static index(house, params){
|
||||
params = extend({
|
||||
}, params);
|
||||
if (params.dates){
|
||||
params.dates = params.dates.map((date_range)=>{
|
||||
if (date_range[0]) date_range[0] = date_range[0].unix();
|
||||
if (date_range[1]) date_range[1] = date_range[1].unix();
|
||||
return [date_range[0], date_range[1]];
|
||||
})
|
||||
}
|
||||
static index(params){
|
||||
return jQuery.ajax({
|
||||
url: ENDPOINT + '?' + jQuery.param(params),
|
||||
type: 'GET',
|
||||
|
||||
13
client/d3/grid/calendar_grid.js
vendored
13
client/d3/grid/calendar_grid.js
vendored
@@ -6,7 +6,7 @@ class CalendarGridChart extends Chart{
|
||||
|
||||
get chart_options(){
|
||||
var chart = this;
|
||||
return extend(Chart.DEFAULTS, {
|
||||
return extend(Object.assign({}, Chart.DEFAULTS), {
|
||||
margin: {top: 30, left: 150, bottom: 0, right: 0},
|
||||
grid_padding: 0.05,
|
||||
parse_date_format: '%Y-%m-%d',
|
||||
@@ -103,6 +103,7 @@ class CalendarGridChart extends Chart{
|
||||
|
||||
drawData(data){
|
||||
var grid_chart = this;
|
||||
grid_chart.i = grid_chart.i || 1;
|
||||
data = grid_chart.serializeData(data);
|
||||
|
||||
// calibrate axes
|
||||
@@ -118,9 +119,9 @@ class CalendarGridChart extends Chart{
|
||||
|
||||
var grid_units = grid_chart.svg.selectAll(".d3-chart-grid-unit")
|
||||
.data(data.values);
|
||||
grid_chart.applyData(data, grid_units.enter().append("rect"));
|
||||
grid_chart.applyData(data, grid_units.transition());
|
||||
grid_units.exit().remove();
|
||||
grid_chart.applyData(data, grid_units.enter().append("rect"));
|
||||
grid_chart.applyData(data, grid_units);
|
||||
}
|
||||
|
||||
// helper method for drawData.
|
||||
@@ -128,7 +129,7 @@ class CalendarGridChart extends Chart{
|
||||
var grid_chart = this,
|
||||
series_class = "d3-chart-grid-unit " + data.css_class;
|
||||
elements
|
||||
.attr("class", function(d){ return series_class; })
|
||||
.attr("class", series_class)
|
||||
.attr("y", function(d) {
|
||||
var bottom = grid_chart.y_scale(grid_chart.toMonthString(d)),
|
||||
middle = grid_chart.y_scale.rangeBand() / 2 - grid_chart.grid_unit_size / 2;
|
||||
@@ -140,10 +141,10 @@ class CalendarGridChart extends Chart{
|
||||
})
|
||||
.attr("width", function(d) { return grid_chart.grid_unit_size; })
|
||||
.attr('fill', grid_chart.color)
|
||||
.attr("opacity", function(d) { return grid_chart.applyOpacity(grid_chart.rangeValue(d), data.range); });
|
||||
.attr("opacity", function(d) { return grid_chart.calculateOpacity(75, data.range); });
|
||||
}
|
||||
|
||||
applyOpacity(value, range){
|
||||
calculateOpacity(value, range){
|
||||
return Math.max(0, Math.min(1, 1 - (range.max - (value - range.min)) / range.diff));
|
||||
};
|
||||
|
||||
|
||||
10
client/d3/line/spline_stack.js
vendored
10
client/d3/line/spline_stack.js
vendored
@@ -6,7 +6,7 @@ const INTERPOLATION = 'cardinal';
|
||||
class SplineStackChart extends LineChart {
|
||||
|
||||
get chart_options(){
|
||||
return Object.assign(LineChart.DEFAULTS, {
|
||||
return Object.assign(Object.assign({}, LineChart.DEFAULTS), {
|
||||
interpolation: INTERPOLATION
|
||||
});
|
||||
}
|
||||
@@ -63,7 +63,7 @@ class SplineStackChart extends LineChart {
|
||||
|
||||
var stack = spline_stack.svg.selectAll(".d3-chart-spline-stack")
|
||||
.data(data.series);
|
||||
[stack.enter().append("path"), stack.transition()].forEach((paths)=>{
|
||||
[stack.enter().append("path"), stack].forEach((paths)=>{
|
||||
spline_stack.applyData(paths);
|
||||
});
|
||||
stack.exit().remove();
|
||||
@@ -72,7 +72,7 @@ class SplineStackChart extends LineChart {
|
||||
data.series.forEach((series)=>{
|
||||
var dots = spline_stack.svg.selectAll(".d3-chart-spline-dot." + series.css_class)
|
||||
.data(series.values);
|
||||
[dots.enter().append("circle"), dots.transition()].forEach((circles)=>{
|
||||
[dots.enter().append("circle"), dots].forEach((circles)=>{
|
||||
spline_stack.applyDots(series, circles);
|
||||
});
|
||||
dots.exit().remove();
|
||||
@@ -83,7 +83,7 @@ class SplineStackChart extends LineChart {
|
||||
applyData(paths){
|
||||
var spline_stack = this;
|
||||
paths
|
||||
.attr("class", function(series){"d3-chart-spline-stack " + series.css_class;})
|
||||
.attr("class", function(series){ return "d3-chart-spline-stack " + series.css_class;})
|
||||
.attr("d", function(series){ return spline_stack.fnArea(series.values); })
|
||||
.style("fill", function(series){ return spline_stack.fnColor(series.title); })
|
||||
.attr('opacity', 1);
|
||||
@@ -92,7 +92,7 @@ class SplineStackChart extends LineChart {
|
||||
applyDots(series, circles){
|
||||
var spline_stack = this;
|
||||
circles
|
||||
.attr('class', 'd3-chart-spline-dot' + series.css_class)
|
||||
.attr('class', 'd3-chart-spline-dot ' + series.css_class)
|
||||
.attr("r", 2)
|
||||
.attr("cx", function(d, i){ return spline_stack.x_scale(d.x); })
|
||||
.attr("cy", function(d, i){ return spline_stack.y_scale(d.y + d.y0); })
|
||||
|
||||
@@ -28,9 +28,14 @@ var Energy = React.createClass({
|
||||
});
|
||||
},
|
||||
|
||||
componentWillUnmount: function(){
|
||||
var energy = this;
|
||||
energy.destroyGraph();
|
||||
},
|
||||
|
||||
componentWillReceiveProps: function(new_props){
|
||||
var energy = this;
|
||||
if (new_props.house !== energy.state.house){
|
||||
if (new_props.house !== energy.props.house){
|
||||
energy.setState({loading_data: true});
|
||||
new_props.house.setEnergyData().then(()=>{
|
||||
energy.setState({loading_data: false});
|
||||
@@ -44,6 +49,9 @@ var Energy = React.createClass({
|
||||
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){
|
||||
@@ -65,17 +73,17 @@ var Energy = React.createClass({
|
||||
energy.graph = new CalendarGridChart({
|
||||
container: '#energy_graph',
|
||||
outer_width: 800,
|
||||
outer_height: 200,
|
||||
outer_height: 300,
|
||||
date_attr: 'day',
|
||||
color: '#0404B4',
|
||||
toDate: (energy_datum)=>{ return energy_datum.data.day.toDate(); }
|
||||
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.data.day.toDate()),
|
||||
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}`;
|
||||
}
|
||||
@@ -103,6 +111,21 @@ var Energy = React.createClass({
|
||||
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() {
|
||||
return energyRt.call(this);
|
||||
}
|
||||
|
||||
@@ -24,16 +24,23 @@ var Layout = React.createClass({
|
||||
var layout = this;
|
||||
// window.addEventListener('resize', this.handleResize);
|
||||
House.ensureHouses().then((houses)=>{
|
||||
layout.setState({houses: houses, house: houses[0], requesting_data: false});
|
||||
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}, ()=>{
|
||||
house.closeDb();
|
||||
old_house.closeDb();
|
||||
});
|
||||
},
|
||||
|
||||
@@ -50,6 +57,16 @@ var Layout = React.createClass({
|
||||
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,
|
||||
|
||||
@@ -37,6 +37,24 @@
|
||||
type="button" class="btn btn-primary">Table</button>
|
||||
</div>
|
||||
|
||||
<Energy rt-if="this.state.house && this.state.dataset === 'energy'" house="{this.state.house}" view="{this.state.view}"></Energy>
|
||||
<Power rt-if="this.state.house && this.state.dataset === 'power'" house="{this.state.house}" view="{this.state.view}"></Power>
|
||||
<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>
|
||||
</div>
|
||||
|
||||
@@ -8,6 +8,14 @@ function repeatHouse1(house, houseIndex) {
|
||||
'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',
|
||||
@@ -45,11 +53,17 @@ export default function () {
|
||||
'className': _.keys(_.pick({ active: this.state.view === 'table' }, _.identity)).join(' ') + ' ' + 'btn btn-primary',
|
||||
'onClick': this.setView,
|
||||
'type': 'button'
|
||||
}, 'Table')), this.state.house && this.state.dataset === 'energy' ? React.createElement(Energy, {
|
||||
}, '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
|
||||
'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
|
||||
'view': this.state.view,
|
||||
'year': this.state.year
|
||||
}) : null);
|
||||
};
|
||||
@@ -34,15 +34,19 @@ var Power = React.createClass({
|
||||
});
|
||||
},
|
||||
|
||||
componentWillUnmount: function(){
|
||||
var power = this;
|
||||
power.destroyGraph();
|
||||
},
|
||||
|
||||
componentWillReceiveProps: function(new_props){
|
||||
var power = this;
|
||||
if (new_props.house !== power.state.house){
|
||||
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.initDateRange();
|
||||
power.initGraph();
|
||||
}
|
||||
});
|
||||
@@ -56,9 +60,13 @@ var Power = React.createClass({
|
||||
house = power.props.house;
|
||||
// view has changed from graph to table.
|
||||
if (prev_props.view !== 'graph' && power.props.view === 'graph'){
|
||||
power.initDateRange();
|
||||
power.initGraph();
|
||||
}
|
||||
if (prev_props.house !== house) power.initDateRange();
|
||||
var need_update = false;
|
||||
if (prev_props.year !== power.props.year){
|
||||
power.updateCurrentMonth();
|
||||
}
|
||||
},
|
||||
|
||||
initGraph: function(){
|
||||
@@ -75,7 +83,7 @@ var Power = React.createClass({
|
||||
range_attr: 'y',
|
||||
include_dots: true,
|
||||
titleizeDatum: (series, d)=>{
|
||||
return series.title + '<br/>' + Math.round(d.y) + ' W<br/>' + moment.tz(d.x.getTime(), house.timezone).format('MMM D [at] HH:mm')
|
||||
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({
|
||||
@@ -97,7 +105,8 @@ var Power = React.createClass({
|
||||
title: 'Net Power Consumption',
|
||||
values: house.power_data.map((power_datum)=>{
|
||||
return {
|
||||
x: house.toDate(power_datum.data.time),
|
||||
power_datum: power_datum,
|
||||
x: power_datum.time_to_date,
|
||||
y: Math.max(0, power_datum.data.consumption - power_datum.data.production) }
|
||||
})
|
||||
},
|
||||
@@ -105,7 +114,8 @@ var Power = React.createClass({
|
||||
title: 'Power Production',
|
||||
values: house.power_data.map((power_datum)=>{
|
||||
return {
|
||||
x: house.toDate(power_datum.data.time),
|
||||
power_datum: power_datum,
|
||||
x: power_datum.time_to_date,
|
||||
y: power_datum.data.production }
|
||||
})
|
||||
};
|
||||
@@ -124,7 +134,7 @@ var Power = React.createClass({
|
||||
container: '#power_date_setter',
|
||||
outer_height: 100,
|
||||
maxDelta: function(changed_date, other_date){
|
||||
if (Math.abs(changed_date.getTime() - other_date.getTime()) > 3600 * 24 * 7 * 1000){
|
||||
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 {
|
||||
@@ -141,28 +151,46 @@ var Power = React.createClass({
|
||||
house.power_date_range = [Math.round(min.getTime() / 1000), Math.round(max.getTime() / 1000)]
|
||||
house.setPowerData()
|
||||
.then(()=>{
|
||||
power.updateGraph();
|
||||
if (power.props.view === 'graph') power.updateGraph();
|
||||
else power.forceUpdate();
|
||||
});
|
||||
}, 500);
|
||||
};
|
||||
var four_week_start = house.data.data_until - 3600 * 24 * 28,
|
||||
data_from = Math.max(four_week_start, house.data.data_from);
|
||||
power.date_range_slider.drawData({
|
||||
abs_min: house.toDate(four_week_start),
|
||||
abs_max: house.toDate(house.data.data_until),
|
||||
current_min: house.toDate(house.default_power_start),
|
||||
current_max: house.toDate(house.default_power_end)
|
||||
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_date_setter').innerHTML = '';
|
||||
document.getElementById('power_graph').innerHTML = '';
|
||||
power.date_range_slider = undefined;
|
||||
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() {
|
||||
return powerRt.call(this);
|
||||
}
|
||||
|
||||
@@ -1,8 +1,17 @@
|
||||
<div id="power_view">
|
||||
<div class="btn-group">
|
||||
<button
|
||||
rt-if="this.props.house"
|
||||
rt-repeat="month in this.props.house.availableMonths()"
|
||||
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>
|
||||
</div>
|
||||
<div class="alert alert-warning" rt-if="this.state.loading_data">
|
||||
Retrieving power data for the {this.props.house.name} household...
|
||||
</div>
|
||||
<h4>Select dates:</h4>
|
||||
<div id="power_date_setter"></div>
|
||||
<table rt-if="this.props.view === 'table'" class="table">
|
||||
<thead>
|
||||
@@ -15,7 +24,7 @@
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr rt-repeat="power_datum in this.props.house.power_data" key="{power_datum.scoped_id}">
|
||||
<td></td>
|
||||
<td>{power_datum.data.id}</td>
|
||||
<td>{power_datum.time_to_s}</td>
|
||||
<td>{power_datum.consumption_to_s}</td>
|
||||
<td>{power_datum.production_to_s}</td>
|
||||
|
||||
@@ -1,12 +1,24 @@
|
||||
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));
|
||||
function repeatMonth1(month, monthIndex) {
|
||||
return React.createElement('button', {
|
||||
'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
|
||||
}, month);
|
||||
}
|
||||
function repeatPower_datum2(power_datum, power_datumIndex) {
|
||||
return React.createElement('tr', { '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' }, 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('h4', {}, 'Select dates:'), 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, [
|
||||
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_datum1.bind(this))
|
||||
_.map(this.props.house.power_data, repeatPower_datum2.bind(this))
|
||||
])) : null, this.props.view === 'graph' ? React.createElement('div', { 'id': 'power_graph' }) : null);
|
||||
};
|
||||
@@ -32,18 +32,18 @@ var databasable = {
|
||||
}
|
||||
},
|
||||
|
||||
collection: function(collection_name, options){
|
||||
collection: function(db_name, collection_name, options){
|
||||
var databasable = this;
|
||||
return databasable.accessDb()
|
||||
return databasable.accessDb(db_name)
|
||||
.then((db)=>{
|
||||
var collection = db.getCollection(collection_name)
|
||||
if (!collection){
|
||||
collection = db.addCollection(collection_name, options);
|
||||
}
|
||||
if (options && options.unique_indices){
|
||||
options.unique_indices.forEach((field)=>{
|
||||
collection.ensureUniqueIndex(field);
|
||||
});
|
||||
if (options && options.unique_indices){
|
||||
options.unique_indices.forEach((field)=>{
|
||||
collection.ensureUniqueIndex(field);
|
||||
});
|
||||
}
|
||||
}
|
||||
return collection;
|
||||
});
|
||||
@@ -55,12 +55,12 @@ var databasable = {
|
||||
var start_condition = {},
|
||||
end_condition = {};
|
||||
date_params['$and'] = [start_condition, end_condition];
|
||||
start_condition[attr] = {'$gt': range[0]};
|
||||
end_condition[attr] = {'$lt': range[1]};
|
||||
start_condition[attr] = {'$gte': range[0]};
|
||||
end_condition[attr] = {'$lte': range[1]};
|
||||
} else if (range[0] !== undefined) {
|
||||
date_params[attr] = {'$gt': range[0]}
|
||||
date_params[attr] = {'$gte': range[0]}
|
||||
} else if (range[1] !== undefined) {
|
||||
date_params[attr] = {'$lt': range[1]}
|
||||
date_params[attr] = {'$lte': range[1]}
|
||||
}
|
||||
return date_params;
|
||||
}
|
||||
|
||||
@@ -10,7 +10,6 @@ class EnergyDatum {
|
||||
constructor(data, house){
|
||||
var energy_datum = this;
|
||||
energy_datum.house = house;
|
||||
data.day = moment.tz(data.day, house.data.timezone);
|
||||
energy_datum.data = data;
|
||||
}
|
||||
|
||||
@@ -18,16 +17,16 @@ class EnergyDatum {
|
||||
return `energy-datum-${this.data.id}`;
|
||||
}
|
||||
|
||||
// returns a datestamp that has the client timezone, but actually is house local time.
|
||||
get day_to_date(){
|
||||
var energy_datum = this,
|
||||
moment_tz = moment.tz(energy_datum.data.day, energy_datum.house.data.timezone);
|
||||
// will have to do some additional math here to account for local offset.
|
||||
return moment(moment_tz.toArray()).toDate();
|
||||
house = energy_datum.house;
|
||||
return house.toDate(energy_datum.data.day);
|
||||
}
|
||||
|
||||
get day_to_s(){
|
||||
var energy_datum = this;
|
||||
return energy_datum.data.day.format('YYYY-MM-DD');
|
||||
return moment.tz(energy_datum.data.day * 1000, energy_datum.house.data.timezone).format('YYYY-MM-DD');
|
||||
}
|
||||
|
||||
get consumption_to_s(){
|
||||
|
||||
@@ -12,6 +12,8 @@ import MathUtil from './../../shared/utils/math';
|
||||
import DateRange from './../../shared/utils/date_range';
|
||||
import Databasable from './../lib/databasable';
|
||||
|
||||
const NAME = 'House';
|
||||
|
||||
class House {
|
||||
|
||||
// must be initiated with a dataset already in Loki database (not directly JSON).
|
||||
@@ -19,69 +21,123 @@ class House {
|
||||
var house = this;
|
||||
house.data = data;
|
||||
Object.assign(house, Databasable);
|
||||
house.power_date_range = [house.default_power_start, house.default_power_end];
|
||||
|
||||
var n_years = house.data_until_moment.year() - house.data_from_moment.year() + 1;
|
||||
house.years = [];
|
||||
for (var year=house.data_from_moment.year(); year<=house.data_until_moment.year(); year+=1){
|
||||
house.years.push(year);
|
||||
}
|
||||
house.current_month = house.data_until_moment.format('MMM');
|
||||
house.current_year = house.data_until_moment.year();
|
||||
house.setCurrentMonthMoment();
|
||||
}
|
||||
|
||||
get data_from_moment(){
|
||||
var house = this;
|
||||
return moment.tz(house.data.data_from * 1000, house.data.timezone);
|
||||
}
|
||||
|
||||
get data_until_moment(){
|
||||
var house = this;
|
||||
return moment.tz(house.data.data_until * 1000, house.data.timezone);
|
||||
}
|
||||
|
||||
get end_of_current_data_moment(){
|
||||
var house = this,
|
||||
end_of_month = house.current_month_moment.clone().endOf('month');
|
||||
return end_of_month > house.data_until_moment ? house.data_until_moment : end_of_month;
|
||||
}
|
||||
|
||||
get scoped_id(){
|
||||
return `house-${this.data.id}`;
|
||||
}
|
||||
|
||||
get default_power_start(){
|
||||
var house = this;
|
||||
// 3600 * 24 seconds * 4 = 4 days.
|
||||
return house.data.data_until - 3600 * 24 * 4;
|
||||
availableMonths(){
|
||||
var house = this,
|
||||
all_months = moment.monthsShort(),
|
||||
year = house.current_year.toString();
|
||||
if ((year) === house.data_from_moment.format('YYYY')){
|
||||
return all_months.slice(house.data_from_moment.month(), 12);
|
||||
} else if (year === house.data_until_moment.format('YYYY')){
|
||||
return all_months.slice(0, house.data_until_moment.month() + 1);
|
||||
} else {
|
||||
return all_months;
|
||||
}
|
||||
}
|
||||
|
||||
get default_power_end(){
|
||||
setYear(year){
|
||||
var house = this;
|
||||
return house.data.data_until;
|
||||
house.current_year = year;
|
||||
return house.setCurrentMonthMoment();
|
||||
}
|
||||
|
||||
setMonth(month){
|
||||
var house = this;
|
||||
house.current_month = month;
|
||||
return house.setCurrentMonthMoment();
|
||||
}
|
||||
|
||||
setCurrentMonthMoment(){
|
||||
var house = this,
|
||||
month_i = moment.monthsShort().indexOf(house.current_month),
|
||||
new_month_moment = moment.tz({year: house.current_year, month: month_i, day: 1}, house.data.timezone).startOf('month');
|
||||
if (!house.current_month_moment || new_month_moment.unix() !== house.current_month_moment.unix()){
|
||||
house.current_month_moment = new_month_moment;
|
||||
house.power_date_range = [house.end_of_current_data_moment.clone().subtract(4, 'days').unix(), house.end_of_current_data_moment.unix()];
|
||||
house.energy_date_range = [house.end_of_current_data_moment.clone().startOf('year').unix(), house.end_of_current_data_moment.clone().endOf('year').unix()]
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
offset_diff(unix){
|
||||
var house = this,
|
||||
tz = moment.tz.zone(house.data.timezone);
|
||||
return (new Date().getTimezoneOffset() - tz.offset(unix * 1000)) * 60;
|
||||
}
|
||||
|
||||
toDate(unix){
|
||||
var house = this;
|
||||
return moment.tz(unix * 1000, house.data.timezone).toDate();
|
||||
return new Date((unix + house.offset_diff(unix)) * 1000);
|
||||
}
|
||||
|
||||
formatDate(unix, format){
|
||||
var house = this;
|
||||
return moment.tz(unix * 1000, house.data.timezone).format(format)
|
||||
}
|
||||
|
||||
save(){
|
||||
var house = this;
|
||||
return House.collection(House.NAME)
|
||||
return House.collection(House.NAME, House.NAME)
|
||||
.then((house_collection)=>{
|
||||
house_collection.update(house.data);
|
||||
return House.db.save();
|
||||
});
|
||||
}
|
||||
|
||||
setPowerData(opts){
|
||||
setPowerData(){
|
||||
var house = this;
|
||||
opts = Object.assign({
|
||||
dates: house.power_date_range
|
||||
}, opts || {});
|
||||
return house.collection(PowerDatum.NAME, PowerDatum.COLLECTION_OPTIONS)
|
||||
return house.collection(house.scoped_id, PowerDatum.NAME, PowerDatum.COLLECTION_OPTIONS)
|
||||
.then((power_collection)=>{
|
||||
return house.ensurePowerData(opts)
|
||||
return house.ensurePowerData()
|
||||
.then(()=>{
|
||||
var params = house.rangeToLokiParams('time', opts.dates);
|
||||
var params = house.rangeToLokiParams('time', house.power_date_range);
|
||||
house.power_data = power_collection.find(params)
|
||||
.sort((pd1, pd2)=>{
|
||||
if (pd1.time === pd2.time) return 0;
|
||||
if (pd1.time > pd2.time) return 1;
|
||||
if (pd1.time < pd2.time) return -1;
|
||||
})
|
||||
.map((data)=>{ return new PowerDatum(data, house); })
|
||||
.map((data)=>{ return new PowerDatum(data, house); });
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
ensurePowerData(opts){
|
||||
opts = extend({
|
||||
start_date: undefined,
|
||||
end_date: undefined
|
||||
}, opts || {});
|
||||
ensurePowerData(){
|
||||
var house = this,
|
||||
existing_ranges = house.data.power_datum_ranges || [],
|
||||
query_ranges;
|
||||
|
||||
query_ranges = DateRange.addRange(opts.dates, existing_ranges);
|
||||
query_ranges = DateRange.addRange(house.power_date_range, house.data.power_datum_ranges || []);
|
||||
if (query_ranges.gaps_filled.length > 0){
|
||||
var params = {dates: query_ranges.gaps_filled};
|
||||
return house.getPowerData(params)
|
||||
@@ -95,60 +151,42 @@ class House {
|
||||
getPowerData(params){
|
||||
var house = this;
|
||||
params.house_id = house.data.id;
|
||||
return PowerDataApi.index(params)
|
||||
.then((power_data)=>{
|
||||
return house.collection(PowerDatum.NAME, PowerDatum.COLLECTION_OPTIONS)
|
||||
.then((power_collection)=>{
|
||||
power_collection.insert(power_data);
|
||||
house.db.save();
|
||||
});
|
||||
})
|
||||
}
|
||||
|
||||
clearData(){
|
||||
var house = this;
|
||||
return new Promise((fnResolve, fnReject)=>{
|
||||
house.collection(PowerDatum.NAME)
|
||||
.then((power_collection)=>{
|
||||
power_collection.removeWhere({});
|
||||
house.db.save(()=>{
|
||||
House.collection(House.NAME)
|
||||
.then((house_collection)=>{
|
||||
house_collection.remove(house.data);
|
||||
House.db.save(()=>{
|
||||
fnResolve();
|
||||
})
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
setEnergyData(opts){
|
||||
var house = this;
|
||||
return house.ensureEnergyData(opts)
|
||||
.then(()=>{
|
||||
return house.collection(EnergyDatum.NAME, EnergyDatum.COLLECTION_OPTIONS)
|
||||
.then((energy_collection)=>{
|
||||
var params = house.rangeToLokiParams('day', [opts.start_date, opts.end_date]);
|
||||
house.energy_data = energy_collection.find(params).map((data)=>{ return new EnergyDatum(data, house); })
|
||||
return house.collection(house.scoped_id, PowerDatum.NAME, PowerDatum.COLLECTION_OPTIONS)
|
||||
.then((power_collection)=>{
|
||||
return PowerDataApi.index(params)
|
||||
.then((power_data)=>{
|
||||
power_collection.insert(power_data);
|
||||
house.db.save();
|
||||
});
|
||||
})
|
||||
}
|
||||
|
||||
ensureEnergyData(opts){
|
||||
opts = extend({
|
||||
start_date: undefined,
|
||||
end_date: undefined
|
||||
}, opts || {});
|
||||
setEnergyData(){
|
||||
var house = this;
|
||||
return house.collection(house.scoped_id, EnergyDatum.NAME, EnergyDatum.COLLECTION_OPTIONS)
|
||||
.then((energy_collection)=>{
|
||||
return house.ensureEnergyData()
|
||||
.then(()=>{
|
||||
var params = house.rangeToLokiParams('day', house.energy_date_range);
|
||||
house.energy_data = energy_collection.find(params)
|
||||
.sort((pd1, pd2)=>{
|
||||
if (pd1.day === pd2.day) return 0;
|
||||
if (pd1.day > pd2.day) return 1;
|
||||
if (pd1.day < pd2.day) return -1;
|
||||
})
|
||||
.map((data)=>{ return new EnergyDatum(data, house); });
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
ensureEnergyData(){
|
||||
var house = this,
|
||||
query_ranges = DateRange.addRange([opts.start_date, opts.end_date], house.data.energy_datum_ranges);
|
||||
|
||||
query_ranges = DateRange.addRange(house.energy_date_range, house.data.energy_datum_ranges || []);
|
||||
if (query_ranges.gaps_filled.length > 0){
|
||||
house.getEnergyData({dates: query_ranges.gaps_filled})
|
||||
return house.getEnergyData({dates: query_ranges.gaps_filled})
|
||||
.then(()=>{
|
||||
house.data.energy_datum_ranges = query_ranges.new_ranges;
|
||||
return house.save();
|
||||
house.save();
|
||||
});
|
||||
} else { return Promise.resolve(); }
|
||||
}
|
||||
@@ -156,18 +194,47 @@ class House {
|
||||
getEnergyData(params){
|
||||
var house = this;
|
||||
params.house_id = house.data.id;
|
||||
return EnergyDataApi.index(params)
|
||||
.then((energy_data)=>{
|
||||
return house.collection(EnergyDatum.NAME, EnergyDatum.COLLECTION_OPTIONS)
|
||||
.then((energy_collection)=>{
|
||||
return house.collection(house.scoped_id, EnergyDatum.NAME, EnergyDatum.COLLECTION_OPTIONS)
|
||||
.then((energy_collection)=>{
|
||||
return EnergyDataApi.index(params)
|
||||
.then((energy_data)=>{
|
||||
energy_collection.insert(energy_data);
|
||||
return house.db.save();
|
||||
house.db.save();
|
||||
});
|
||||
})
|
||||
}
|
||||
|
||||
// removes all energy and power data from LokiJs (memory and persisted) database.
|
||||
clearData(){
|
||||
var house = this,
|
||||
all = [
|
||||
new Promise((fnResolve, fnReject)=>{
|
||||
house.collection(house.scoped_id, PowerDatum.NAME)
|
||||
.then((power_collection)=>{
|
||||
power_collection.removeWhere({});
|
||||
house.db.save(fnResolve);
|
||||
});
|
||||
}),
|
||||
new Promise((fnResolve, fnReject)=>{
|
||||
house.collection(house.scoped_id, EnergyDatum.NAME)
|
||||
.then((energy_collection)=>{
|
||||
energy_collection.removeWhere({});
|
||||
house.db.save(fnResolve);
|
||||
});
|
||||
}),
|
||||
new Promise((fnResolve, fnReject)=>{
|
||||
House.collection(House.NAME, House.NAME)
|
||||
.then((house_collection)=>{
|
||||
house_collection.remove(house.data);
|
||||
House.db.save(fnResolve);
|
||||
});
|
||||
})
|
||||
]
|
||||
return Promise.all(all)
|
||||
}
|
||||
|
||||
static ensureHouses(ids){
|
||||
return House.collection(House.NAME)
|
||||
return House.collection(House.NAME, House.NAME)
|
||||
.then((house_collection)=>{
|
||||
var houses_data = ids ? house_collection.find({id: {$in: ids}}) : house_collection.find();
|
||||
if (!ids && houses_data.length === 0 || ids && houses_data.length !== ids.length){
|
||||
@@ -188,5 +255,7 @@ class House {
|
||||
|
||||
}
|
||||
|
||||
House.NAME = NAME;
|
||||
|
||||
Object.assign(House, Databasable);
|
||||
export default House;
|
||||
|
||||
@@ -20,14 +20,14 @@ class PowerDatum {
|
||||
|
||||
get time_to_date(){
|
||||
var power_datum = this,
|
||||
moment_tz = moment.tz(power_datum.data.time, power_datum.house.data.timezone);
|
||||
// will have to do some additional math here to account for local offset.
|
||||
return moment(moment_tz.toArray()).toDate();
|
||||
house = power_datum.house;
|
||||
return house.toDate(power_datum.data.time);
|
||||
}
|
||||
|
||||
get time_to_s(){
|
||||
var power_datum = this;
|
||||
return power_datum.data.time.format('YYYY-MM-DD HH:mm');
|
||||
var power_datum = this,
|
||||
moment_tz = moment.tz(power_datum.data.time * 1000, power_datum.house.data.timezone);
|
||||
return moment_tz.format('YYYY-MM-DD HH:mm');
|
||||
}
|
||||
get consumption_to_s(){
|
||||
var power_datum = this;
|
||||
|
||||
@@ -10,15 +10,15 @@ class ApiHelper {
|
||||
dates.forEach((min_max)=>{
|
||||
var condition_n = {};
|
||||
condition_n[field_name] = {};
|
||||
if (min_max[0]) condition_n[field_name]['$gt'] = min_max[0];
|
||||
if (min_max[1]) condition_n[field_name]['$lt'] = min_max[1];
|
||||
if (min_max[0]) condition_n[field_name]['$gte'] = min_max[0];
|
||||
if (min_max[1]) condition_n[field_name]['$lte'] = min_max[1];
|
||||
if (Object.keys(condition_n).length) params['$or'].push(condition_n);
|
||||
});
|
||||
} else {
|
||||
var min_max = dates[0],
|
||||
condition = {}
|
||||
if (min_max[0]) condition['$gt'] = min_max[0];
|
||||
if (min_max[1]) condition['$lt'] = min_max[1];
|
||||
if (min_max[0]) condition['$gte'] = min_max[0];
|
||||
if (min_max[1]) condition['$lte'] = min_max[1];
|
||||
if (Object.keys(condition).length) params[field_name] = condition;
|
||||
}
|
||||
return params;
|
||||
|
||||
@@ -34,6 +34,8 @@ var EnergyDatum = DB.sequelize.define(NAME, {
|
||||
exposeForHouseAtDates: (house_id, dates)=>{
|
||||
var params = {house_id: house_id};
|
||||
extend(params, ApiHelper.datesParamToSequelize(dates, 'day'));
|
||||
console.log('EnergyDatum#exposeForHouseAtDates')
|
||||
console.log(params, dates)
|
||||
return EnergyDatum.findAll({
|
||||
where: params,
|
||||
attributes: ['id', 'production', 'consumption', 'day']
|
||||
|
||||
Reference in New Issue
Block a user