#167 Refactoring controller's logic to create better Percentage graphs
#167 Refactoring controller's logic to create better Percentage graphs

--- a/ckanext/ga_report/controller.py
+++ b/ckanext/ga_report/controller.py
@@ -411,49 +411,52 @@
     if data==[]:
         return data
     # Create a consistent x-axis
-    num_points = [ len(package['data']) for package in data ]
+    num_points = [ len(series['data']) for series in data ]
     ideal_index = num_points.index( max(num_points) )
     x_axis = [ point['x'] for point in data[ideal_index]['data'] ]
-    for package in data:
-        xs = [ point['x'] for point in package['data'] ]
+    for series in data:
+        xs = [ point['x'] for point in series['data'] ]
         assert set(xs).issubset( set(x_axis) ), (xs, x_axis)
         # Zero pad any missing values
         for x in set(x_axis).difference(set(xs)):
-            package['data'].append( {'x':x, 'y':0} )
-        assert len(package['data'])==len(x_axis), (len(package['data']),len(x_axis),package['data'],x_axis,set(x_axis).difference(set(xs)))
+            series['data'].append( {'x':x, 'y':0} )
+        assert len(series['data'])==len(x_axis), (len(series['data']),len(x_axis),series['data'],x_axis,set(x_axis).difference(set(xs)))
     if percentageMode:
+        def get_totals(series_list):
+            totals = {}
+            for series in series_list:
+                for point in series['data']:
+                    totals[point['x']] = totals.get(point['x'],0) + point['y']
+            lengths = [ len(series['data']) for series in series_list ]
+            assert len(set(lengths))==1
+            assert lengths[0] == len(totals)
+            return totals
         # Transform data into percentage stacks
-        totals = {}
-        for x in x_axis:
-            for package in data:
-                for point in package['data']:
-                    totals[ point['x'] ] = totals.get(point['x'],0) + point['y']
+        totals = get_totals(data)
         # Roll insignificant series into a catch-all
         THRESHOLD = 0.01
-        significant_series = []
-        for package in data:
-            for point in package['data']:
+        raw_data = data
+        data = []
+        for series in raw_data:
+            for point in series['data']:
                 fraction = float(point['y']) / totals[point['x']]
-                if fraction>THRESHOLD and not (package in significant_series):
-                    significant_series.append(package)
-        temp = {}
-        for package in data:
-            if package in significant_series: continue
-            for point in package['data']:
-                temp[point['x']] = temp.get(point['x'],0) + point['y']
-        catch_all = { 'name':'Other','data': [ {'x':x,'y':y} for x,y in temp.items() ] }
-        # Roll insignificant series into one
-        data = significant_series
-        data.append(catch_all)
+                if not (series in data) and fraction>THRESHOLD:
+                    data.append(series)
+        # Overwrite data with a set of intereting series
+        others = [ x for x in raw_data if not (x in data) ]
+        data.append({ 
+            'name':'Other',
+            'data': [ {'x':x,'y':y} for x,y in get_totals(others).items() ] 
+            })
         # Turn each point into a percentage
-        for package in data:
-            for point in package['data']:
+        for series in data:
+            for point in series['data']:
                 point['y'] = (point['y']*100) / totals[point['x']]
     # Sort the points
-    for package in data:
-        package['data'] = sorted( package['data'], key=lambda x:x['x'] )
+    for series in data:
+        series['data'] = sorted( series['data'], key=lambda x:x['x'] )
         # Strip the latest month's incomplete analytics
-        package['data'] = package['data'][:-1]
+        series['data'] = series['data'][:-1]
     return data
 
 

