From ffcd1fde0f2af1fb031ee307ee871f7e543712e1 Mon Sep 17 00:00:00 2001 From: Sander Dorigo Date: Thu, 30 Oct 2014 18:06:29 +0100 Subject: [PATCH] Even more charts and tables. --- app/controllers/GoogleChartController.php | 158 ++++++++++++++++++ app/controllers/GoogleTableController.php | 110 ++++++++++++ app/routes.php | 10 +- app/views/accounts/show.blade.php | 27 ++- public/assets/javascript/firefly/accounts.js | 20 ++- public/assets/javascript/firefly/gcharts.js | 116 ++++++++++++- .../javascript/firefly/gcharts.options.js | 9 +- 7 files changed, 428 insertions(+), 22 deletions(-) create mode 100644 app/controllers/GoogleTableController.php diff --git a/app/controllers/GoogleChartController.php b/app/controllers/GoogleChartController.php index c4e5ab89d8..8d08b8d3e7 100644 --- a/app/controllers/GoogleChartController.php +++ b/app/controllers/GoogleChartController.php @@ -174,6 +174,10 @@ class GoogleChartController extends BaseController } + /** + * @return \Illuminate\Http\JsonResponse + * @throws \Firefly\Exception\FireflyException + */ public function recurringTransactionsOverview() { @@ -260,4 +264,158 @@ class GoogleChartController extends BaseController return Response::json($chart->getData()); } + + /** + * @param Account $account + */ + public function accountBalanceChart(Account $account) + { + /** @var \Grumpydictator\Gchart\GChart $chart */ + $chart = App::make('gchart'); + $chart->addColumn('Day of month', 'date'); + $chart->addColumn('Balance for ' . $account->name, 'number'); + + /* + * Loop the date, then loop the accounts, then add balance. + */ + $start = Session::get('start'); + $end = Session::get('end'); + $current = clone $start; + + while ($end >= $current) { + $row = [clone $current]; + if ($current > Carbon::now()) { + $row[] = null; + } else { + $row[] = $account->balance($current); + } + + $chart->addRowArray($row); + $current->addDay(); + } + + + $chart->generate(); + return Response::json($chart->getData()); + } + + /** + * @param Account $account + * + * @return \Illuminate\Http\JsonResponse + */ + public function accountSankeyOutChart(Account $account) + { + // collect all relevant entries. + $set = []; + + /** @var \Grumpydictator\Gchart\GChart $chart */ + $chart = App::make('gchart'); + $chart->addColumn('From', 'string'); + $chart->addColumn('To', 'string', 'domain'); + $chart->addColumn('Weight', 'number'); + + $transactions = $account->transactions()->with( + ['transactionjournal', 'transactionjournal.transactions', 'transactionjournal.budgets', 'transactionjournal.transactiontype', + 'transactionjournal.categories'] + )->before(Session::get('end'))->after( + Session::get('start') + )->get(); + + /** @var Transaction $transaction */ + foreach ($transactions as $transaction) { + $amount = floatval($transaction->amount); + $type = $transaction->transactionJournal->transactionType->type; + + if ($amount < 0 && $type != 'Transfer') { + + // from account to a budget (if present). + $budgetName = isset($transaction->transactionJournal->budgets[0]) ? $transaction->transactionJournal->budgets[0]->name : '(no budget)'; + $set[] = [$account->name, $budgetName, $amount * -1]; + + // from budget to category. + $categoryName = isset($transaction->transactionJournal->categories[0]) ? ' ' . $transaction->transactionJournal->categories[0]->name + : '(no cat)'; + $set[] = [$budgetName, $categoryName, $amount * -1]; + } + } + // loop the set, group everything together: + $grouped = []; + foreach ($set as $entry) { + $key = $entry[0] . $entry[1]; + if (isset($grouped[$key])) { + $grouped[$key][2] += $entry[2]; + } else { + $grouped[$key] = $entry; + } + } + + // add rows to the chart: + foreach ($grouped as $entry) { + $chart->addRow($entry[0], $entry[1], $entry[2]); + } + + $chart->generate(); + return Response::json($chart->getData()); + + } + + /** + * @param Account $account + * + * @return \Illuminate\Http\JsonResponse + */ + public function accountSankeyInChart(Account $account) + { + // collect all relevant entries. + $set = []; + + /** @var \Grumpydictator\Gchart\GChart $chart */ + $chart = App::make('gchart'); + $chart->addColumn('From', 'string'); + $chart->addColumn('To', 'string', 'domain'); + $chart->addColumn('Weight', 'number'); + + $transactions = $account->transactions()->with( + ['transactionjournal', 'transactionjournal.transactions' => function ($q) { + $q->where('amount', '<', 0); + }, 'transactionjournal.budgets', 'transactionjournal.transactiontype', 'transactionjournal.categories'] + )->before(Session::get('end'))->after( + Session::get('start') + )->get(); + + /** @var Transaction $transaction */ + foreach ($transactions as $transaction) { + $amount = floatval($transaction->amount); + $type = $transaction->transactionJournal->transactionType->type; + + if ($amount > 0 && $type != 'Transfer') { + + $otherAccount = $transaction->transactionJournal->transactions[0]->account->name; + $categoryName = isset($transaction->transactionJournal->categories[0]) ? $transaction->transactionJournal->categories[0]->name + : '(no cat)'; + $set[] = [$otherAccount, $categoryName, $amount]; + $set[] = [$categoryName, $account->name, $amount]; + } + } + // loop the set, group everything together: + $grouped = []; + foreach ($set as $entry) { + $key = $entry[0] . $entry[1]; + if (isset($grouped[$key])) { + $grouped[$key][2] += $entry[2]; + } else { + $grouped[$key] = $entry; + } + } + + // add rows to the chart: + foreach ($grouped as $entry) { + $chart->addRow($entry[0], $entry[1], $entry[2]); + } + + $chart->generate(); + return Response::json($chart->getData()); + + } } \ No newline at end of file diff --git a/app/controllers/GoogleTableController.php b/app/controllers/GoogleTableController.php new file mode 100644 index 0000000000..727b6af6fb --- /dev/null +++ b/app/controllers/GoogleTableController.php @@ -0,0 +1,110 @@ +addColumn('ID', 'number'); + $chart->addColumn('ID_Edit', 'string'); + $chart->addColumn('ID_Delete', 'string'); + $chart->addColumn('Date', 'date'); + $chart->addColumn('Description_URL', 'string'); + $chart->addColumn('Description', 'string'); + $chart->addColumn('Amount', 'number'); + $chart->addColumn('From_URL', 'string'); + $chart->addColumn('From', 'string'); + $chart->addColumn('To_URL', 'string'); + $chart->addColumn('To', 'string'); + $chart->addColumn('Budget_URL', 'string'); + $chart->addColumn('Budget', 'string'); + $chart->addColumn('Category_URL', 'string'); + $chart->addColumn('Category', 'string'); + + /* + * Find transactions: + */ + $accountID = $account->id; + $transactions = $account->transactions()->with( + ['transactionjournal', 'transactionjournal.transactions' => function ($q) use ($accountID) { + $q->where('account_id', '!=', $accountID); + }, 'transactionjournal.budgets', 'transactionjournal.transactiontype', + 'transactionjournal.categories'] + )->before(Session::get('end'))->after( + Session::get('start') + )->orderBy('date', 'DESC')->get(); + + /** @var Transaction $transaction */ + foreach ($transactions as $transaction) { + $date = $transaction->transactionJournal->date; + $descriptionURL = route('transactions.show', $transaction->transaction_journal_id); + $description = $transaction->transactionJournal->description; + $amount = floatval($transaction->amount); + + if ($transaction->transactionJournal->transactions[0]->account->id == $account->id) { + $opposingAccountURI = route('accounts.show', $transaction->transactionJournal->transactions[1]->account->id); + $opposingAccountName = $transaction->transactionJournal->transactions[1]->account->name; + } else { + $opposingAccountURI = route('accounts.show', $transaction->transactionJournal->transactions[0]->account->id); + $opposingAccountName = $transaction->transactionJournal->transactions[0]->account->name; + } + if (isset($transaction->transactionJournal->budgets[0])) { + $budgetURL = route('budgets.show', $transaction->transactionJournal->budgets[0]->id); + $budget = $transaction->transactionJournal->budgets[0]->name; + } else { + $budgetURL = ''; + $budget = ''; + } + + if (isset($transaction->transactionJournal->categories[0])) { + $categoryURL = route('categories.show', $transaction->transactionJournal->categories[0]->id); + $category = $transaction->transactionJournal->categories[0]->name; + } else { + $budgetURL = ''; + $budget = ''; + } + + + if ($amount < 0) { + $from = $account->name; + $fromURL = route('accounts.show', $account->id); + + $to = $opposingAccountName; + $toURL = $opposingAccountURI; + } else { + $to = $account->name; + $toURL = route('accounts.show', $account->id); + + $from = $opposingAccountName; + $fromURL = $opposingAccountURI; + } + + $budcat = 'Budcat'; + $id = $transaction->transactionJournal->id; + $edit = route('transactions.edit', $transaction->transactionJournal->id); + $delete = route('transactions.delete', $transaction->transactionJournal->id); + $chart->addRow( + $id, $edit, $delete, $date, $descriptionURL, $description, $amount, $fromURL, $from, $toURL, $to, $budgetURL, $budget, $categoryURL, $category + ); + } + +// Date +// Description +// Amount (€) +// From +// To +// Budget / category +// ID + + + $chart->generate(); + return Response::json($chart->getData()); + } +} \ No newline at end of file diff --git a/app/routes.php b/app/routes.php index 9eced9703c..049619b7e3 100644 --- a/app/routes.php +++ b/app/routes.php @@ -161,11 +161,19 @@ Route::group(['before' => 'auth'], function () { Route::get('/categories/edit/{category}',['uses' => 'CategoryController@edit','as' => 'categories.edit']); Route::get('/categories/delete/{category}',['uses' => 'CategoryController@delete','as' => 'categories.delete']); - // chart controller + // google chart controller Route::get('/chart/home/account', ['uses' => 'GoogleChartController@allAccountsBalanceChart']); Route::get('/chart/home/budgets', ['uses' => 'GoogleChartController@allBudgetsHomeChart']); Route::get('/chart/home/categories', ['uses' => 'GoogleChartController@allCategoriesHomeChart']); Route::get('/chart/home/recurring', ['uses' => 'GoogleChartController@recurringTransactionsOverview']); + Route::get('/chart/account/{account}', ['uses' => 'GoogleChartController@accountBalanceChart']); + Route::get('/chart/sankey/{account}/out', ['uses' => 'GoogleChartController@accountSankeyOutChart']); + Route::get('/chart/sankey/{account}/in', ['uses' => 'GoogleChartController@accountSankeyInChart']); + + // google table controller + Route::get('/table/account/{account}/transactions', ['uses' => 'GoogleTableController@transactionsByAccount']); + + Route::get('/chart/home/info/{accountnameA}/{day}/{month}/{year}', ['uses' => 'ChartController@homeAccountInfo', 'as' => 'chart.info']); diff --git a/app/views/accounts/show.blade.php b/app/views/accounts/show.blade.php index 8cac788611..1985b8c2c7 100644 --- a/app/views/accounts/show.blade.php +++ b/app/views/accounts/show.blade.php @@ -7,7 +7,7 @@ {{{$account->name}}}
-
+
@@ -71,7 +71,7 @@ Out
-
+
@@ -81,7 +81,7 @@ In
-
+
@@ -94,20 +94,8 @@ Transactions
+
- - - - - - - - - - - - -
DateDescriptionAmount (€)FromToBudget / categoryID
{{-- @@ -170,5 +158,12 @@ {{HTML::script('assets/javascript/datatables/jquery.dataTables.min.js')}} {{HTML::script('assets/javascript/datatables/dataTables.bootstrap.js')}} + + + + +{{HTML::script('assets/javascript/firefly/gcharts.options.js')}} +{{HTML::script('assets/javascript/firefly/gcharts.js')}} + {{HTML::script('assets/javascript/firefly/accounts.js')}} @stop \ No newline at end of file diff --git a/public/assets/javascript/firefly/accounts.js b/public/assets/javascript/firefly/accounts.js index 33d0b22040..702335e3ae 100644 --- a/public/assets/javascript/firefly/accounts.js +++ b/public/assets/javascript/firefly/accounts.js @@ -1,10 +1,24 @@ $(function () { + + if (typeof(googleLineChart) == "function") { + googleLineChart('chart/account/' + accountID, 'overview-chart'); + } + // + if(typeof(googleSankeyChart) == 'function') { + googleSankeyChart('chart/sankey/' + accountID + '/out','account-out-sankey'); + googleSankeyChart('chart/sankey/' + accountID + '/in','account-in-sankey'); + } + if(typeof(googleTable) == 'function') { + googleTable('table/account/' + accountID + '/transactions','account-transactions'); + } + + if ($('#accountTable').length == 1) { drawDatatable(); } - if ($('#overviewChart').length == 1) { - drawOverviewChart(); - } + //if ($('#overviewChart').length == 1) { + // drawOverviewChart(); + //} }); diff --git a/public/assets/javascript/firefly/gcharts.js b/public/assets/javascript/firefly/gcharts.js index 998514d0f1..32c748b0ac 100644 --- a/public/assets/javascript/firefly/gcharts.js +++ b/public/assets/javascript/firefly/gcharts.js @@ -1,4 +1,4 @@ -google.load('visualization', '1.0', {'packages': ['corechart']}); +google.load('visualization', '1.1', {'packages': ['corechart', 'sankey', 'table']}); function googleLineChart(URL, container) { $.getJSON(URL).success(function (data) { @@ -115,6 +115,120 @@ function googlePieChart(URL, container) { */ chart.draw(gdata, defaultPieChartOptions); + }).fail(function () { + $('#' + container).addClass('google-chart-error'); + }); +} + +function googleSankeyChart(URL, container) { + $.getJSON(URL).success(function (data) { + /* + Get the data from the JSON + */ + gdata = new google.visualization.DataTable(data); + + /* + Format as money + */ + + console.log(gdata.getNumberOfRows()) + if (gdata.getNumberOfRows() < 1) { + console.log('remove'); + $('#' + container).parent().parent().remove(); + return; + } else if (gdata.getNumberOfRows() < 6) { + defaultSankeyChartOptions.height = 100 + } else { + defaultSankeyChartOptions.height = 400 + } + + + /* + Create a new google charts object. + */ + var chart = new google.visualization.Sankey(document.getElementById(container)); + + /* + Draw it: + */ + chart.draw(gdata, defaultSankeyChartOptions); + + }).fail(function () { + $('#' + container).addClass('google-chart-error'); + }); +} + +function googleTable(URL, container) { + $.getJSON(URL).success(function (data) { + /* + Get the data from the JSON + */ + var gdata = new google.visualization.DataTable(data); + + /* + Create a new google charts object. + */ + var chart = new google.visualization.Table(document.getElementById(container)); + + /* + Do something with formatters: + */ + var x = gdata.getNumberOfColumns(); + var columnsToHide = new Array; + var URLFormatter = new google.visualization.PatternFormat('{1}'); + + var EditButtonFormatter = new google.visualization.PatternFormat('
'); + + var money = new google.visualization.NumberFormat({decimalSymbol: ',', groupingSymbol: '.', prefix: '\u20AC '}); + + + for (var i = 0; i < x; i++) { + var label = gdata.getColumnLabel(i); + console.log('Column ' + i + ':' + label); + /* + Format a string using the previous column as URL. + */ + if (label == 'Description' || label == 'From' || label == 'To' || label == 'Budget' || label == 'Category') { + URLFormatter.format(gdata, [i - 1, i], i); + columnsToHide.push(i - 1); + } + if(label == 'ID') { + EditButtonFormatter.format(gdata, [i+1,i+2],i); + columnsToHide.push(i+1,i+2); + } + + /* + Format with buttons: + */ + + + /* + Format as money + */ + if (label == 'Amount') { + money.format(gdata, i); + } + + } + + + //var formatter = new google.visualization.PatternFormat('{1}'); + + //formatter.format(gdata, [5, 6], 6); + //formatter.format(gdata, [7, 8], 8); + + + var view = new google.visualization.DataView(gdata); + // hide certain columns: + + view.hideColumns(columnsToHide); + + + /* + Draw it: + */ + chart.draw(view, defaultTableOptions); + }).fail(function () { $('#' + container).addClass('google-chart-error'); }); diff --git a/public/assets/javascript/firefly/gcharts.options.js b/public/assets/javascript/firefly/gcharts.options.js index a038b286a8..75064974fc 100644 --- a/public/assets/javascript/firefly/gcharts.options.js +++ b/public/assets/javascript/firefly/gcharts.options.js @@ -52,8 +52,15 @@ var defaultPieChartOptions = { width: '100%', height: '100%' }, - height:200, + height: 200, legend: { position: 'none' } +}; + +var defaultSankeyChartOptions = { + height: 400 +} +var defaultTableOptions = { + allowHtml: true }; \ No newline at end of file