PHP/JavaScript webapp to analyse spending habits
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

analyser.js 6.9KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213
  1. var previousPoint = null;
  2. var plots = {};
  3. var months = ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December'];
  4. function colourTableRows(table) {
  5. $('tr', table).removeClass('alt');
  6. $('tr:visible:even', table).addClass('alt');
  7. }
  8. function showTooltip(x, y, contents) {
  9. $('<div id="tooltip">' + contents + '</div>').css( {
  10. position: 'absolute',
  11. display: 'none',
  12. top: y + 5,
  13. left: x + 5,
  14. border: '1px solid #fdd',
  15. padding: '2px',
  16. 'background-color': '#fee',
  17. }).appendTo("body").fadeIn(200);
  18. }
  19. function getDateKey(date) {
  20. return date.getFullYear() + '-' + (date.getMonth() < 9 ? '0' : '') + (date.getMonth() + 1);
  21. }
  22. function showSelectedMonths(start, end, incoming, outgoing) {
  23. $('#historytable tr.data').remove();
  24. $('#historytable').show();
  25. var startDate = new Date(start), endDate = new Date(end);
  26. if (startDate.getDate() > 1) {
  27. startDate.setDate(1);
  28. startDate.getMonth() == 11 && startDate.setYear(startDate.getFullYear() + 1);
  29. startDate.setMonth(startDate.getMonth() == 11 ? 0 : startDate.getMonth() + 1);
  30. }
  31. var startKey = getDateKey(startDate), endKey = getDateKey(endDate);
  32. if (startKey == endKey) {
  33. $('#historytable h3').text('Transactions for ' + months[startDate.getMonth()] + ' ' + startDate.getFullYear());
  34. } else if (startDate.getFullYear() == endDate.getFullYear()) {
  35. $('#historytable h3').text('Transactions for ' + months[startDate.getMonth()] + '-' + months[endDate.getMonth()] + ' ' + startDate.getFullYear());
  36. } else {
  37. $('#historytable h3').text('Transactions for ' + months[startDate.getMonth()] + ' ' + startDate.getFullYear() + ' - ' + months[endDate.getMonth()] + ' ' + endDate.getFullYear());
  38. }
  39. var pieData = {};
  40. var table = $('#historytable table');
  41. var include = false;
  42. $.each(data, function(month, monthData) {
  43. if (month == startKey) { include = true; }
  44. if (include) {
  45. $.each(monthData, function(index, trans) {
  46. if (incoming != trans.Amount > 0) { return; }
  47. var category = trans.Category ? trans.Category : 'Unsorted';
  48. var tr = $('<tr/>').addClass('data').addClass('category' + category.replace(/[^a-zA-Z]*/g, '')).appendTo(table);
  49. $('<td/>').text(trans.Date.date.split(' ')[0]).appendTo(tr);
  50. $('<td/>').text(trans.Type ? trans.Type : 'Other').appendTo(tr);
  51. $('<td/>').text(trans.Category ? trans.Category : '').appendTo(tr);
  52. $('<td/>').text(trans.Description).appendTo(tr);
  53. $('<td/>').text(trans.Amount).appendTo(tr);
  54. if (category != '(Ignored)') {
  55. if (!pieData[category]) { pieData[category] = 0; }
  56. pieData[category] += Math.abs(trans.Amount);
  57. }
  58. });
  59. }
  60. if (month == endKey) { include = false; }
  61. });
  62. colourTableRows(table);
  63. var seriesData = [];
  64. $.each(pieData, function(category, amount) {
  65. seriesData.push({ label: category + ' (' + Math.round(amount) + ')', data: amount });
  66. });
  67. seriesData.sort(function(a, b) { return b.data - a.data; });
  68. plots.expense = $.plot($('#expense'), seriesData, {
  69. series: { pie: { show: true, innerRadius: 0.5, highlight: { opacity: 0.5 } } },
  70. grid: { clickable: true }
  71. });
  72. }
  73. $(function() {
  74. var transData = [{label: 'Income', data: []}, {label: 'Expense', data: []}, {label: 'Difference', data: []}];
  75. var categories = {};
  76. var min = new Date().getTime(), max = 0;
  77. $.each(data, function(month, entries) {
  78. var split = month.split('-');
  79. var timestamp = new Date(split[0], split[1] - 1).getTime();
  80. var sum = [0, 0];
  81. $.each(entries, function() {
  82. if (this.Category == '(Ignored)') { return; }
  83. if (this.Amount < 0) {
  84. var category = this.Category ? this.Category : 'Unsorted';
  85. if (!categories[category]) { categories[category] = {}; }
  86. if (!categories[category][timestamp]) { categories[category][timestamp] = 0; }
  87. categories[category][timestamp] -= this.Amount;
  88. }
  89. sum[this.Amount < 0 ? 1 : 0] += this.Amount;
  90. });
  91. transData[0].data.push([timestamp, sum[0]]);
  92. transData[1].data.push([timestamp, sum[1]]);
  93. transData[2].data.push([timestamp, sum[0] + sum[1]]);
  94. min = Math.min(min, timestamp);
  95. max = Math.max(max, timestamp);
  96. });
  97. var catData = [];
  98. $.each(categories, function(category, entries) {
  99. var series = {label: category, data: []};
  100. var total = 0;
  101. $.each(transData[0].data, function() {
  102. var timestamp = this[0];
  103. var val = entries[timestamp] ? entries[timestamp] : 0;
  104. total += val;
  105. series.data.push([timestamp, val]);
  106. });
  107. series.total = total;
  108. catData.push(series);
  109. });
  110. catData.sort(function(a, b) { return a.total - b.total; });
  111. plots.history = $.plot($('#history'), transData, {
  112. xaxis: { mode: 'time', timeformat: '%y/%m' },
  113. series: {
  114. lines: { show: true, fill: true },
  115. points: { show: true }
  116. },
  117. grid: {
  118. hoverable: true,
  119. clickable: true,
  120. markings: [{ color: '#000', lineWidth: 1, xaxis: { from: min, to: max }, yaxis: { from: 0, to: 0 } }]
  121. },
  122. selection: { mode : "x" }
  123. });
  124. plots.cathistory = $.plot($('#cathistory'), catData, {
  125. xaxis: { mode: 'time', timeformat: '%y/%m' },
  126. legend: { noColumns: 2 },
  127. series: {
  128. stack: true,
  129. lines: { show: true, fill: true }
  130. },
  131. });
  132. $("#history").bind("plothover", function (event, pos, item) {
  133. if (item) {
  134. var id = {dataIndex: item.dataIndex, seriesIndex: item.seriesIndex};
  135. if (previousPoint == null || previousPoint.dataIndex != id.dataIndex || previousPoint.seriesIndex != id.seriesIndex) {
  136. previousPoint = id;
  137. $("#tooltip").remove();
  138. var x = item.datapoint[0],
  139. y = item.datapoint[1].toFixed(2);
  140. var date = new Date(x);
  141. var seriesTitles = ["Money in", "Money out", "Balance change"];
  142. showTooltip(item.pageX, item.pageY, (seriesTitles[item.seriesIndex]) + " during " + months[date.getMonth()] + " " + date.getFullYear() + " = " + y);
  143. }
  144. } else {
  145. $("#tooltip").remove();
  146. previousPoint = null;
  147. }
  148. });
  149. $('#history').bind('plotselected', function(event, ranges) {
  150. var startDate = parseInt(ranges.xaxis.from.toFixed());
  151. var endDate = parseInt(ranges.xaxis.to.toFixed());
  152. $.history.load('start:' + startDate + ';end:' + endDate + ';type:expenses');
  153. });
  154. $('#history').bind('plotclick', function(event, pos, item) {
  155. if (item) {
  156. $.history.load('start:' + item.datapoint[0] + ';end:' + item.datapoint[0] + ';type:' + (item.seriesIndex == 0 ? 'income' : 'expenses'));
  157. }
  158. });
  159. $('#expense').bind('plotclick', function(event, pos, item) {
  160. $('#historytable .data').hide();
  161. $('#historytable .category' + item.series.label.replace(/[^a-zA-Z]*/g, '')).show();
  162. colourTableRows($('#historytable'));
  163. });
  164. $.history.init(function(hash) {
  165. var match = /start:([0-9]+);end:([0-9]+);type:(income|expenses)/.exec(hash);
  166. if (match != null) {
  167. showSelectedMonths(parseInt(match[1]), parseInt(match[2]), match[3] == 'income', match[3] == 'expenses');
  168. plots.history.setSelection({ xaxis: { from: parseInt(match[1]), to: parseInt(match[2]) }});
  169. }
  170. });
  171. });