--- a/ckanext/ga_report/public/scripts/ckanext_ga_reports.js
+++ b/ckanext/ga_report/public/scripts/ckanext_ga_reports.js
@@ -1,9 +1,18 @@
-
 var CKAN = CKAN || {};
 CKAN.GA_Reports = {};
 
 CKAN.GA_Reports.render_rickshaw = function( css_name, data, mode, colorscheme ) {
     var graphLegends = $('#graph-legend-container');
+
+    if (!Modernizr.svg) {
+        $("#chart_"+css_name)
+          .html( '<div class="alert">Your browser does not support vector graphics. No graphs can be rendered.</div>')
+          .closest('.rickshaw_chart_container').css('height',50);
+        var myLegend = $('<div id="legend_'+css_name+'"/>')
+          .html('(Graph cannot be rendered)')
+          .appendTo(graphLegends);
+        return;
+    }
     var myLegend = $('<div id="legend_'+css_name+'"/>').appendTo(graphLegends);
 
     var palette = new Rickshaw.Color.Palette( { scheme: colorscheme } );
@@ -24,20 +33,15 @@
         graph: graph,
         orientation: 'left',
         tickFormat: Rickshaw.Fixtures.Number.formatKMBT,
-        element: document.getElementById('y_axis_'+css_name),
+        element: document.getElementById('y_axis_'+css_name)
     } );
     var legend = new Rickshaw.Graph.Legend( {
         element: document.querySelector('#legend_'+css_name),
         graph: graph
     } );
