From 9ea4a8ebe96fdfd954de292c4434c5318a9256bc Mon Sep 17 00:00:00 2001 From: Eric Hulburd Date: Mon, 15 Feb 2016 16:09:40 -0600 Subject: [PATCH] energy spline stack --- client/d3/base.js | 4 +- client/d3/line/line.js | 95 +++++++++-------------------- client/d3/line/spline_stack.js | 97 ++++++++++++++---------------- client/dashboard/layout/layout.js | 2 +- client/dashboard/power/power.js | 37 +++++++----- client/dashboard/power/power.rt | 6 +- client/dashboard/power/power.rt.js | 6 +- 7 files changed, 106 insertions(+), 141 deletions(-) diff --git a/client/d3/base.js b/client/d3/base.js index 98f3cf4..2da3768 100644 --- a/client/d3/base.js +++ b/client/d3/base.js @@ -3,7 +3,7 @@ import extend from 'extend'; const DEFAULTS = { outer_width: 500, outer_height: 300, - margin: {top: 0, left: 70, bottom: 50, right: 20}, + margin: {top: 30, left: 70, bottom: 50, right: 20}, domain_ticks: 10, range_ticks: 8, container: "container", @@ -12,7 +12,7 @@ const DEFAULTS = { domain_attr: undefined, range_attr: undefined, titleize: function(series, datum){ - var s = datum ? datum.name : series.title; + var s = datum ? datum.title : series.title; if (!s) return ''; var words = s.split(' '), array = []; diff --git a/client/d3/line/line.js b/client/d3/line/line.js index f4c510d..20db7b8 100644 --- a/client/d3/line/line.js +++ b/client/d3/line/line.js @@ -1,42 +1,10 @@ import extend from 'extend'; -const DEFAULTS = { - outer_width: 500, - outer_height: 300, - margin: {top: 0, left: 70, bottom: 50, right: 20}, - domain_ticks: 10, - range_ticks: 8, - container: "container", - time_series: true, - range_label: "range", - domain_attr: null, - range_attr: 'y', - titleize: function(series, datum){ - var s = datum ? datum.name : series.name, - words = s.split(' '), - array = []; - for (var i=0; i{ - series_data.range_min = Math.min(series_data.range_min, value[line_chart.range_attr]); - series_data.range_max = Math.max(series_data.range_max, value[line_chart.range_attr]); - series_data.domain_min = Math.min(series_data.domain_min, value[line_chart.domain_attr]); - series_data.domain_max = Math.max(series_data.domain_max, value[line_chart.domain_attr]); - return {x: value[spline_stack.domain_attr], y: value[spline_stack.range_attr]}; - }); + if (spline_stack.domain_attr !== 'x' && spline_stack.range_attr !== 'y'){ + series.values = series.values.map((value)=>{ + return {x: value[spline_stack.domain_attr], y: value[spline_stack.range_attr], series: series}; + }); + } serialized_data.series.push(series); }); + serialized_data.series = spline_stack.fnStack(serialized_data.series); + // assume all series have same domain, use first series to establish extent. + serialized_data.domain_extent = d3.extent(serialized_data.series[0].values.map((value)=>{ return value.x; })); + // final series will have the highest y values. + serialized_data.range_max = d3.max(serialized_data.series[serialized_data.series.length - 1].values.map((value)=>{ return value.y0 + value.y; })) + return serialized_data; }; drawData(data){ var spline_stack = this; - data = spline_stack.serialize_data; + data = spline_stack.serializeData(data); // calibrate axes. - bar_chart.y_scale.domain([Math.min(0, data.range_min), data.range_max]); - bar_chart.svg.select(".d3-chart-range.d3-chart-axis") - .call(bar_chart.y_axis); + spline_stack.y_scale.domain([0, data.range_max]); + spline_stack.svg.select(".d3-chart-range.d3-chart-axis") + .call(spline_stack.y_axis); - bar_chart.x_scale.domain([Math.min(data.domain_min), data.domain_max]); - bar_chart.svg.select(".d3-chart-domain.d3-chart-axis").call(bar_chart.x_axis); + spline_stack.x_scale.domain(data.domain_extent); + spline_stack.svg.select(".d3-chart-domain.d3-chart-axis").call(spline_stack.x_axis); - var stack_data = spline_stack.stack(data.series), - stack = svg.selectAll(".d3-chart-spline-stack") - .data(stack_data); - - [stack.enter().append("g"), stack.transition()].forEach((groups)=>{ - applyData(groups); + var stack = spline_stack.svg.selectAll(".d3-chart-spline-stack") + .data(data.series); + [stack.enter().append("path"), stack.transition()].forEach((paths)=>{ + spline_stack.applyData(paths); }); stack.exit().remove(); } - applyData(groups){ + applyData(paths){ var spline_stack = this; - groups + paths .attr("class", function(series){"d3-chart-spline-stack " + series.css_class;}) - .append("path") - .attr("d", function(series){ return spline_stack.area(series.xy_values); }) - .style("fill", function(series){ return spline_stack.color(series.name); }); + .attr("d", function(series){ return spline_stack.fnArea(series.values); }) + .style("fill", function(series){ return spline_stack.fnColor(series.title); }); } } + +export default SplineStackChart; diff --git a/client/dashboard/layout/layout.js b/client/dashboard/layout/layout.js index ca215ce..f945bd4 100644 --- a/client/dashboard/layout/layout.js +++ b/client/dashboard/layout/layout.js @@ -10,7 +10,7 @@ var Layout = React.createClass({ houses: null, house: null, view: 'graph', - dataset: 'energy', + dataset: 'power', requesting_data: true }; }, diff --git a/client/dashboard/power/power.js b/client/dashboard/power/power.js index 738f01b..7f133e5 100644 --- a/client/dashboard/power/power.js +++ b/client/dashboard/power/power.js @@ -36,10 +36,11 @@ var Power = React.createClass({ if (power.props.view === 'graph') power.initGraph(); }); } + if (new_props.view !== 'graph' && power.props.view === 'graph') power.destroyGraph(); }, componentDidUpdate: function(prev_props, _prev_state){ - var power_datum = this, + var power = this, house = power.props.house; if (prev_props.view !== 'graph' && power.props.view === 'graph') power.initGraph(); }, @@ -47,23 +48,20 @@ var Power = React.createClass({ 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(); } + range_attr: 'y', + domain_attr: 'x', + time_series: true }); jQuery('#power_graph').tooltip({ - selector: '.d3-chart-grid-unit', + selector: 'path', 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}`; + return this.__data__.title; } }); } @@ -72,17 +70,28 @@ var Power = React.createClass({ updateGraph: function(){ var power = this, - house = power.props.house; - power.graph.rangeValue = (datum)=>{ return datum.data[power.state.graph_attr]; } + house = power.props.house, + net_power = { + title: 'Net Power Consumption', + values: house.power_data.slice(0, 200).map((power_datum)=>{ return {y: Math.max(0, power_datum.data.consumption - power_datum.data.production), x: power_datum.data.time.toDate() } }) + }, + savings = { + title: 'Power Production', + values: house.power_data.slice(0, 200).map((power_datum)=>{ return {y: power_datum.data.production, x: power_datum.data.time.toDate() } }) + }; power.graph.drawData({ title: power.graph_title, css_class: '', - min_range: 0, - max_range: 150, - values: house.power_data + series: [net_power, savings] }); }, + destroyGraph: function(){ + var power = this; + document.getElementById('power_graph').innerHTML = ''; + power.graph = undefined; + }, + render: function() { return powerRt.call(this); } diff --git a/client/dashboard/power/power.rt b/client/dashboard/power/power.rt index 5dae1fa..4cebd67 100644 --- a/client/dashboard/power/power.rt +++ b/client/dashboard/power/power.rt @@ -3,7 +3,7 @@
Retrieving power data for the {this.props.house.name} household...
- +
@@ -13,7 +13,7 @@ - + @@ -21,5 +21,5 @@
{power_datum.time_to_s} {power_datum.consumption_to_s}
-
+
diff --git a/client/dashboard/power/power.rt.js b/client/dashboard/power/power.rt.js index cb1d591..b455bce 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', {}, '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, [ + 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, 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.state.house.power_data, repeatPower_datum1.bind(this)) - ])), React.createElement('div', { 'id': 'power_data' })); + _.map(this.props.house.power_data, repeatPower_datum1.bind(this)) + ])) : null, this.props.view === 'graph' ? React.createElement('div', { 'id': 'power_graph' }) : null); }; \ No newline at end of file