diff --git a/js/components/ReportsTab.js b/js/components/ReportsTab.js
index 9924db7..a96c143 100644
--- a/js/components/ReportsTab.js
+++ b/js/components/ReportsTab.js
@@ -1,24 +1,6 @@
var React = require('react');
-var ReactDOM = require('react-dom');
-var ReactBootstrap = require('react-bootstrap');
-var Grid = ReactBootstrap.Grid;
-var Row = ReactBootstrap.Row;
-var Col = ReactBootstrap.Col;
-var Form = ReactBootstrap.Form;
-var FormGroup = ReactBootstrap.FormGroup;
-var FormControl = ReactBootstrap.FormControl;
-var ControlLabel = ReactBootstrap.ControlLabel;
-var Button = ReactBootstrap.Button;
-var ButtonGroup = ReactBootstrap.ButtonGroup;
-var ButtonToolbar = ReactBootstrap.ButtonToolbar;
-var Glyphicon = ReactBootstrap.Glyphicon;
-var ListGroup = ReactBootstrap.ListGroup;
-var ListGroupItem = ReactBootstrap.ListGroupItem;
-var Modal = ReactBootstrap.Modal;
-var Panel = ReactBootstrap.Panel;
-
-var Combobox = require('react-widgets').Combobox;
+var StackedBarChart = require('../components/StackedBarChart');
module.exports = React.createClass({
displayName: "ReportsTab",
@@ -29,9 +11,13 @@ module.exports = React.createClass({
this.props.onFetchReport("monthly_expenses");
},
render: function() {
- console.log(this.props.reports);
+ report = [];
+ if (this.props.reports['monthly_expenses'])
+ report = ();
return (
-
hello
+
+ {report}
+
);
}
});
diff --git a/js/components/StackedBarChart.js b/js/components/StackedBarChart.js
new file mode 100644
index 0000000..56b3783
--- /dev/null
+++ b/js/components/StackedBarChart.js
@@ -0,0 +1,162 @@
+var d3 = require('d3');
+var React = require('react');
+
+var Panel = require('react-bootstrap').Panel;
+
+module.exports = React.createClass({
+ displayName: "StackedBarChart",
+ calcMinMax: function(data) {
+ var children = [];
+ for (var child in data) {
+ if (data.hasOwnProperty(child))
+ children.push(data[child]);
+ }
+
+ var positiveValues = [0];
+ var negativeValues = [0];
+ if (children.length > 0 && children[0].length > 0) {
+ for (var j = 0; j < children[0].length; j++) {
+ positiveValues.push(children.reduce(function(accum, curr, i, arr) {
+ if (arr[i][j] > 0)
+ return accum + arr[i][j];
+ return accum;
+ }, 0));
+ negativeValues.push(children.reduce(function(accum, curr, i, arr) {
+ if (arr[i][j] < 0)
+ return accum + arr[i][j];
+ return accum;
+ }, 0));
+ }
+ }
+
+ return [Math.min.apply(Math, negativeValues), Math.max.apply(Math, positiveValues)];
+ },
+ calcAxisMarkSeparation: function(minMax, height, ticksPerHeight) {
+ var targetTicks = height / ticksPerHeight;
+ var range = minMax[1]-minMax[0];
+ var rangePerTick = range/targetTicks;
+ var roundOrder = Math.floor(Math.log(rangePerTick) / Math.LN10);
+ var roundTo = Math.pow(10, roundOrder);
+ return Math.ceil(rangePerTick/roundTo)*roundTo;
+ },
+ render: function() {
+ var data = this.props.data.mapReduceChildren(null,
+ function(accumulator, currentValue, currentIndex, array) {
+ return accumulator + currentValue;
+ }
+ );
+
+ height = 400;
+ width = 600;
+ legendWidth = 100;
+ xMargin = 70;
+ yMargin = 70;
+ height -= yMargin*2;
+ width -= xMargin*2;
+
+ var minMax = this.calcMinMax(data);
+ var y = d3.scaleLinear()
+ .range([0, height])
+ .domain(minMax);
+
+ var xAxisMarksEvery = this.calcAxisMarkSeparation(minMax, height, 40);
+
+ var x = d3.scaleLinear()
+ .range([0, width])
+ .domain([0, this.props.data.Labels.length + 0.5]);
+
+ var bars = [];
+ var labels = [];
+
+ var barWidth = x(0.75);
+ var barStart = x(0.25) + (x(1) - barWidth)/2;
+ var childId=0;
+
+ // Add Y axis marks and labels, and initialize positive- and
+ // negativeSum arrays
+ var positiveSum = [];
+ var negativeSum = [];
+ for (var i=0; i < this.props.data.Labels.length; i++) {
+ positiveSum.push(0);
+ negativeSum.push(0);
+ var labelX = x(i) + barStart + barWidth/2;
+ var labelY = height + 15;
+ labels.push((
+ {this.props.data.Labels[i]}
+ ));
+ labels.push((
+
+ ));
+ }
+
+ // Make X axis marks and labels
+ var makeXLabel = function(value) {
+ labels.push((
+
+ ));
+ labels.push((
+ {value}
+ ));
+ }
+ for (var i=0; i < minMax[1]; i+= xAxisMarksEvery)
+ makeXLabel(i);
+ for (var i=0-xAxisMarksEvery; i > minMax[0]; i -= xAxisMarksEvery)
+ makeXLabel(i);
+
+ //TODO handle Values from current series?
+ var legendMap = {};
+ for (var child in data) {
+ childId++;
+ var rectClasses = "chart-element chart-color" + (childId % 12);
+ if (data.hasOwnProperty(child)) {
+ for (var i=0; i < data[child].length; i++) {
+ var value = data[child][i];
+ if (value == 0)
+ continue;
+ legendMap[child] = childId;
+ if (value > 0) {
+ rectHeight = y(value) - y(0);
+ positiveSum[i] += rectHeight;
+ rectY = height - y(0) - positiveSum[i];
+ } else {
+ rectHeight = y(0) - y(value);
+ rectY = height - y(0) + negativeSum[i];
+ negativeSum[i] += rectHeight;
+ }
+
+ bars.push((
+
+ ));
+ }
+ }
+ }
+
+ var legend = [];
+ for (var series in legendMap) {
+ var legendClasses = "chart-color" + (legendMap[series] % 12);
+ var legendY = (legendMap[series] - 1)*15;
+ legend.push((
+
+ ));
+ legend.push((
+ {series}
+ ));
+ }
+
+ return (
+
+
+
+ );
+ }
+});
diff --git a/js/models.js b/js/models.js
index 3a78419..bf505bc 100644
--- a/js/models.js
+++ b/js/models.js
@@ -425,6 +425,39 @@ Series.prototype.fromJSONobj = function(json_obj) {
}
}
+Series.prototype.mapReduceChildren = function(mapFn, reduceFn) {
+ var children = {}
+ for (var child in this.Children) {
+ if (this.Children.hasOwnProperty(child))
+ children[child] = this.Children[child].mapReduce(mapFn, reduceFn);
+ }
+ return children;
+}
+
+Series.prototype.mapReduce = function(mapFn, reduceFn) {
+ var childValues = [];
+ if (mapFn)
+ childValues.push(this.Values.map(mapFn));
+ else
+ childValues.push(this.Values.slice());
+
+ for (var child in this.Children) {
+ if (this.Children.hasOwnProperty(child))
+ childValues.push(this.Children[child].mapReduce(mapFn, reduceFn));
+ }
+
+ var reducedValues = [];
+ if (reduceFn && childValues.length > 0 && childValues[0].length > 0) {
+ for (var j = 0; j < childValues[0].length; j++) {
+ reducedValues.push(childValues.reduce(function(accum, curr, i, arr) {
+ return reduceFn(accum, arr[i][j]);
+ }, childValues[0][j]));
+ }
+ }
+
+ return reducedValues;
+}
+
function Report() {
this.ReportId = "";
this.Title = "";
@@ -475,6 +508,19 @@ Report.prototype.fromJSON = function(json_input) {
}
}
+Report.prototype.mapReduceChildren = function(mapFn, reduceFn) {
+ var series = {}
+ for (var child in this.Series) {
+ if (this.Series.hasOwnProperty(child))
+ series[child] = this.Series[child].mapReduce(mapFn, reduceFn);
+ }
+ return series;
+}
+
+Report.prototype.mapReduceSeries = function(mapFn, reduceFn) {
+ return this.mapReduceChildren(mapFn, reduceFn);
+}
+
module.exports = models = {
// Classes
diff --git a/package.json b/package.json
index 7cb61ea..88a4d49 100644
--- a/package.json
+++ b/package.json
@@ -9,6 +9,7 @@
"big.js": "^3.1.3",
"browserify": "^13.1.0",
"cldr-data": "^29.0.2",
+ "d3": "^4.5.0",
"globalize": "^1.1.1",
"keymirror": "^0.1.1",
"react": "^15.3.2",
diff --git a/static/css/reports.css b/static/css/reports.css
new file mode 100644
index 0000000..f522620
--- /dev/null
+++ b/static/css/reports.css
@@ -0,0 +1,67 @@
+.chart-color1 {
+ fill: #a6cee3;
+ stroke: #86aec3;
+}
+.chart-color2 {
+ fill: #1f78b4;
+ stroke: #005894;
+}
+.chart-color3 {
+ fill: #b2df8a;
+ stroke: #92bf6a;
+}
+.chart-color4 {
+ fill: #33a02c;
+ stroke: #13800c;
+}
+.chart-color5 {
+ fill: #fb9a99;
+ stroke: #db7a79;
+}
+.chart-color6 {
+ fill: #e31a1c;
+ stroke: #c30000;
+}
+.chart-color7 {
+ fill: #fdbf6f;
+ stroke: #dd9f4f;
+}
+.chart-color8 {
+ fill: #ff7f00;
+ fill: #df5f00;
+}
+.chart-color9 {
+ fill: #cab2d6;
+ stroke: #aa92b6;
+}
+.chart-color10 {
+ fill: #6a3d9a;
+ stroke: #4a1d7a;
+}
+.chart-color11 {
+ fill: #ffff99;
+ stroke: #dfdf79;
+}
+.chart-color12 {
+ fill: #b15928;
+ stroke: #913908;
+}
+
+.chart-element {
+ stroke-width: 0;
+}
+.chart-element:hover {
+ stroke-width: 2;
+}
+.chart-legend rect {
+ stroke-width: 2;
+}
+
+.axis {
+ stroke: #000;
+ stroke-width: 2;
+}
+.axis-tick {
+ stroke: #000;
+ stroke-width: 1;
+}
diff --git a/static/stylesheet.css b/static/css/stylesheet.css
similarity index 98%
rename from static/stylesheet.css
rename to static/css/stylesheet.css
index cdc733a..9da7490 100644
--- a/static/stylesheet.css
+++ b/static/css/stylesheet.css
@@ -1,3 +1,5 @@
+@import url("reports.css");
+
html, body {
height: 100%;
}
diff --git a/static/index.html b/static/index.html
index 9e4e78b..34b4c9c 100644
--- a/static/index.html
+++ b/static/index.html
@@ -5,7 +5,7 @@
-
+