-    var hoverDetail = new Rickshaw.Graph.HoverDetail( {
+    var shelving = new Rickshaw.Graph.Behavior.Series.Toggle( {
       graph: graph,
-      formatter: function(series, x, y) {
-        var date = '<span class="date">' + new Date(x * 1000).toUTCString() + '</span>';
-        var swatch = '<span class="detail_swatch" style="background-color: ' + series.color + '"></span>';
-        var content = swatch + series.name + ": " + parseInt(y) + '<br>' + date;
-        return content;
-      }
+      legend: legend
     } );
     graph.render();
 };

--- /dev/null
+++ b/ckanext/ga_report/public/scripts/rickshaw_ie7_shim.js
@@ -1,1 +1,109 @@
+/* 
+ * Collection of shims to allow d3 and Rickshaw to load, error-free
+ * (but ultimately unusable) on Internet Explorer 7. The browser's
+ * API lacks several crucial functions which these libraries depend
+ * upon to load; we try to hide these errors from the user.
+ *
+ * With thanks to Array functions from:
+ * http://stackoverflow.com/questions/2790001/fixing-javascript-array-functions-in-internet-explorer-indexof-foreach-etc
+ *
+ * Use (Modernizr.svg==true) to detect whether it's okay to draw a graph.
+ */
+'use strict';
 
+window.Element = window.Element || {'prototype': {}};
+window.CSSStyleDeclaration = window.CSSStyleDeclaration || {'prototype':{}};
+
+// Add ECMA262-5 method binding if not supported natively
+//
+if (!('bind' in Function.prototype)) {
+    Function.prototype.bind= function(owner) {
+        var that= this;
+        if (arguments.length<=1) {
+            return function() {
+                return that.apply(owner, arguments);
+            };
+        } else {
+            var args= Array.prototype.slice.call(arguments, 1);
+            return function() {
+                return that.apply(owner, arguments.length===0? args : args.concat(Array.prototype.slice.call(arguments)));
+            };
+        }
+    };
+}
+
+// Add ECMA262-5 string trim if not supported natively
+//
+if (!('trim' in String.prototype)) {
+    String.prototype.trim= function() {
+        return this.replace(/^\s+/, '').replace(/\s+$/, '');
+    };
+}
+
+// Add ECMA262-5 Array methods if not supported natively
+//
+if (!('indexOf' in Array.prototype)) {
+    Array.prototype.indexOf= function(find, i /*opt*/) {
+        if (i===undefined) i= 0;
+        if (i<0) i+= this.length;
+        if (i<0) i= 0;
+        for (var n= this.length; i<n; i++)
+            if (i in this && this[i]===find)
+                return i;
+        return -1;
+    };
+}
+if (!('lastIndexOf' in Array.prototype)) {
+    Array.prototype.lastIndexOf= function(find, i /*opt*/) {
+        if (i===undefined) i= this.length-1;
+        if (i<0) i+= this.length;
+        if (i>this.length-1) i= this.length-1;
+        for (i++; i-->0;) /* i++ because from-argument is sadly inclusive */
+            if (i in this && this[i]===find)
+                return i;
+        return -1;
+    };
+}
+if (!('forEach' in Array.prototype)) {
+    Array.prototype.forEach= function(action, that /*opt*/) {
+        for (var i= 0, n= this.length; i<n; i++)
+            if (i in this)
+                action.call(that, this[i], i, this);
+    };
+}
+if (!('map' in Array.prototype)) {
+    Array.prototype.map= function(mapper, that /*opt*/) {
+        var other= new Array(this.length);
+        for (var i= 0, n= this.length; i<n; i++)
+            if (i in this)
+                other[i]= mapper.call(that, this[i], i, this);
+        return other;
+    };
+}
+if (!('filter' in Array.prototype)) {
+    Array.prototype.filter= function(filter, that /*opt*/) {
+        var other= [], v;
+        for (var i=0, n= this.length; i<n; i++)
+            if (i in this && filter.call(that, v= this[i], i, this))
+                other.push(v);
+        return other;
+    };
+}
+if (!('every' in Array.prototype)) {
+    Array.prototype.every= function(tester, that /*opt*/) {
+        for (var i= 0, n= this.length; i<n; i++)
+            if (i in this && !tester.call(that, this[i], i, this))
+                return false;
+        return true;
+    };
+}
+if (!('some' in Array.prototype)) {
+    Array.prototype.some= function(tester, that /*opt*/) {
+        for (var i= 0, n= this.length; i<n; i++)
+            if (i in this && tester.call(that, this[i], i, this))
+                return true;
+        return false;
+    };
+}
+
+

--- a/ckanext/ga_report/templates/ga_report/publisher/index.html
+++ b/ckanext/ga_report/templates/ga_report/publisher/index.html
@@ -13,6 +13,11 @@
       <p><center>
           <a class="btn button btn-primary" href="${h.url_for(controller='ckanext.ga_report.controller:GaDatasetReport',action='publisher_csv',month=c.month or 'all')}">Download as CSV</a></center>
       </p>
+    </li>
+    <li class="widget-container boxed widget_text">
+      <h4>Graph Legend</h4>
+      <div id="graph-legend-container">
+      </div>
     </li>
     <xi:include href="../notes.html" />
   </py:match>

--- a/ckanext/ga_report/templates/ga_report/publisher/read.html
+++ b/ckanext/ga_report/templates/ga_report/publisher/read.html
@@ -23,6 +23,11 @@
       <p><center>
           <a class="btn button btn-primary" href="${h.url_for(controller='ckanext.ga_report.controller:GaDatasetReport',action='dataset_csv',id=c.publisher_name or 'all',month=c.month or 'all')}">Download as CSV</a></center>
       </p>
+    </li>
+    <li class="widget-container boxed widget_text">
+      <h4>Graph Legend</h4>
+      <div id="graph-legend-container">
+      </div>
     </li>
     <xi:include href="../notes.html" />
   </py:match>

--- a/ckanext/ga_report/templates/ga_report/site/index.html
+++ b/ckanext/ga_report/templates/ga_report/site/index.html
@@ -12,6 +12,7 @@
     <link rel="stylesheet" type="text/css" href="/css/ga_report.css"/>
     <script type="text/javascript" src="/scripts/ckanext_ga_reports.js"></script>
     <script type="text/javascript" src="/scripts/vendor/jquery.sparkline.modified.js"></script>
+    <script type="text/javascript" src="/scripts/rickshaw_ie7_shim.js"></script>
     <script type="text/javascript" src="/scripts/vendor/d3.v2.js"></script>
     <script type="text/javascript" src="/scripts/vendor/d3.layout.min.js"></script>
     <script type="text/javascript" src="/scripts/vendor/rickshaw.min.js"></script>
@@ -142,7 +143,12 @@
       $(function() {
         CKAN.GA_Reports.bind_sparklines();
         CKAN.GA_Reports.bind_sidebar();
-        $(window).trigger('hashchange');
+        if (!window.location.hash) {
+          window.location.hash='totals';
+        }
+        else {
+          $(window).trigger('hashchange');
+        }
       });  
     </script>
   </py:def>