diff --git a/client/d3/base.js b/client/d3/base.js index 482ad6b..98f3cf4 100644 --- a/client/d3/base.js +++ b/client/d3/base.js @@ -31,7 +31,7 @@ class Chart { constructor(options){ var chart = this; - chart = extend(chart, chart.chart_options, options); + chart = extend(chart, chart.chart_options, options); chart.height = chart.outer_height - chart.margin.top - chart.margin.bottom; chart.width = chart.outer_width - chart.margin.left - chart.margin.right; diff --git a/client/d3/line/line.js b/client/d3/line/line.js index dc3f0d4..f4c510d 100644 --- a/client/d3/line/line.js +++ b/client/d3/line/line.js @@ -10,7 +10,7 @@ const DEFAULTS = { time_series: true, range_label: "range", domain_attr: null, - range_attr: + range_attr: 'y', titleize: function(series, datum){ var s = datum ? datum.name : series.name, words = s.split(' '), @@ -75,8 +75,8 @@ class LineChart { .orient("left"); if (line_chart.time_series){ - line_chart.x_scale = d3.time.scale(), - .range([0, line_chart.width]);); + line_chart.x_scale = d3.time.scale() + .range([0, line_chart.width]); } else { line_chart.x_scale = d3.scale.linear() .range([0, line_chart.width]); @@ -147,7 +147,7 @@ class LineChart { applyData(groups){ var line_chart = this; groups - .attr('class', function(series){ return "d3-chart-line " + series.css_class; ) + .attr('class', function(series){ return "d3-chart-line " + series.css_class; }) .attr("title", function(series){ return series.title; }) .append("path") .attr("d", function(series){ return line_chart.line(series.values); }) diff --git a/client/dashboard/energy/energy.js b/client/dashboard/energy/energy.js index a43899f..3329254 100644 --- a/client/dashboard/energy/energy.js +++ b/client/dashboard/energy/energy.js @@ -8,7 +8,6 @@ var Energy = React.createClass({ getInitialState: function(){ var energy = this; return { - view: 'graph', graph_attr: 'production', loading_data: true }; @@ -25,29 +24,26 @@ var Energy = React.createClass({ energy.graph_title = 'Daily Consumption'; house.ensureEnergyData().then(()=>{ energy.setState({loading_data: false}); - energy.initGraph(); + if (energy.props.view === 'graph') energy.initGraph(); }); }, componentWillReceiveProps: function(new_props){ var energy = this; - energy.setState({loading_data: true}); if (new_props.house !== energy.state.house){ + energy.setState({loading_data: true}); new_props.house.ensureEnergyData().then(()=>{ energy.setState({loading_data: false}); - if (energy.state.view === 'graph') energy.initGraph(); + if (energy.props.view === 'graph') energy.initGraph(); }); } + if (new_props.view !== 'graph' && energy.props.view === 'graph') energy.destroyGraph(); }, - setView: function(event){ + componentDidUpdate: function(prev_props, _prev_state){ var energy = this, - view = event.target.dataset.value, house = energy.props.house; - if (view !== energy.state.view){ - energy.setState({view: view}); - if (energy.state.view === 'graph') energy.initGraph(); - } + if (prev_props.view !== 'graph' && energy.props.view === 'graph') energy.initGraph(); }, setGraphAttr: function(event){ @@ -58,7 +54,7 @@ var Energy = React.createClass({ energy.setState({ graph_attr: graph_attr }, function(){ - if (energy.state.view === 'graph') energy.updateGraph(); + if (energy.props.view === 'graph') energy.updateGraph(); }) } }, @@ -66,7 +62,6 @@ var Energy = React.createClass({ initGraph: function(){ var energy = this; if (energy.graph === undefined){ - document.getElementById('energy_graph').innerHTML = ''; energy.graph = new CalendarGridChart({ container: '#energy_graph', outer_width: 800, @@ -102,6 +97,12 @@ var Energy = React.createClass({ }); }, + destroyGraph: function(){ + var energy = this; + document.getElementById('energy_graph').innerHTML = ''; + energy.graph = undefined; + }, + render: function() { return energyRt.call(this); } diff --git a/client/dashboard/energy/energy.rt b/client/dashboard/energy/energy.rt index 16e72d0..8b7cea3 100644 --- a/client/dashboard/energy/energy.rt +++ b/client/dashboard/energy/energy.rt @@ -1,33 +1,23 @@
-

Household Daily Energy

Retrieving energy data for the {this.props.house.name} household...
-
- - -

-
- - +
+

Select Data

+
+ + +
- +
@@ -45,5 +35,5 @@
-
+
diff --git a/client/dashboard/energy/energy.rt.js b/client/dashboard/energy/energy.rt.js index a999376..cb2b4a4 100644 --- a/client/dashboard/energy/energy.rt.js +++ b/client/dashboard/energy/energy.rt.js @@ -4,20 +4,7 @@ function repeatEnergy_datum1(energy_datum, energy_datumIndex) { return React.createElement('tr', { 'key': energy_datum.react_key }, 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' }, React.createElement('h2', {}, 'Household Daily Energy'), this.state.loading_data ? React.createElement('div', { 'className': 'alert alert-warning' }, '\n Retrieving energy data for the ', this.props.house.name, ' household...\n ') : null, 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('br', {}), this.state.view === 'graph' ? React.createElement('div', { + 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', { 'className': 'btn-group', 'role': 'group' }, React.createElement('button', { @@ -30,9 +17,9 @@ export default function () { 'className': _.keys(_.pick({ active: this.state.graph_attr === 'production' }, _.identity)).join(' ') + ' ' + 'btn btn-primary', 'onClick': this.setGraphAttr, 'type': 'button' - }, 'Production')) : null, this.state.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, [ + }, '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, React.createElement('div', { 'id': 'energy_graph' })); + ])) : null, this.props.view === 'graph' ? React.createElement('div', { 'id': 'energy_graph' }) : null); }; \ No newline at end of file diff --git a/client/dashboard/layout/layout.js b/client/dashboard/layout/layout.js index 785331f..ca215ce 100644 --- a/client/dashboard/layout/layout.js +++ b/client/dashboard/layout/layout.js @@ -2,18 +2,15 @@ import React from 'react'; import layoutRt from './layout.rt.js'; import House from './../../models/house'; -const VIEWS = [['power', 'Power Savings'], ['energy', 'Energy Production']]; - var Layout = React.createClass({ getInitialState: function(){ var layout = this; - layout.view_name = VIEWS[0][1]; return { - views: VIEWS, houses: null, house: null, - view: 'energy', + view: 'graph', + dataset: 'energy', requesting_data: true }; }, @@ -30,13 +27,6 @@ var Layout = React.createClass({ }); }, - setView: function(event){ - var layout = this, - view = event.target.value; - layout.view_name = event.target.innerText; - layout.setState({view: view}); - }, - setHouse: function(event){ var layout = this, house_id = event.target.value, @@ -44,6 +34,19 @@ var Layout = React.createClass({ layout.setState({house: house}); }, + 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}); + }, + render: function() { return layoutRt.call(this); } diff --git a/client/dashboard/layout/layout.rt b/client/dashboard/layout/layout.rt index e9c008a..62768b6 100644 --- a/client/dashboard/layout/layout.rt +++ b/client/dashboard/layout/layout.rt @@ -3,15 +3,40 @@
Retrieving houses...

{this.state.house.name}

-

{this.view_name}

- +

Select household:

- - +

Select dataset:

+
+ + +
+ +

View as:

+
+ + +
+ + +
diff --git a/client/dashboard/layout/layout.rt.js b/client/dashboard/layout/layout.rt.js index 6e63f8b..ef830c5 100644 --- a/client/dashboard/layout/layout.rt.js +++ b/client/dashboard/layout/layout.rt.js @@ -2,32 +2,51 @@ import React from 'react'; import _ from 'lodash'; import Energy from './../energy/energy'; import Power from './../power/power'; -function repeatView1(view, viewIndex) { - return React.createElement('option', { - 'value': view[0], - 'key': 'view-' + view[0] - }, view[1]); -} -function repeatHouse2(house, houseIndex) { +function repeatHouse1(house, houseIndex) { return React.createElement('option', { 'value': house.data.id, 'key': house.react_key }, house.data.name); } export default function () { - return React.createElement('div', { 'id': 'layout' }, this.state.requesting_data ? React.createElement('div', { 'className': 'alert alert-warning' }, 'Retrieving houses...') : null, this.state.house ? React.createElement('h1', {}, this.state.house.name) : null, this.state.view ? React.createElement('h3', {}, this.view_name) : null, React.createElement.apply(this, [ - 'select', - { - 'className': 'form-control', - 'onChange': this.setView - }, - _.map(this.state.views, repeatView1.bind(this)) - ]), this.state.houses ? React.createElement.apply(this, [ + return React.createElement('div', { 'id': 'layout' }, this.state.requesting_data ? React.createElement('div', { 'className': 'alert alert-warning' }, 'Retrieving houses...') : null, this.state.house ? React.createElement('h1', {}, this.state.house.name) : null, React.createElement('h4', {}, 'Select household:'), this.state.houses ? React.createElement.apply(this, [ 'select', { 'className': 'form-control', 'onChange': this.setHouse }, - _.map(this.state.houses, repeatHouse2.bind(this)) - ]) : null, this.state.house && this.state.view === 'energy' ? React.createElement(Energy, { 'house': this.state.house }) : null); + _.map(this.state.houses, repeatHouse1.bind(this)) + ]) : 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')), this.state.house && this.state.dataset === 'energy' ? React.createElement(Energy, { + 'house': this.state.house, + 'view': this.state.view + }) : null, this.state.house && this.state.dataset === 'power' ? React.createElement(Power, { + 'house': this.state.house, + 'view': this.state.view + }) : null); }; \ No newline at end of file diff --git a/client/dashboard/power/power.js b/client/dashboard/power/power.js index e69de29..738f01b 100644 --- a/client/dashboard/power/power.js +++ b/client/dashboard/power/power.js @@ -0,0 +1,91 @@ +import React from 'react'; +import powerRt from './power.rt.js'; +import House from './../../models/house'; +import SplineStackChart from './../../d3/line/spline_stack'; + +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 = ''; + house.ensurePowerData().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.state.house){ + power.setState({loading_data: true}); + new_props.house.ensurePowerData().then(()=>{ + power.setState({loading_data: false}); + if (power.props.view === 'graph') power.initGraph(); + }); + } + }, + + componentDidUpdate: function(prev_props, _prev_state){ + var power_datum = this, + house = power.props.house; + if (prev_props.view !== 'graph' && power.props.view === 'graph') power.initGraph(); + }, + + initGraph: function(){ + var power = this; + if (power.graph === undefined){ + document.getElementById('power_graph').innerHTML = ''; + power.graph = new SplineStackChart({ + container: '#power_graph', + outer_width: 800, + outer_height: 200, + date_attr: 'day', + color: '#0404B4', + toDate: (power_datum)=>{ return power_datum.data.day.toDate(); } + }); + jQuery('#power_graph').tooltip({ + selector: '.d3-chart-grid-unit', + container: 'body', + title: function(){ + var power_datum = this.__data__, + date_s = d3.time.format('%a %b %d, %Y')(power_datum.data.day.toDate()), + range_value = `${Math.round(power_datum.data[power.state.graph_attr])} kWh`; + return `${date_s}: ${range_value}`; + } + }); + } + power.updateGraph(); + }, + + updateGraph: function(){ + var power = this, + house = power.props.house; + power.graph.rangeValue = (datum)=>{ return datum.data[power.state.graph_attr]; } + power.graph.drawData({ + title: power.graph_title, + css_class: '', + min_range: 0, + max_range: 150, + values: house.power_data + }); + }, + + render: function() { + return powerRt.call(this); + } +}); + +export default Power; diff --git a/client/dashboard/power/power.rt b/client/dashboard/power/power.rt index 8966de3..5dae1fa 100644 --- a/client/dashboard/power/power.rt +++ b/client/dashboard/power/power.rt @@ -1,5 +1,8 @@
-

Fifteen Minute Power Interval

+

Household 15-minute Power Statistics

+
+ Retrieving power data for the {this.props.house.name} household... +
@@ -18,4 +21,5 @@
+
diff --git a/client/dashboard/power/power.rt.js b/client/dashboard/power/power.rt.js index b2237e7..cb1d591 100644 --- a/client/dashboard/power/power.rt.js +++ b/client/dashboard/power/power.rt.js @@ -4,9 +4,9 @@ function repeatPower_datum1(power_datum, power_datumIndex) { return React.createElement('tr', { 'key': power_datum.react_key }, 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 React.createElement('div', { 'id': 'power_view' }, React.createElement('h2', {}, 'Fifteen Minute Power Interval'), React.createElement('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('h2', {}, 'Household 15-minute Power Statistics'), 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('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.state.house.power_data, repeatPower_datum1.bind(this)) - ]))); + ])), React.createElement('div', { 'id': 'power_data' })); }; \ No newline at end of file diff --git a/package.json b/package.json index 7105210..fc3fb2d 100644 --- a/package.json +++ b/package.json @@ -16,15 +16,12 @@ "pg": "~4.4.3", "pg-hstore": "~2.3.2", "fast-csv": "0.6.0", - "babel-core": "6.3.21", - "babel-loader": "6.2.0", "babel-polyfill": "6.3.14", "babel-preset-es2015": "6.3.13", "babel-preset-react": "6.3.13", "babel-preset-stage-0": "6.3.13", "babel-core": "6.3.21", "babel-loader": "6.2.0", - "babel-polyfill": "6.3.14", "babel-preset-es2015": "6.3.13", "babel-preset-react": "6.3.13", "babel-preset-stage-0": "6.3.13",