diff --git a/accountant/frontend/static/js/app.js b/accountant/frontend/static/js/app.js index c409d07..6bba7e7 100644 --- a/accountant/frontend/static/js/app.js +++ b/accountant/frontend/static/js/app.js @@ -15,7 +15,8 @@ along with Accountant. If not, see . */ var accountantApp = angular.module("accountantApp", [ - "mgcrea.ngStrap" + "mgcrea.ngStrap", + "highcharts-ng" ]) .config(function($interpolateProvider, $httpProvider) { diff --git a/accountant/frontend/static/js/entries.js b/accountant/frontend/static/js/entries.js index de13ed9..9694b41 100644 --- a/accountant/frontend/static/js/entries.js +++ b/accountant/frontend/static/js/entries.js @@ -28,105 +28,151 @@ accountantApp.controller( // Placeholder for saved value to cancel entry edition $scope.savedItem = null; - $scope.entriesLoaded = function(entries) { - var entriesReversed = entries.slice().reverse(); - - var categories = []; - - var chartValues = []; - - var pieChartValuesTmp = {}; - var pieChartValues = []; - - angular.forEach(entriesReversed, function(entry) { - var category = entry.category; - var value = entry.value ? Number(entry.value) : null; - var sold = entry.sold ? Number(entry.sold) : null; - var operation_date = entry.operation_date; - - if(operation_date && sold) { - chartValues.push([entry.operation_date, sold]); - } - - if(category && category != '') { - if(categories.indexOf(category) == -1) { - categories.push(category); - } - - if(value && value < 0.0) { - var oldValue = 0.0; - - if(pieChartValuesTmp[category]) { - oldValue = pieChartValuesTmp[category]; + // Configure pie chart for categories. + $scope.categoriesChartConfig = { + options: { + chart: { + type: 'pie', + animation: { + duration: 500 + } + }, + plotOptions: { + pie: { + startAngle: -90 + } + }, + tooltip: { + valueDecimals: 2, + valueSuffix: '€' } + }, + yAxis: { + title: { + text: "Categories" + } + }, + title: { + text: "Répartition dépenses/recettes" + }, + series: [{ + name: "Value", + data: [], + innerSize: '33%', + size: '60%', + dataLabels: { + formatter: function() { + return this.point.name; + }, + distance: -40 + } + }, { + name: "Value", + data: [], + innerSize: '66%', + size: '60%', + dataLabels: { + formatter: function() { + return this.point.name !== null && this.y > 100 ? this.point.name : null; + }, + } + }] + }; - pieChartValuesTmp[category] = oldValue - value; - } - } - }); - - $scope.chartValues = chartValues; + // Load categories, mainly to populate the pie chart. + $scope.loadCategories = function() { + $http.get("/api/categories", { + params: { + account: $scope.account.id, + begin: $scope.begin.format('YYYY-MM-DD'), + end: $scope.end.format('YYYY-MM-DD') + } + }).success(function(data) { + var expenses = [], revenues = []; + var colors = Highcharts.getOptions().colors; - // Second pass: transform to an array readable by jqplot. - var pieChartValues = []; - angular.forEach(pieChartValuesTmp, function(value, key) { - pieChartValues.push([key, value]); - }); + var config = $scope.categoriesChartConfig; - $scope.pieChartValues = pieChartValues; + angular.forEach(angular.fromJson(data), function(category) { + brightness = 0.2; - //$scope.categories.length = 0; - //$scope.categories.concat(categories); - $scope.categories = categories; + expenses.push({ + name: category.category, + y: -category.expenses, + color: Highcharts.Color(config.series[0].data[1].color).brighten(brightness).get() + }); - nv.addGraph($scope.drawChart); - nv.addGraph($scope.drawPieChart); - //$scope.drawPieChart(pieChartValues, "#expense-categories-chart-placeholder"); - //$scope.drawPieChart(); + revenues.push({ + name: category.category, + y: category.revenues, + color: Highcharts.Color(config.series[0].data[0].color).brighten(brightness).get() + }); + }); + + // Note: expenses and revenues must be in the same order than in series[0]. + config.series[1].data = revenues.concat(expenses); + }); }; $scope.getAccountStatus = function(account, month) { - if(account != null && month != null) { - // Note: Month is 0 indexed. - var begin = moment({year: month.year, month: month.month - 1, day: 1}); - var end = begin.clone().endOf('month'); + // Note: Month is 0 indexed. + $scope.begin = moment({year: month.year, month: month.month - 1, day: 1}); + $scope.end = $scope.begin.clone().endOf('month'); - $http.get("/api/accounts/" + account.id, - {params: { - begin: begin.format('YYYY-MM-DD'), - end: end.format('YYYY-MM-DD') - }}).success($scope.getAccountStatus_success); - } - }; + $http.get("/api/accounts/" + account.id, { + params: { + begin: $scope.begin.format('YYYY-MM-DD'), + end: $scope.end.format('YYYY-MM-DD') + } + }).success(function(status) { + var colors = Highcharts.getOptions().colors; - $scope.getAccountStatus_success = function(status) { - $scope.accountStatus = status; + var config = $scope.categoriesChartConfig; + + // Update pie chart subtitle with Balance. + config.subtitle = { + text: "Balance: " + status.balance + }; + + config.series[0].data = [{ + name: "Revenues", + y: status.revenues, + color: colors[2] + }, { + name: "Expenses", + y: -status.expenses, + color: colors[3], + }]; + + $scope.loadCategories(); + }); }; // Function to load entries from server for a specific account and month. $scope.loadEntries = function(account, month) { - if(account) { - $scope.account = account; - } + if(account) { + $scope.account = account; + } - // Clean up selected entry. - $scope.selectedItem = null; - $scope.savedItem = null; + // Clean up selected entry. + $scope.selectedItem = null; + $scope.savedItem = null; - if(account && month) { - // Note: Month is 0 indexed. - var begin = moment({year: month.year, month: month.month - 1, day: 1}); - var end = begin.clone().endOf('month'); + if(account && month) { + // Note: Month is 0 indexed. + var begin = moment({year: month.year, month: month.month - 1, day: 1}); + var end = begin.clone().endOf('month'); - $http.get("/api/entries", - {params: { - account: account.id, - begin: begin.format('YYYY-MM-DD'), - end: end.format('YYYY-MM-DD') - }}).success($scope.loadEntries_success); - } else { - $scope.loadEntries_success(null); - } + $http.get("/api/entries", { + params: { + account: account.id, + begin: begin.format('YYYY-MM-DD'), + end: end.format('YYYY-MM-DD') + } + }).success($scope.loadEntries_success); + } else { + $scope.loadEntries_success(null); + } }; // Load entries success callback @@ -362,97 +408,6 @@ accountantApp.controller( } }; - // Function to draw the sold evolution chart. - $scope.drawChart = function() { - // Clear previous chart - var entries = $scope.chartValues; - console.debug("drawChart", entries); - - var width = 700; - var height = 300; - - //if(entries && entries.length > 1) { - // Prepare for today vertical line. - var today = new Date(); - today.setHours(0); - today.setMinutes(0); - - // FIXME Alexis Lahouze 2015-06-12 Date format. - - // Find first and last days to set limits of the x axis. - var firstDate = moment(entries[0][0]).subtract(1, 'day').format(); - var lastDate = moment(entries[entries.length - 1][0]).add(1, 'day').format(); - - var chart = nv.models.lineChart().options({ - x: function(d) { return new Date(d[0]); }, - y: function(d) { return new Number(d[1]); }, - transitionDuration: 250, - showXAxis: true, - showYAxis: true, - width: width, - height: height, - }); - - chart.lines.interpolate("monotone"); - - chart.xAxis - .axisLabel("Date") - .tickFormat(function(d) { - return d3.time.format("%x")(new Date(d)); - }); - //chart.xAxis.scale().range([firstDate, lastDate]); - - chart.yAxis - .axisLabel("Solde") - .tickFormat(d3.format('.02f')); - - // FIXME add vertical line for today - graph = d3.select("#entries-chart-placeholder").datum([ - { color: "orange", key: "Zero", values:[ - [firstDate, "0"], - [lastDate, "0"] - ]}, - { color: "red", key: "Authorized overdraft", values : [ - [firstDate, new Number($scope.account.authorized_overdraft)], - [lastDate, new Number($scope.account.authorized_overdraft)] - ]}, - { color: "darkblue", key: "Sold evolution", values: entries}, - ]) - .transition().duration(1200) - .attr("width", width) - .attr("height", height) - .call(chart); - - nv.utils.windowResize(chart.update); - - return chart; - }; - - // Function to draw the expense category pie chart. - $scope.drawPieChart = function() { - // FIXME retrieve width and height from DOM - var width = 300; - var height = 300; - - var chart = nv.models.pieChart() - .x(function(d) { console.debug(d); return d[0]; }) - .y(function(d) { return d[1]; }) - .width(width) - .height(height) - .showLabels(true); - - d3.select("#expense-categories-chart-placeholder") - .datum($scope.pieChartValues) - .transition().duration(1200) - .attr('width', width) - .attr('height', height) - .call(chart); - - nv.utils.windowResize(chart.update); - - return chart - }; - $rootScope.$on("monthsLoadedEvent", function(event, args){ $scope.loadEntries(args.account, args.month); }); @@ -460,8 +415,4 @@ accountantApp.controller( $rootScope.$on("monthsLoadedEvent", function(event, args){ $scope.getAccountStatus(args.account, args.month); }); - - $scope.$on("entriesLoadedEvent", function(event, args) { - $scope.entriesLoaded(args.entries); - }); }); diff --git a/accountant/frontend/templates/index.html b/accountant/frontend/templates/index.html index 8c47f9a..19890b3 100644 --- a/accountant/frontend/templates/index.html +++ b/accountant/frontend/templates/index.html @@ -20,25 +20,10 @@
-
- -
+ -
- -
- - -
-
- - - - -
Dépenses :[[accountStatus.expenses]]
Recettes :[[accountStatus.revenues]]
Balance :[[accountStatus.balance]]
-
-
+