|
@@ -1,13 +1,188 @@
|
1
|
1
|
var previousPoint = null;
|
2
|
2
|
var state = {};
|
3
|
3
|
var plots = {};
|
4
|
|
-var months = ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December'];
|
5
|
4
|
|
|
5
|
+// -----------------------------------------------------------------------------
|
|
6
|
+// ------------------ Date handling --------------------------------------------
|
|
7
|
+// -----------------------------------------------------------------------------
|
|
8
|
+
|
|
9
|
+Date.prototype.months = ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December'];
|
|
10
|
+
|
|
11
|
+/**
|
|
12
|
+ * Formats this date as year and two digit month, separated by a '-'.
|
|
13
|
+ *
|
|
14
|
+ * @return This date formatted as a key
|
|
15
|
+ */
|
|
16
|
+Date.prototype.getKey = function() {
|
|
17
|
+ return this.getFullYear() + '-' + (this.getMonth() < 9 ? '0' : '') + (this.getMonth() + 1);
|
|
18
|
+}
|
|
19
|
+
|
|
20
|
+/**
|
|
21
|
+ * Gets a textual representation of this date's month
|
|
22
|
+ *
|
|
23
|
+ * @return This date's month name
|
|
24
|
+ */
|
|
25
|
+Date.prototype.getDisplayMonth = function() {
|
|
26
|
+ return this.months[this.getMonth()];
|
|
27
|
+}
|
|
28
|
+
|
|
29
|
+/**
|
|
30
|
+ * Gets a textual representation of the range of months between this date and
|
|
31
|
+ * the specified other. It is assumed that the other date will occur after this.
|
|
32
|
+ *
|
|
33
|
+ * @param {Date} other The end date
|
|
34
|
+ * @return {String} A textual representation of the date range
|
|
35
|
+ */
|
|
36
|
+Date.prototype.getRangeText = function(other) {
|
|
37
|
+ if (this.getFullYear() == other.getFullYear() && this.getMonth() == other.getMonth()) {
|
|
38
|
+ return this.getDisplayMonth() + ' ' + this.getFullYear();
|
|
39
|
+ } else if (this.getFullYear() == other.getFullYear()) {
|
|
40
|
+ return this.getDisplayMonth() + '-' + other.getDisplayMonth() + ' ' + this.getFullYear();
|
|
41
|
+ } else {
|
|
42
|
+ return this.getDisplayMonth() + ' ' + this.getFullYear() + ' - ' + other.getDisplayMonth() + ' ' + other.getFullYear();
|
|
43
|
+ }
|
|
44
|
+}
|
|
45
|
+
|
|
46
|
+/**
|
|
47
|
+ * Gets a date object corresponding to the specified timestamp. If advanceToNext
|
|
48
|
+ * is specified, and the timestamp doesn't already correspond to the first of
|
|
49
|
+ * the month, the date is forwarded to the first day of the next month.
|
|
50
|
+ *
|
|
51
|
+ * @param {int} timestamp The timestamp to convert to a date
|
|
52
|
+ * @param {bool} advanceToNext Whether to advance to the 1st or not
|
|
53
|
+ * @return {Date} A corresponding date object
|
|
54
|
+ */
|
|
55
|
+function getDate(timestamp, advanceToNext) {
|
|
56
|
+ var date = new Date(timestamp);
|
|
57
|
+
|
|
58
|
+ if (advanceToNext && date.getDate() > 1) {
|
|
59
|
+ date.setDate(1);
|
|
60
|
+ date.getMonth() == 11 && date.setYear(date.getFullYear() + 1);
|
|
61
|
+ date.setMonth(date.getMonth() == 11 ? 0 : date.getMonth() + 1);
|
|
62
|
+ }
|
|
63
|
+
|
|
64
|
+ return date;
|
|
65
|
+}
|
|
66
|
+
|
|
67
|
+// -----------------------------------------------------------------------------
|
|
68
|
+// ------------------ Data handling --------------------------------------------
|
|
69
|
+// -----------------------------------------------------------------------------
|
|
70
|
+
|
|
71
|
+/**
|
|
72
|
+ * Calculates the sum of transactions belonging to each category within
|
|
73
|
+ * the specified data.
|
|
74
|
+ *
|
|
75
|
+ * @param data An array of transactions to include
|
|
76
|
+ * @param {bool} incoming True to tally income, false to tally expenses
|
|
77
|
+ * @return A mapping of categories to the sum of their transactions
|
|
78
|
+ */
|
|
79
|
+function getCategoryTotals(data, incoming) {
|
|
80
|
+ var catData = {};
|
|
81
|
+
|
|
82
|
+ $.each(data, function() {
|
|
83
|
+ trans = this;
|
|
84
|
+ var category = trans.Category ? trans.Category : 'Unsorted';
|
|
85
|
+
|
|
86
|
+ if (category != '(Ignored)' && incoming == trans.Amount > 0) {
|
|
87
|
+ if (!catData[category]) { catData[category] = 0; }
|
|
88
|
+ catData[category] += Math.abs(trans.Amount);
|
|
89
|
+ }
|
|
90
|
+ });
|
|
91
|
+
|
|
92
|
+ return catData;
|
|
93
|
+}
|
|
94
|
+
|
|
95
|
+/**
|
|
96
|
+ * Retrieves an array of transactions which occur between the specified two
|
|
97
|
+ * dates. This has a resolution of a month -- any data in the same month
|
|
98
|
+ * as the start date will be included, and any data after the month of the
|
|
99
|
+ * end date will be excluded.
|
|
100
|
+ *
|
|
101
|
+ * @param {Date} start The date to start including data at
|
|
102
|
+ * @param {Date} end The date to stop including data at
|
|
103
|
+ * @return An array of transactions between the two dates
|
|
104
|
+ */
|
|
105
|
+function getDataForRange(start, end) {
|
|
106
|
+ var include = false;
|
|
107
|
+ var included = [];
|
|
108
|
+ $.each(data, function(month, monthData) {
|
|
109
|
+ include |= month == start.getKey();
|
|
110
|
+
|
|
111
|
+ if (include) {
|
|
112
|
+ $.each(monthData, function(index, trans) {
|
|
113
|
+ included.push(trans);
|
|
114
|
+ });
|
|
115
|
+ }
|
|
116
|
+
|
|
117
|
+ include &= month != end.getKey();
|
|
118
|
+ });
|
|
119
|
+
|
|
120
|
+ return included;
|
|
121
|
+}
|
|
122
|
+
|
|
123
|
+// -----------------------------------------------------------------------------
|
|
124
|
+// ------------------ State handling -------------------------------------------
|
|
125
|
+// -----------------------------------------------------------------------------
|
|
126
|
+
|
|
127
|
+/**
|
|
128
|
+ * Update the page's state with the specified new state. This causes the state
|
|
129
|
+ * to be loaded into the user's history so they can use back and forward
|
|
130
|
+ * functionality in their browser. New state is merged with the old state.
|
|
131
|
+ *
|
|
132
|
+ * @param newState The new properties to add to the state
|
|
133
|
+ * @param invalidatedState An array of state keys to remove
|
|
134
|
+ */
|
|
135
|
+function setState(newState, invalidatedState) {
|
|
136
|
+ $.extend(true, state, newState);
|
|
137
|
+
|
|
138
|
+ $.each(invalidatedState, function(_, x) { delete state[x]; });
|
|
139
|
+
|
|
140
|
+ $.history.load(JSON.stringify(state));
|
|
141
|
+}
|
|
142
|
+
|
|
143
|
+/**
|
|
144
|
+ * Called when the page state changes (either via a call to $.history.load or
|
|
145
|
+ * by the user manually changing the fragment or going back or forward).
|
|
146
|
+ *
|
|
147
|
+ * @param {string} hash The new page fragment
|
|
148
|
+ */
|
|
149
|
+function handleStateChange(hash) {
|
|
150
|
+ var oldState = $.extend({}, state);
|
|
151
|
+
|
|
152
|
+ try {
|
|
153
|
+ state = JSON.parse(hash);
|
|
154
|
+ } catch (ex) {
|
|
155
|
+ state = {};
|
|
156
|
+ }
|
|
157
|
+
|
|
158
|
+ if (state.start && state.end && state.type) {
|
|
159
|
+ // Update the transaction table and pie charts
|
|
160
|
+ showSelectedMonths(state.start, state.end, state.type == 'income', state.type == 'expenses', state.categoryFilter, state.expanded);
|
|
161
|
+
|
|
162
|
+ // If the selection has changed, update the visual representation
|
|
163
|
+ (oldState.start != state.start || oldState.end != state.end) && plots.history.setSelection({ xaxis: { from: state.start, to: state.end }});
|
|
164
|
+ }
|
|
165
|
+}
|
|
166
|
+
|
|
167
|
+// -----------------------------------------------------------------------------
|
|
168
|
+
|
|
169
|
+/**
|
|
170
|
+ * Adds an 'alt' class to every other visible row in the specified table.
|
|
171
|
+ *
|
|
172
|
+ * @param table The table to be marked-up
|
|
173
|
+ */
|
6
|
174
|
function colourTableRows(table) {
|
7
|
175
|
$('tr', table).removeClass('alt');
|
8
|
176
|
$('tr:visible:even', table).addClass('alt');
|
9
|
177
|
}
|
10
|
178
|
|
|
179
|
+/**
|
|
180
|
+ * Shows a tooltip with the specified content at the given co-ordinates.
|
|
181
|
+ *
|
|
182
|
+ * @param {int} x The x co-ordinate to show the tooltip at
|
|
183
|
+ * @param {int} y The y co-ordinate to show the tooltip at
|
|
184
|
+ * @param contents The content to display in the tooltip element
|
|
185
|
+ */
|
11
|
186
|
function showTooltip(x, y, contents) {
|
12
|
187
|
$('<div id="tooltip">' + contents + '</div>').css( {
|
13
|
188
|
position: 'absolute',
|
|
@@ -20,10 +195,12 @@ function showTooltip(x, y, contents) {
|
20
|
195
|
}).appendTo("body").fadeIn(200);
|
21
|
196
|
}
|
22
|
197
|
|
23
|
|
-function getDateKey(date) {
|
24
|
|
- return date.getFullYear() + '-' + (date.getMonth() < 9 ? '0' : '') + (date.getMonth() + 1);
|
25
|
|
-}
|
26
|
|
-
|
|
198
|
+/**
|
|
199
|
+ * Called when the user clicks on the expand/contract toggle on a transaction
|
|
200
|
+ * line where similar entries have been merged.
|
|
201
|
+ *
|
|
202
|
+ * @param event The corresponding event
|
|
203
|
+ */
|
27
|
204
|
function expandLinkHandler(event) {
|
28
|
205
|
var text = $(this).text();
|
29
|
206
|
var expanded = text.substr(0, 2) == '(+';
|
|
@@ -44,110 +221,107 @@ function expandLinkHandler(event) {
|
44
|
221
|
return false;
|
45
|
222
|
}
|
46
|
223
|
|
47
|
|
-function setState(newState, invalidatedState) {
|
48
|
|
- $.extend(true, state, newState);
|
|
224
|
+/**
|
|
225
|
+ * Determines if the two transactions should be merged together. That is,
|
|
226
|
+ * whether the transactions have an identical description, type and category.
|
|
227
|
+ *
|
|
228
|
+ * @param a The first transaction
|
|
229
|
+ * @param b The second transaction
|
|
230
|
+ * @return True if the transactions should be merged, false otherwise
|
|
231
|
+ */
|
|
232
|
+function shouldMerge(a, b) {
|
|
233
|
+ return a.Description == b.Description && a.Type == b.Type && a.Category == b.Category;
|
|
234
|
+}
|
49
|
235
|
|
50
|
|
- $.each(invalidatedState, function(_, x) { delete state[x]; });
|
|
236
|
+/**
|
|
237
|
+ * Draws a pie chart of transactions by category.
|
|
238
|
+ *
|
|
239
|
+ * @param included An array of transactions to include in the chart
|
|
240
|
+ * @param incoming True to show income, false to show expenses
|
|
241
|
+ */
|
|
242
|
+function drawCategoryPieChart(included, incoming) {
|
|
243
|
+ var pieData = getCategoryTotals(included, incoming);
|
|
244
|
+ var seriesData = [];
|
|
245
|
+ $.each(pieData, function(category, amount) {
|
|
246
|
+ seriesData.push({ label: category + ' (' + Math.round(amount) + ')', data: amount });
|
|
247
|
+ });
|
51
|
248
|
|
52
|
|
- $.history.load(JSON.stringify(state));
|
|
249
|
+ seriesData.sort(function(a, b) { return b.data - a.data; });
|
|
250
|
+
|
|
251
|
+ plots.expense = $.plot($('#expense'), seriesData, {
|
|
252
|
+ series: { pie: { show: true, innerRadius: 0.5, highlight: { opacity: 0.5 } } },
|
|
253
|
+ grid: { clickable: true }
|
|
254
|
+ });
|
53
|
255
|
}
|
54
|
256
|
|
55
|
|
-function showSelectedMonths(start, end, incoming, outgoing, categoryFilter) {
|
|
257
|
+/**
|
|
258
|
+ * Displays transactions and draws a category pie chart for the specified
|
|
259
|
+ * date range. Note that dates have a granularity of a month.
|
|
260
|
+ *
|
|
261
|
+ * @param {int} start The timestamp to start including transactions from
|
|
262
|
+ * @param {int} end The timestamp to stop including transactions at
|
|
263
|
+ * @param {bool} incoming Whether or not to include incoming transactions (income)
|
|
264
|
+ * @param {bool} outgoing Whether or not to include outgoing transactions (expenses)
|
|
265
|
+ * @param {string} categoryFilter The category to filter transactions to (or null)
|
|
266
|
+ * @param expanded An object containing entries indicating which merged
|
|
267
|
+ * transactions should be shown as expanded
|
|
268
|
+ */
|
|
269
|
+function showSelectedMonths(start, end, incoming, outgoing, categoryFilter, expanded) {
|
56
|
270
|
$('#historytable tr.data').remove();
|
57
|
271
|
$('#historytable').show();
|
58
|
272
|
|
59
|
|
- var startDate = new Date(start), endDate = new Date(end);
|
60
|
|
-
|
61
|
|
- if (startDate.getDate() > 1) {
|
62
|
|
- startDate.setDate(1);
|
63
|
|
- startDate.getMonth() == 11 && startDate.setYear(startDate.getFullYear() + 1);
|
64
|
|
- startDate.setMonth(startDate.getMonth() == 11 ? 0 : startDate.getMonth() + 1);
|
65
|
|
- }
|
|
273
|
+ expanded = expanded || [];
|
66
|
274
|
|
67
|
|
- var startKey = getDateKey(startDate), endKey = getDateKey(endDate);
|
|
275
|
+ var startDate = getDate(start, 1), endDate = getDate(end);
|
68
|
276
|
|
69
|
|
- if (startKey == endKey) {
|
70
|
|
- $('#historytable h3').text('Transactions for ' + months[startDate.getMonth()] + ' ' + startDate.getFullYear());
|
71
|
|
- } else if (startDate.getFullYear() == endDate.getFullYear()) {
|
72
|
|
- $('#historytable h3').text('Transactions for ' + months[startDate.getMonth()] + '-' + months[endDate.getMonth()] + ' ' + startDate.getFullYear());
|
73
|
|
- } else {
|
74
|
|
- $('#historytable h3').text('Transactions for ' + months[startDate.getMonth()] + ' ' + startDate.getFullYear() + ' - ' + months[endDate.getMonth()] + ' ' + endDate.getFullYear());
|
75
|
|
- }
|
|
277
|
+ $('#historytable h3').text((categoryFilter ? categoryFilter + ' t' : 'T') + 'ransactions for ' + startDate.getRangeText(endDate));
|
76
|
278
|
|
77
|
|
- var pieData = {};
|
78
|
279
|
var table = $('#historytable table');
|
79
|
|
- var include = false;
|
80
|
280
|
var lastEntry = {};
|
81
|
281
|
var id = 0;
|
82
|
|
- $.each(data, function(month, monthData) {
|
83
|
|
- if (month == startKey) { include = true; }
|
|
282
|
+ var included = getDataForRange(startDate, endDate);
|
84
|
283
|
|
85
|
|
- if (include) {
|
86
|
|
- $.each(monthData, function(index, trans) {
|
87
|
|
- if (incoming != trans.Amount > 0) { return; }
|
88
|
|
-
|
89
|
|
- var category = trans.Category ? trans.Category : 'Unsorted';
|
90
|
|
-
|
91
|
|
- if (category != '(Ignored)') {
|
92
|
|
- if (!pieData[category]) { pieData[category] = 0; }
|
93
|
|
- pieData[category] += Math.abs(trans.Amount);
|
94
|
|
- }
|
95
|
|
-
|
96
|
|
- if (categoryFilter && categoryFilter != category) { return; }
|
97
|
|
-
|
98
|
|
- var tr = $('<tr/>').addClass('data').addClass('category' + category.replace(/[^a-zA-Z]*/g, '')).appendTo(table);
|
99
|
|
-
|
100
|
|
- if (lastEntry.Description == trans.Description && lastEntry.Type == trans.Type && lastEntry.Category == lastEntry.Category) {
|
101
|
|
- tr.hide();
|
102
|
|
-
|
103
|
|
- if (lastEntry.id) {
|
104
|
|
- var prefix = '(' + (state.expanded && state.expanded[lastEntry.id] ? '-' : '+');
|
105
|
|
- lastEntry.count++;
|
106
|
|
- $('span', lastEntry.tr).text(prefix + lastEntry.count + ')');
|
107
|
|
- } else {
|
108
|
|
- lastEntry.id = ++id;
|
109
|
|
- lastEntry.count = 1;
|
110
|
|
- var prefix = '(' + (state.expanded && state.expanded[lastEntry.id] ? '-' : '+');
|
111
|
|
- var a = $('<span>').addClass('link').text(prefix + '1)').appendTo($('td.desc', lastEntry.tr).append(' '));
|
112
|
|
- a.bind('click', { id: lastEntry.id, tr: lastEntry.tr }, expandLinkHandler);
|
113
|
|
- }
|
114
|
|
-
|
115
|
|
- lastEntry.Amount = Math.round(100 * (lastEntry.Amount + trans.Amount)) / 100;
|
116
|
|
- if (state.expanded && state.expanded[lastEntry.id]) {
|
117
|
|
- tr.show();
|
118
|
|
- } else {
|
119
|
|
- $('.amount', lastEntry.tr).text(lastEntry.Amount);
|
120
|
|
- }
|
121
|
|
-
|
122
|
|
- tr.addClass('collapsed hidden' + lastEntry.id);
|
123
|
|
-
|
124
|
|
- } else {
|
125
|
|
- lastEntry = $.extend({}, trans, {tr: tr});
|
126
|
|
- }
|
127
|
|
-
|
128
|
|
- $('<td/>').text(trans.Date.date.split(' ')[0]).appendTo(tr);
|
129
|
|
- $('<td/>').text(trans.Type ? trans.Type : 'Other').appendTo(tr);
|
130
|
|
- $('<td/>').text(trans.Category ? trans.Category : '').appendTo(tr);
|
131
|
|
- $('<td/>').addClass('desc').text(trans.Description).appendTo(tr);
|
132
|
|
- $('<td/>').addClass('amount').text(trans.Amount).appendTo(tr);
|
133
|
|
- });
|
134
|
|
- }
|
|
284
|
+ $.each(included, function() {
|
|
285
|
+ trans = this;
|
|
286
|
+ if (incoming != trans.Amount > 0) { return; }
|
135
|
287
|
|
136
|
|
- if (month == endKey) { include = false; }
|
137
|
|
- });
|
138
|
|
- colourTableRows(table);
|
|
288
|
+ var category = trans.Category ? trans.Category : 'Unsorted';
|
139
|
289
|
|
140
|
|
- var seriesData = [];
|
141
|
|
- $.each(pieData, function(category, amount) {
|
142
|
|
- seriesData.push({ label: category + ' (' + Math.round(amount) + ')', data: amount });
|
143
|
|
- });
|
|
290
|
+ if (categoryFilter && categoryFilter != category) { return; }
|
144
|
291
|
|
145
|
|
- seriesData.sort(function(a, b) { return b.data - a.data; });
|
|
292
|
+ var tr = $('<tr/>').addClass('data').addClass('category' + category.replace(/[^a-zA-Z]*/g, '')).appendTo(table);
|
146
|
293
|
|
147
|
|
- plots.expense = $.plot($('#expense'), seriesData, {
|
148
|
|
- series: { pie: { show: true, innerRadius: 0.5, highlight: { opacity: 0.5 } } },
|
149
|
|
- grid: { clickable: true }
|
|
294
|
+ if (shouldMerge(lastEntry, trans)) {
|
|
295
|
+ if (lastEntry.id) {
|
|
296
|
+ var prefix = '(' + (expanded[lastEntry.id] ? '-' : '+');
|
|
297
|
+ lastEntry.count++;
|
|
298
|
+ $('span', lastEntry.tr).text(prefix + lastEntry.count + ')');
|
|
299
|
+ } else {
|
|
300
|
+ lastEntry.id = ++id;
|
|
301
|
+ lastEntry.count = 1;
|
|
302
|
+ var prefix = '(' + (expanded[lastEntry.id] ? '-' : '+');
|
|
303
|
+ var a = $('<span>').addClass('link').text(prefix + '1)').appendTo($('td.desc', lastEntry.tr).append(' '));
|
|
304
|
+ a.bind('click', { id: lastEntry.id, tr: lastEntry.tr }, expandLinkHandler);
|
|
305
|
+ }
|
|
306
|
+
|
|
307
|
+ lastEntry.Amount = Math.round(100 * (lastEntry.Amount + trans.Amount)) / 100;
|
|
308
|
+
|
|
309
|
+ !expanded[lastEntry.id] && tr.hide() && $('.amount', lastEntry.tr).text(lastEntry.Amount);
|
|
310
|
+
|
|
311
|
+ tr.addClass('collapsed hidden' + lastEntry.id);
|
|
312
|
+ } else {
|
|
313
|
+ lastEntry = $.extend({}, trans, {tr: tr});
|
|
314
|
+ }
|
|
315
|
+
|
|
316
|
+ $('<td/>').text(trans.Date.date.split(' ')[0]).appendTo(tr);
|
|
317
|
+ $('<td/>').text(trans.Type ? trans.Type : 'Other').appendTo(tr);
|
|
318
|
+ $('<td/>').text(trans.Category ? trans.Category : '').appendTo(tr);
|
|
319
|
+ $('<td/>').addClass('desc').text(trans.Description).appendTo(tr);
|
|
320
|
+ $('<td/>').addClass('amount').text(trans.Amount).appendTo(tr);
|
150
|
321
|
});
|
|
322
|
+
|
|
323
|
+ colourTableRows(table);
|
|
324
|
+ drawCategoryPieChart(included, incoming);
|
151
|
325
|
}
|
152
|
326
|
|
153
|
327
|
$(function() {
|
|
@@ -178,7 +352,7 @@ $(function() {
|
178
|
352
|
transData[2].data.push([timestamp, sum[0] + sum[1]]);
|
179
|
353
|
min = Math.min(min, timestamp);
|
180
|
354
|
max = Math.max(max, timestamp);
|
181
|
|
- });
|
|
355
|
+ });
|
182
|
356
|
|
183
|
357
|
var catData = [];
|
184
|
358
|
$.each(categories, function(category, entries) {
|
|
@@ -199,6 +373,7 @@ $(function() {
|
199
|
373
|
|
200
|
374
|
var markings = [];
|
201
|
375
|
|
|
376
|
+ // Add a marking for each year division
|
202
|
377
|
var year = new Date(new Date(max).getFullYear(), 0);
|
203
|
378
|
while (year.getTime() > min) {
|
204
|
379
|
markings.push({ color: '#000', lineWidth: 1, xaxis: { from: year.getTime(), to: year.getTime() } });
|
|
@@ -231,7 +406,7 @@ $(function() {
|
231
|
406
|
grid: {
|
232
|
407
|
hoverable: true,
|
233
|
408
|
clickable: true,
|
234
|
|
- markings: markings
|
|
409
|
+ markings: markings
|
235
|
410
|
},
|
236
|
411
|
selection: { mode : "x" }
|
237
|
412
|
});
|
|
@@ -242,11 +417,11 @@ $(function() {
|
242
|
417
|
|
243
|
418
|
if (previousPoint == null || previousPoint.dataIndex != id.dataIndex || previousPoint.seriesIndex != id.seriesIndex) {
|
244
|
419
|
previousPoint = id;
|
245
|
|
-
|
|
420
|
+
|
246
|
421
|
$("#tooltip").remove();
|
247
|
422
|
var x = item.datapoint[0],
|
248
|
423
|
y = item.datapoint[1].toFixed(2);
|
249
|
|
-
|
|
424
|
+
|
250
|
425
|
var date = new Date(x);
|
251
|
426
|
|
252
|
427
|
var seriesTitles = ["Money in", "Money out", "Balance change"];
|
|
@@ -254,14 +429,14 @@ $(function() {
|
254
|
429
|
}
|
255
|
430
|
} else {
|
256
|
431
|
$("#tooltip").remove();
|
257
|
|
- previousPoint = null;
|
|
432
|
+ previousPoint = null;
|
258
|
433
|
}
|
259
|
434
|
});
|
260
|
435
|
|
261
|
436
|
$('#history').bind('plotselected', function(event, ranges) {
|
262
|
437
|
var startDate = parseInt(ranges.xaxis.from.toFixed());
|
263
|
438
|
var endDate = parseInt(ranges.xaxis.to.toFixed());
|
264
|
|
-
|
|
439
|
+
|
265
|
440
|
if (state.start != startDate || state.end != endDate || state.type != 'expenses') {
|
266
|
441
|
setState({ start: startDate, end: endDate, type: 'expenses' }, ['categoryFilter', 'expanded']);
|
267
|
442
|
}
|
|
@@ -277,20 +452,5 @@ $(function() {
|
277
|
452
|
setState({ categoryFilter: item.series.label.replace(/ \([0-9]+\)$/, '') }, ['expanded']);
|
278
|
453
|
});
|
279
|
454
|
|
280
|
|
- $.history.init(function(hash) {
|
281
|
|
- var oldState = $.extend({}, state);
|
282
|
|
-
|
283
|
|
- try {
|
284
|
|
- state = JSON.parse(hash);
|
285
|
|
- } catch (ex) {
|
286
|
|
- state = {};
|
287
|
|
- }
|
288
|
|
-
|
289
|
|
- var match = /start:([0-9]+);end:([0-9]+);type:(income|expenses)/.exec(hash);
|
290
|
|
-
|
291
|
|
- if (state.start && state.end && state.type) {
|
292
|
|
- showSelectedMonths(state.start, state.end, state.type == 'income', state.type == 'expenses', state.categoryFilter);
|
293
|
|
- (oldState.start != state.start || oldState.end != state.end) && plots.history.setSelection({ xaxis: { from: state.start, to: state.end }});
|
294
|
|
- }
|
295
|
|
- });
|
|
455
|
+ $.history.init(handleStateChange);
|
296
|
456
|
});
|