Use highcharts instead of nvd3.

This commit is contained in:
Alexis Lahouze 2015-06-15 13:08:44 +02:00
parent 2131d1f39a
commit fa981d4a32
3 changed files with 131 additions and 194 deletions

View File

@ -15,7 +15,8 @@
along with Accountant. If not, see <http://www.gnu.org/licenses/>. along with Accountant. If not, see <http://www.gnu.org/licenses/>.
*/ */
var accountantApp = angular.module("accountantApp", [ var accountantApp = angular.module("accountantApp", [
"mgcrea.ngStrap" "mgcrea.ngStrap",
"highcharts-ng"
]) ])
.config(function($interpolateProvider, $httpProvider) { .config(function($interpolateProvider, $httpProvider) {

View File

@ -28,105 +28,151 @@ accountantApp.controller(
// Placeholder for saved value to cancel entry edition // Placeholder for saved value to cancel entry edition
$scope.savedItem = null; $scope.savedItem = null;
$scope.entriesLoaded = function(entries) { // Configure pie chart for categories.
var entriesReversed = entries.slice().reverse(); $scope.categoriesChartConfig = {
options: {
var categories = []; chart: {
type: 'pie',
var chartValues = []; animation: {
duration: 500
var pieChartValuesTmp = {}; }
var pieChartValues = []; },
plotOptions: {
angular.forEach(entriesReversed, function(entry) { pie: {
var category = entry.category; startAngle: -90
var value = entry.value ? Number(entry.value) : null; }
var sold = entry.sold ? Number(entry.sold) : null; },
var operation_date = entry.operation_date; tooltip: {
valueDecimals: 2,
if(operation_date && sold) { valueSuffix: '€'
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];
} }
},
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; // 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;
$scope.chartValues = chartValues; var config = $scope.categoriesChartConfig;
// Second pass: transform to an array readable by jqplot. angular.forEach(angular.fromJson(data), function(category) {
var pieChartValues = []; brightness = 0.2;
angular.forEach(pieChartValuesTmp, function(value, key) {
pieChartValues.push([key, value]);
});
$scope.pieChartValues = pieChartValues; expenses.push({
name: category.category,
y: -category.expenses,
color: Highcharts.Color(config.series[0].data[1].color).brighten(brightness).get()
});
//$scope.categories.length = 0; revenues.push({
//$scope.categories.concat(categories); name: category.category,
$scope.categories = categories; y: category.revenues,
color: Highcharts.Color(config.series[0].data[0].color).brighten(brightness).get()
});
});
nv.addGraph($scope.drawChart); // Note: expenses and revenues must be in the same order than in series[0].
nv.addGraph($scope.drawPieChart); config.series[1].data = revenues.concat(expenses);
//$scope.drawPieChart(pieChartValues, "#expense-categories-chart-placeholder"); });
//$scope.drawPieChart();
}; };
$scope.getAccountStatus = function(account, month) { $scope.getAccountStatus = function(account, month) {
if(account != null && month != null) { // Note: Month is 0 indexed.
// Note: Month is 0 indexed. $scope.begin = moment({year: month.year, month: month.month - 1, day: 1});
var begin = moment({year: month.year, month: month.month - 1, day: 1}); $scope.end = $scope.begin.clone().endOf('month');
var end = begin.clone().endOf('month');
$http.get("/api/accounts/" + account.id, $http.get("/api/accounts/" + account.id, {
{params: { params: {
begin: begin.format('YYYY-MM-DD'), begin: $scope.begin.format('YYYY-MM-DD'),
end: end.format('YYYY-MM-DD') end: $scope.end.format('YYYY-MM-DD')
}}).success($scope.getAccountStatus_success); }
} }).success(function(status) {
}; var colors = Highcharts.getOptions().colors;
$scope.getAccountStatus_success = function(status) { var config = $scope.categoriesChartConfig;
$scope.accountStatus = status;
// 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. // Function to load entries from server for a specific account and month.
$scope.loadEntries = function(account, month) { $scope.loadEntries = function(account, month) {
if(account) { if(account) {
$scope.account = account; $scope.account = account;
} }
// Clean up selected entry. // Clean up selected entry.
$scope.selectedItem = null; $scope.selectedItem = null;
$scope.savedItem = null; $scope.savedItem = null;
if(account && month) { if(account && month) {
// Note: Month is 0 indexed. // Note: Month is 0 indexed.
var begin = moment({year: month.year, month: month.month - 1, day: 1}); var begin = moment({year: month.year, month: month.month - 1, day: 1});
var end = begin.clone().endOf('month'); var end = begin.clone().endOf('month');
$http.get("/api/entries", $http.get("/api/entries", {
{params: { params: {
account: account.id, account: account.id,
begin: begin.format('YYYY-MM-DD'), begin: begin.format('YYYY-MM-DD'),
end: end.format('YYYY-MM-DD') end: end.format('YYYY-MM-DD')
}}).success($scope.loadEntries_success); }
} else { }).success($scope.loadEntries_success);
$scope.loadEntries_success(null); } else {
} $scope.loadEntries_success(null);
}
}; };
// Load entries success callback // 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){ $rootScope.$on("monthsLoadedEvent", function(event, args){
$scope.loadEntries(args.account, args.month); $scope.loadEntries(args.account, args.month);
}); });
@ -460,8 +415,4 @@ accountantApp.controller(
$rootScope.$on("monthsLoadedEvent", function(event, args){ $rootScope.$on("monthsLoadedEvent", function(event, args){
$scope.getAccountStatus(args.account, args.month); $scope.getAccountStatus(args.account, args.month);
}); });
$scope.$on("entriesLoadedEvent", function(event, args) {
$scope.entriesLoaded(args.entries);
});
}); });

View File

@ -20,25 +20,10 @@
<!-- Chart row --> <!-- Chart row -->
<div class="row"> <div class="row">
<!-- Sold evolution chart placeholder --> <!-- Sold evolution chart placeholder -->
<div class="col-md-7"> <highchart id="entries-chart" config="entriesChartConfig" class="col-md-8"></highchart>
<svg id="entries-chart-placeholder" style="stroke-width: 1px"/>
</div>
<!-- Expense category piechart --> <!-- Expense category piechart -->
<div class="col-md-3"> <highchart id="expense-categories-chart" config="categoriesChartConfig" class="col-md-4"></highchart>
<svg id="expense-categories-chart-placeholder" style='height:300px'/>
</div>
<!-- Balance -->
<div class="col-md-2">
<div class="row">
<table class="table">
<tr><td>Dépenses&nbsp;:</td><td>[[accountStatus.expenses]]</td></tr>
<tr><td>Recettes&nbsp;:</td><td>[[accountStatus.revenues]]</td></tr>
<tr><td>Balance&nbsp;:</td><td><span ng-class="entryValueClass(accountStatus.balance)">[[accountStatus.balance]]</span></td></tr>
</table>
</div>
</div>
</div> </div>
<!-- Row with entry table --> <!-- Row with entry table -->