PageRenderTime 104ms CodeModel.GetById 25ms app.highlight 68ms RepoModel.GetById 1ms app.codeStats 1ms

/js/app.js

https://github.com/open-city/look-at-cook
JavaScript | 869 lines | 814 code | 33 blank | 22 comment | 101 complexity | 4bc2c936cc108b5c02a1a2b92ecac608 MD5 | raw file
  1(function(){
  2
  3    var app = {}
  4
  5    // Configuration variables to set
  6    startYear   = 1993;  // first year of budget data
  7    endYear     = 2017;  // last year of budget data
  8    activeYear  = 2016;  // default year to select
  9    debugMode   = false; // change to true for debugging message in the javascript console
 10    municipalityName = 'Cook County Budget'; // name of budget municipality 
 11    apropTitle  = 'Appropriations'; // label for first chart line
 12    expendTitle = 'Expenditures';   // label for second chart line
 13
 14    // CSV data source for budget data
 15    dataSource  = '/data/cook_county_budget_cleaned.csv';
 16    
 17    app.GlobalChartOpts = {
 18        apropColor:   '#264870',
 19        apropSymbol:  'circle',
 20        
 21        expendColor:  '#7d9abb',
 22        expendSybmol: 'square',
 23
 24        apropTitle:   apropTitle, 
 25        expendTitle:  expendTitle, 
 26        pointInterval: 365 * 24 * 3600 * 1000 // chart interval set to one year (in ms)
 27    }
 28
 29    app.MainChartModel = Backbone.Model.extend({
 30        setYear: function(year, index){
 31            var exp = this.get('expenditures');
 32            var approp = this.get('appropriations');
 33            var expChange = BudgetHelpers.calc_change(exp[index], exp[index -1]);
 34            var appropChange = BudgetHelpers.calc_change(approp[index], approp[index - 1]);
 35            this.set({
 36                'selectedExp': accounting.formatMoney(exp[index]),
 37                'selectedApprop': accounting.formatMoney(approp[index]),
 38                'expChange': expChange,
 39                'appropChange': appropChange,
 40                'viewYear': year,
 41                'prevYear': year - 1
 42            });
 43        }
 44    });
 45
 46    app.BreakdownRow = Backbone.Model.extend({
 47        yearIndex: null
 48    });
 49
 50    app.BreakdownColl = Backbone.Collection.extend({
 51        setRows: function(year, index){
 52            var self = this;
 53            $.each(this.models, function(i, row){
 54                var query = {}
 55                query[row.get('type')] = row.get('rowName')
 56                var summ = collection.getSummary(row.get('type'), query, year)
 57                row.set(summ);
 58                row.yearIndex = index;
 59            });
 60            
 61            var max_app = _.max(this.models, function(obj){return obj.get('appropriations')});
 62            var max_exp = _.max(this.models, function(obj){return obj.get('expenditures')});
 63            var maxes = [max_app.get('appropriations'), max_exp.get('expenditures')];
 64            this.maxNum = maxes.sort(function(a,b){return b-a})[0];
 65            $.each(this.models, function(i, row){
 66
 67                var apps = row.get('appropriations');
 68                var exps = row.get('expenditures');
 69
 70                var app_perc = parseFloat((apps/self.maxNum) * 100) + '%';
 71                var exp_perc = parseFloat((exps/self.maxNum) * 100) + '%';
 72                row.set({app_perc:app_perc, exp_perc:exp_perc});
 73            });
 74        }
 75    });
 76
 77    app.BudgetColl = Backbone.Collection.extend({
 78        startYear: startYear,
 79        endYear: endYear,
 80        activeYear: activeYear,
 81        updateYear: function(year, yearIndex){
 82            var expanded = [];
 83            $.each($('tr.expanded-content'), function(i, row){
 84                var name = $(row).prev().find('a.rowName').text();
 85                expanded.push(name);
 86                $(row).remove();
 87            })
 88            this.mainChartData.setYear(year, yearIndex);
 89            this.breakdownChartData.setRows(year, yearIndex);
 90            this.dataTable.fnDestroy();
 91            this.initDataTable();
 92            $.each(expanded, function(i, name){
 93                var sel = 'a.details:contains("' + name + '")';
 94                $(sel).first().trigger('click');
 95            })
 96        },
 97        updateTables: function(view, title, filter, year){
 98            // Various cleanup is needed when running this a second time.
 99            if(typeof this.mainChartView !== 'undefined'){
100                this.mainChartView.undelegateEvents();
101            }
102            if($('#breakdown-table-body').html() != ''){
103                $('#breakdown-table-body').empty();
104            }
105            if(typeof this.dataTable !== 'undefined'){
106                this.dataTable.fnClearTable();
107                this.dataTable.fnDestroy();
108            }
109            // Need to orientate the views to a top level
110            if(typeof this.hierarchy[view] !== 'undefined'){
111                this.topLevelView = view
112            } else {
113                this.bdView = view;
114            }
115            $('#secondary-title').text(this.topLevelView);
116            if (typeof year === 'undefined'){
117                year = this.activeYear;
118            }
119            var exp = [];
120            var approp = [];
121            var self = this;
122            var values = this.toJSON();
123            if (debugMode == true){
124                console.log("Update Tables");
125                console.log(this);
126            }
127            var incomingFilter = false;
128            if (typeof filter !== 'undefined'){
129                values = _.where(this.toJSON(), filter);
130                incomingFilter = true;
131            }
132            var yearRange = this.getYearRange()
133            $.each(yearRange, function(i, year){
134                exp.push(self.getTotals(values, expendTitle, year));
135                approp.push(self.getTotals(values, apropTitle, year));
136            });
137            var yearIndex = yearRange.indexOf(parseInt(year))
138            var selExp = exp[yearIndex];
139            var prevExp = exp[yearIndex - 1];
140            var expChange = BudgetHelpers.calc_change(selExp, prevExp);
141            var selApprop = approp[yearIndex];
142            var prevApprop = approp[yearIndex - 1];
143            var appropChange = BudgetHelpers.calc_change(selApprop, prevApprop);
144            this.mainChartData = new app.MainChartModel({
145                expenditures: exp,
146                appropriations: approp,
147                title: title,
148                viewYear: year,
149                prevYear: year - 1,
150                selectedExp: accounting.formatMoney(selExp),
151                selectedApprop: accounting.formatMoney(selApprop),
152                appropChange: appropChange,
153                expChange: expChange,
154                view: self.topLevelView
155            });
156            var bd = []
157            var chartGuts = this.pluck(view).getUnique();
158            var all_nums = []
159            $.each(chartGuts, function(i, name){
160                if (!incomingFilter){
161                    filter = {}
162                }
163                filter[view] = name;
164                var summary = self.getSummary(view, filter, year);
165                if (summary){
166                    var row = new app.BreakdownRow(summary);
167                    bd.push(row);
168                    all_nums.push(summary['expenditures']);
169                    all_nums.push(summary['appropriations']);
170                }
171            });
172
173            if (debugMode == true) console.log("all breakdown numbers: " + all_nums);
174            var maxNum = all_nums.sort(function(a,b){return b-a})[0];
175            this.breakdownChartData = new app.BreakdownColl(bd);
176            this.breakdownChartData.maxNum = maxNum;
177            if (debugMode == true) console.log("max bar chart num: " + maxNum);
178            this.breakdownChartData.forEach(function(row){
179                var exps = accounting.unformat(row.get('expenditures'));
180                var apps = accounting.unformat(row.get('appropriations'));
181                var exp_perc = parseFloat((exps/maxNum) * 100) + '%';
182                var app_perc = parseFloat((apps/maxNum) * 100) + '%';
183                row.set({app_perc:app_perc, exp_perc:exp_perc});
184                var rowView = new app.BreakdownSummary({model:row});
185                $('#breakdown-table-body').append(rowView.render().el);
186            });
187            this.mainChartView = new app.MainChartView({
188                model: self.mainChartData
189            });
190            this.initDataTable();
191        },
192        initDataTable: function(){
193            this.dataTable = $("#breakdown").dataTable({
194                "aaSorting": [[1, "desc"]],
195                "aoColumns": [
196                    null,
197                    {'sType': 'currency'},
198                    {'sType': 'currency'},
199                    null
200                ],
201                "bFilter": false,
202                "bInfo": false,
203                "bPaginate": false,
204                "bRetrieve": true,
205                "bAutoWidth": false
206            });
207        },
208        bootstrap: function(init, year){
209            var self = this;
210            this.spin('#main-chart', 'large');
211
212            $('#download-button').attr('href', dataSource);
213            $.when($.get(dataSource)).then(
214                function(data){
215                    var json = $.csv.toObjects(data);
216                    if (debugMode == true){
217                        console.log("Data source to object");
218                        console.log(data);
219                    }
220                    var loadit = []
221                    $.each(json, function(i, j){
222                        if (debugMode == true){
223                            console.log("Process row");
224                            console.log(j);
225                        }
226                        j['Fund Slug'] = BudgetHelpers.convertToSlug(j['Fund']);
227                        j['Department Slug'] = BudgetHelpers.convertToSlug(j['Department']);
228                        j['Control Officer Slug'] = BudgetHelpers.convertToSlug(j['Control Officer']);
229                        loadit.push(j)
230                    });
231                    self.reset(loadit);
232                    if (debugMode == true){
233                        console.log("Reset loadit");
234                        console.log(loadit);
235                    }
236                    self.hierarchy = {
237                        Fund: ['Fund', 'Department'],
238                        "Control Officer": ['Control Officer', 'Department']
239                    }
240                    if (typeof init === 'undefined'){
241                        self.topLevelView = 'Fund';
242                        if (!year){
243                            year = activeYear;
244                        }
245                        self.updateTables('Fund', municipalityName, undefined, year);
246                    } else {
247                        self.topLevelView = init[0];
248                        var lowerView = init[0];
249                        var name = init[1];
250                        var filter = {}
251                        var key = init[0] + ' Slug'
252                        filter[key] = name;
253                        var title = self.findWhere(filter).get(init[0])
254                        if (init.length == 2){
255                            lowerView = 'Department';
256                        }
257                        if(init.length > 2){
258                            name = init[2];
259                            lowerView = 'Expense Line';
260                            filter['Department Slug'] = name;
261                            title = self.findWhere(filter).get('Department');
262                        }
263                        self.updateTables(lowerView, title, filter, year);
264                    }
265                    // self.searchView = new app.SearchView();
266                }
267            );
268        },
269        spin: function(element, option){
270            // option is either size of spinner or false to cancel it
271            $(element).spin(option);
272        },
273        // Returns an array of valid years.
274        getYearRange: function(){
275            return Number.range(this.startYear, this.endYear + 1);
276        },
277        reduceTotals: function(totals){
278            return totals.reduce(function(a,b){
279              var int_a = parseFloat(a);
280              var int_b = parseFloat(b);
281              return int_a + int_b;
282            });
283        },
284
285        // Returns a total for a given category and year
286        // Example: "Expenditures 1995"
287        getTotals: function(values, category, year){
288            var all = _.pluck(values, category + ' ' + year);
289            return this.reduceTotals(all);
290        },
291        getChartTotals: function(category, rows, year){
292            var totals = [];
293            $.each(rows, function(i, row){
294                var attr = category + ' ' + year
295                var val = row.get(attr);
296                totals.push(parseInt(val));
297            });
298            return totals;
299        },
300        getSummary: function(view, query, year){
301            if (typeof year === 'undefined'){
302                year = this.activeYear;
303            }
304            var guts = this.where(query);
305            if (guts.length < 1) {
306                return null;
307            }
308            var summary = {};
309            var self = this;
310            var exp = self.getChartTotals(expendTitle, guts, year);
311            var approp = self.getChartTotals(apropTitle, guts, year);
312            var prevExp = self.getChartTotals(expendTitle, guts, year - 1);
313            var prevApprop = self.getChartTotals(apropTitle, guts, year - 1);
314            var expChange = BudgetHelpers.calc_change(self.reduceTotals(exp), self.reduceTotals(prevExp));
315            var appropChange = BudgetHelpers.calc_change(self.reduceTotals(approp), self.reduceTotals(prevApprop));
316            var self = this;
317            $.each(guts, function(i, item){
318                summary['rowName'] = item.get(view);
319                summary['prevYear'] = year - 1;
320                summary['year'] = year;
321                summary['description'] = item.get(view + ' Description');
322                summary['expenditures'] = self.reduceTotals(exp);
323                summary['appropriations'] = self.reduceTotals(approp);
324                summary['expChange'] = expChange;
325                summary['appropChange'] = appropChange;
326                summary['rowId'] = item.get(view + ' ID');
327                summary['type'] = view
328                // 'Link to Website' column refers to department website, not fund or control officer
329                if (view == 'Department'){
330                    summary['link'] = item.get('Link to Website');
331                }
332                var hierarchy = self.hierarchy[self.topLevelView]
333                var ranking = hierarchy.indexOf(view)
334                if (ranking == 0){
335                    summary['child'] = hierarchy[1];
336                    summary['parent_type'] = null;
337                } else if(ranking == 1){
338                    summary['child'] = hierarchy[2];
339                    summary['parent_type'] = hierarchy[0];
340                } else if(ranking == 2) {
341                    summary['child'] = null;
342                    summary['parent_type'] = hierarchy[1];
343                }
344                if(summary['parent_type']){
345                    summary['parent'] = self.mainChartData.get('title')
346                }
347                summary['slug'] = item.get(view + ' Slug');
348            });
349            if (typeof summary['expenditures'] !== 'undefined'){
350                return summary
351            } else {
352                return null
353            }
354        }
355    });
356
357    app.MainChartView = Backbone.View.extend({
358        el: $('#main-chart'),
359
360        // The bulk of the chart options are defined in the budget_highcharts.js file
361        // and attached to the window over there. Dunno if that's the best approach but it works
362        chartOpts: window.mainChartOpts,
363
364        events: {
365            'click .breakdown-choice': 'breakIt'
366        },
367
368        // Render the view when you initialize it.
369        initialize: function(){
370            this._modelBinder = new Backbone.ModelBinder();
371            this.render();
372            this.updateCrumbs();
373            this.model.on('change', function(model){
374                if(!model.get('appropChange')){
375                    $('.main-approp').hide();
376                } else {
377                    $('.main-approp').show();
378                }
379                if(!model.get('expChange')){
380                    $('.main-exp').hide();
381                } else {
382                    $('.main-exp').show();
383                }
384            });
385        },
386        updateCrumbs: function(){
387            var links = ['<a href="/">'+municipalityName+'</a>'];
388            if(Backbone.history.fragment){
389                var parts = Backbone.history.fragment;
390                if (parts.indexOf('?') >= 0){
391                    var idx = parts.indexOf('?');
392                    parts = parts.slice(0,idx).split('/')
393                } else {
394                    parts = parts.split('/');
395                }
396                var crumbs = parts.slice(1, parts.length);
397                var topView = collection.topLevelView;
398                var query = {}
399                $.each(crumbs, function(i, crumb){
400                    var link = '<a href="#' + parts.slice(0,i+2).join('/') + '">';
401                    if(i==0){
402                        var key = topView + ' Slug';
403                        query[key] = crumb;
404                        link += collection.findWhere(query).get(topView);
405                    }
406                    if(i==1){
407                        query['Department Slug'] = crumb;
408                        link += collection.findWhere(query).get('Department');
409                    }
410                    if(i==2){
411                        query['Expense Line Slug'] = crumb;
412                        link += collection.findWhere(query).get('Expense Line');
413                    }
414                    link += '</a>';
415                    links.push(link);
416                });
417            }
418            $('#breadcrumbs').html(links.join(' > '));
419        },
420        // This is where the magic happens. Grab the template from the template_cache function
421        // at the top of this file and then update the chart with what's passed in as the model.
422        render: function(){
423            this.$el.html(BudgetHelpers.template_cache('mainChart', {model: this.model}));
424            this._modelBinder.bind(this.model, this.el, {
425                viewYear: '.viewYear',
426                prevYear: '.prevYear',
427                selectedExp: '.expenditures',
428                selectedApprop: '.appropriations',
429                expChange: '.expChange',
430                appropChange: '.appropChange'
431            });
432            this.updateChart(this.model, this.model.get('viewYear'));
433            return this;
434        },
435        updateChart: function(data, year){
436            if (typeof this.highChart !== 'undefined'){
437                delete this.highChart;
438            }
439            var exps = jQuery.extend(true, [], data.get('expenditures'));
440            var approps = jQuery.extend(true, [], data.get('appropriations'));
441
442            if (debugMode == true) {
443                console.log('main chart data:')
444                console.log(exps);
445                console.log(approps);
446            }
447
448            var exp = [];
449            var approp = [];
450            $.each(exps, function(i, e){
451                if (isNaN(e))
452                    e = null;
453                else
454                    e = parseInt(e);
455                exp.push(e);
456            })
457            $.each(approps, function(i, e){
458                if (isNaN(e))
459                    e = null;
460                else
461                    e = parseInt(e);
462                approp.push(e);
463            });
464            var minValuesArray = $.grep(approp.concat(exp),
465              function(val) { return val != null; });
466            var globalOpts = app.GlobalChartOpts;
467            this.chartOpts.plotOptions.area.pointInterval = globalOpts.pointInterval;
468            this.chartOpts.plotOptions.area.pointStart = Date.UTC(collection.startYear, 1, 1);
469            this.chartOpts.plotOptions.series.point.events.click = this.pointClick;
470            this.chartOpts.series = [{
471                color: globalOpts.apropColor,
472                data: approp,
473                marker: {
474                    radius: 6,
475                    symbol: globalOpts.apropSymbol
476                },
477                name: globalOpts.apropTitle
478              }, {
479                color: globalOpts.expendColor,
480                data: exp,
481                marker: {
482                    radius: 6,
483                    symbol: globalOpts.expendSybmol
484                },
485                name: globalOpts.expendTitle
486            }];
487            this.chartOpts.yAxis.min = Math.min.apply( Math, minValuesArray )
488            var selectedYearIndex = year - collection.startYear;
489            this.highChart = new Highcharts.Chart(this.chartOpts, function(){
490                this.series[0].data[selectedYearIndex].select(true, true);
491                this.series[1].data[selectedYearIndex].select(true, true);
492            });
493        },
494        pointClick: function(e){
495            $("#readme").fadeOut("fast");
496            $.cookie("budgetbreakdownreadme", "read", { expires: 7 });
497            var x = this.x,
498            y = this.y,
499            selected = !this.selected,
500            index = this.series.index;
501            this.select(selected, false);
502
503            $.each($('.budget-chart'), function(i, chart){
504              var sel_points = $(chart).highcharts().getSelectedPoints();
505              $.each(sel_points, function(i, point){
506                  point.select(false);
507              });
508              $.each($(chart).highcharts().series, function(i, serie){
509                  $(serie.data).each(function(j, point){
510                    if(x === point.x && point.y != null) {
511                      point.select(selected, true);
512                    }
513                  });
514              });
515            });
516            var clickedYear = new Date(x).getFullYear();
517            var yearIndex = this.series.processedYData.indexOf(y);
518            var hash = window.location.hash;
519            if(hash.indexOf('?') >= 0){
520                hash = hash.slice(0, hash.indexOf('?'));
521            }
522            app_router.navigate(hash + '?year=' + clickedYear);
523            collection.updateYear(clickedYear, yearIndex);
524            $.each($('.bars').children(), function(i, bar){
525                var width = $(bar).text();
526                $(bar).css('width', width);
527            });
528        },
529        breakIt: function(e){
530            e.preventDefault();
531            var view = $(e.currentTarget).data('choice');
532            var year = window.location.hash.split('=')[1];
533            if (year==undefined){
534                year = activeYear;
535            }
536            app_router.navigate('?year=' + year);
537            collection.updateTables(view, municipalityName, undefined, year);
538        }
539    })
540
541    // Breakdown Chart view. Does a lot the same kind of things as the main chart view
542    app.BreakdownSummary = Backbone.View.extend({
543        tagName: 'tr',
544        className: 'rowId',
545        detailShowing: false,
546        events: {
547            'click .details': 'details'
548        },
549        initialize: function(){
550            this._modelBinder = new Backbone.ModelBinder();
551            var self = this;
552            this.model.on('change', function(model){
553                var sel = '#' + model.get('slug') + '-selected-chart';
554                var exp = accounting.unformat(model.get('expenditures'));
555                var approp = accounting.unformat(model.get('appropriations'));
556                if((exp + approp) == 0){
557                    $(self.el).hide();
558                    if($(self.el).next().is(':visible')){
559                        $(self.el).next().hide();
560                    }
561                } else {
562                    $(self.el).show();
563                }
564                if(!model.get('appropChange')){
565                    $(sel).parent().find('.sparkline-budgeted').hide();
566                } else {
567                    $(sel).parent().find('.sparkline-budgeted').show();
568                }
569                if(!model.get('expChange')){
570                    $(sel).parent().find('.sparkline-spent').hide();
571                } else {
572                    $(sel).parent().find('.sparkline-spent').show();
573                }
574            });
575        },
576        render: function(){
577            this.$el.html(BudgetHelpers.template_cache('breakdownSummary', {model:this.model}));
578            this._modelBinder.bind(this.model, this.el, {
579                expenditures: {selector: '[name="expenditures"]', converter: this.moneyChanger},
580                appropriations: {selector: '[name="appropriations"]', converter: this.moneyChanger},
581                app_perc: {selector: '[name=app_perc]'},
582                exp_perc: {selector: '[name=exp_perc]'}
583            });
584            return this;
585        },
586        moneyChanger: function(direction, value){
587            return accounting.formatMoney(value);
588        },
589        details: function(e){
590            e.preventDefault();
591            if (typeof this.detailView !== 'undefined'){
592                this.detailView.undelegateEvents();
593            }
594            if (this.$el.next().hasClass('expanded-content')){
595                this.$el.next().remove();
596                this.$el.find('img').attr('src', 'images/expand.png')
597            } else {
598                var filter = {};
599                var type = this.model.get('type');
600                filter[type] = this.model.get('rowName');
601                var parent_type = this.model.get('parent_type');
602                if(parent_type){
603                    filter[parent_type] = this.model.get('parent');
604                }
605                var expenditures = [];
606                var appropriations = [];
607                $.each(collection.getYearRange(), function(i, year){
608                    var exps = collection.where(filter)
609                    var exp = collection.getChartTotals(expendTitle, exps, year);
610                    if (exp.length > 1){
611                        expenditures.push(collection.reduceTotals(exp));
612                    } else {
613                        expenditures.push(parseFloat(exp[0]));
614                    }
615                    var apps = collection.where(filter);
616                    var approp = collection.getChartTotals(apropTitle, apps, year);
617                    if (approp.length > 1){
618                        appropriations.push(collection.reduceTotals(approp));
619                    } else {
620                        appropriations.push(parseFloat(approp[0]));
621                    }
622                });
623
624                this.model.allExpenditures = expenditures;
625                this.model.allAppropriations = appropriations;
626                this.detailView = new app.BreakdownDetail({model:this.model});
627                this.detailView.render().$el.insertAfter(this.$el);
628                this.detailView.updateChart();
629                this.$el.find('img').attr('src', 'images/collapse.png')
630            }
631        }
632    })
633
634    app.BreakdownDetail = Backbone.View.extend({
635        tagName: 'tr',
636        className: 'expanded-content',
637        chartOpts: window.sparkLineOpts,
638
639        events: {
640            'click .breakdown': 'breakdownNav'
641        },
642        initialize: function(){
643            this._modelBinder = new Backbone.ModelBinder();
644        },
645        render: function(){
646            this.$el.html(BudgetHelpers.template_cache('breakdownDetail', {model: this.model}));
647            this._modelBinder.bind(this.model, this.el, {
648                prevYear: '.prevYear',
649                expChange: '.expChange',
650                appropChange: '.appropChange'
651            });
652            return this;
653        },
654
655        breakdownNav: function(e){
656            var filter = {}
657            var typeView = this.model.get('type');
658            filter[typeView] = this.model.get('rowName')
659            var path = this.model.get('slug');
660            if (this.model.get('parent')){
661                var hierarchy = collection.hierarchy[collection.topLevelView]
662                var type_pos = hierarchy.indexOf(typeView)
663                var parent_type = hierarchy[type_pos - 1];
664                filter[parent_type] = this.model.get('parent');
665                path = BudgetHelpers.convertToSlug(this.model.get('parent')) + '/' + this.model.get('slug')
666            }
667            collection.updateTables(this.model.get('child'), this.model.get('rowName'), filter, this.model.get('year'));
668            document.title = document.title + ' | ' + this.model.get('rowName');
669            $('#secondary-title').text(this.model.get('child'));
670            var pathStart = null;
671            if(collection.topLevelView == 'Fund'){
672                pathStart = 'fund-detail/';
673            } else {
674                pathStart = 'control-officer-detail/';
675            }
676            $('html, body').animate({
677                scrollTop: $('#breadcrumbs').offset().top
678            });
679            if (debugMode == true) {
680                console.log('navigating ...')
681                console.log(pathStart);
682                console.log(path);
683                console.log(this.model.get('year'));
684
685            }
686            app_router.navigate(pathStart + path + '?year=' + this.model.get('year'));
687            collection.mainChartView.updateCrumbs();
688        },
689
690        updateChart: function(){
691            if (typeof this.highChart !== 'undefined'){
692                delete this.highChart;
693            }
694            var data = this.model;
695            var exp = [];
696            var approp = [];
697            $.each(data.allExpenditures, function(i, e){
698                if (isNaN(e)){
699                    e = null;
700                }
701                exp.push(e);
702            })
703            $.each(data.allAppropriations, function(i, e){
704                if (isNaN(e)){
705                    e = null;
706                }
707                approp.push(e);
708            });
709            var minValuesArray = $.grep(approp.concat(exp),
710              function(val) { return val != null; });
711            if (debugMode == true){
712                console.log("minValuesArray");
713                console.log(minValuesArray);
714            }
715            var globalOpts = app.GlobalChartOpts;
716            this.chartOpts.chart.renderTo = data.get('slug') + "-selected-chart";
717            this.chartOpts.chart.marginBottom = 20;
718            this.chartOpts.plotOptions.area.pointInterval = globalOpts.pointInterval
719            this.chartOpts.plotOptions.area.pointStart = Date.UTC(collection.startYear, 1, 1)
720            this.chartOpts.yAxis.min = Math.min.apply( Math, minValuesArray )
721            this.chartOpts.plotOptions.series.point.events.click = this.pointClick;
722            this.chartOpts.series = [{
723                color: globalOpts.apropColor,
724                data: approp,
725                marker: {
726                  radius: 4,
727                  symbol: globalOpts.apropSymbol
728                },
729                name: globalOpts.apropTitle
730              }, {
731                color: globalOpts.expendColor,
732                data: exp,
733                marker: {
734                  radius: 5,
735                  symbol: globalOpts.expendSybmol
736                },
737                name: globalOpts.expendTitle
738              }]
739            // select current year
740            var selectedYearIndex = this.model.get('year') - collection.startYear;
741            this.highChart = new Highcharts.Chart(this.chartOpts, function(){
742                this.series[0].data[selectedYearIndex].select(true, true);
743                this.series[1].data[selectedYearIndex].select(true, true);
744            });
745        },
746
747        // Handler for the click events on the points on the chart
748        pointClick: function(e){
749            $("#readme").fadeOut("fast");
750            $.cookie("budgetbreakdownreadme", "read", { expires: 7 });
751            var x = this.x,
752            y = this.y,
753            selected = !this.selected,
754            index = this.series.index;
755            this.select(selected, false);
756            var active_chart;
757            $.each($('.budget-chart'), function(i, chart){
758              var sel_points = $(chart).highcharts().getSelectedPoints();
759              $.each(sel_points, function(i, point){
760                  point.select(false);
761              });
762              $.each($(chart).highcharts().series, function(i, serie){
763                  $(serie.data).each(function(j, point){
764                    if(x === point.x && point.y != null) {
765                      active_chart = chart;
766                      point.select(selected, true);
767                    }
768                  });
769              });
770            });
771            var clickedYear = new Date(x).getFullYear();
772            var yearIndex = this.series.processedYData.indexOf(y);
773            var hash = window.location.hash;
774            if(hash.indexOf('?') >= 0){
775                hash = hash.slice(0, hash.indexOf('?'));
776            }
777            app_router.navigate(hash + '?year=' + clickedYear);
778            collection.updateYear(clickedYear, yearIndex);
779            $.each($('.bars').children(), function(i, bar){
780                var width = $(bar).text();
781                $(bar).css('width', width);
782            });
783        }
784    });
785
786    app.SearchView = Backbone.View.extend({
787        el: $('#search-form'),
788        initialize: function(){
789            var search_options = {
790                keys: ['Expense Line'],
791                threshold: 0.4
792            }
793            this.Search = new Fuse(collection.toJSON(), search_options);
794            this.render();
795        },
796        events: {
797            'click #search': 'engage'
798        },
799        render: function(){
800            this.$el.html(BudgetHelpers.template_cache('search'));
801        },
802        engage: function(e){
803            e.preventDefault();
804            var input = $(e.currentTarget).parent().prev();
805            var term = $(input).val();
806            var results = this.Search.search(term);
807            if (debugMode == true){
808                console.log("results");
809                console.log(results);
810            }
811        }
812    });
813
814    app.Router = Backbone.Router.extend({
815        // Maybe the thing to do here is to construct a separate route for
816        // the two different top level views. So, fund-detail and control-officer-detail
817        // or something. That would require making sure the correct route is
818        // triggered when links are clicked. Not impossible but probably cleaner
819        routes: {
820            "fund-detail/:topName(/:secondName)": "fundDetailRoute",
821            "control-officer-detail/:topName(/:secondName)": "controlDetailRoute",
822            "(?year=:year)": "defaultRoute"
823        },
824        initialize: function(options){
825            this.collection = options.collection;
826        },
827        defaultRoute: function(year){
828            $('#secondary-title').text('Fund');
829            var init = undefined;
830            this.collection.bootstrap(init, year);
831        },
832        fundDetailRoute: function(topName, secondName){
833            var initYear = this.getInitYear('Fund', topName, secondName);
834            var init = initYear[0];
835            var year = initYear[1];
836            this.collection.bootstrap(init, year);
837        },
838        controlDetailRoute: function(topName, secondName){
839            var initYear = this.getInitYear('Control Officer', topName, secondName);
840            var init = initYear[0];
841            var year = initYear[1];
842            this.collection.bootstrap(init, year);
843        },
844        getInitYear: function(view, topName, secondName){
845            var init = [view];
846            var top = topName;
847            var idx = topName.indexOf('?');
848            var year = undefined;
849            if (idx >= 0){
850                top = topName.slice(0, idx);
851                year = topName.slice(idx+1, topName.length).replace('year=', '');
852            }
853            init.push(top);
854            if(secondName){
855                var second = secondName;
856                var idx = secondName.indexOf('?');
857                if (idx >= 0){
858                    second = secondName.slice(0, idx);
859                    year = secondName.slice(idx+1, secondName.length).replace('year=', '');
860                }
861                init.push(second);
862            }
863            return [init, year]
864        }
865    });
866    var collection = new app.BudgetColl();
867    var app_router = new app.Router({collection: collection});
868    Backbone.history.start();
869})()