[403] Fix rickshaw choking if there is an empty 'Others' series.
[403] Fix rickshaw choking if there is an empty 'Others' series.

--- a/ckanext/ga_report/controller.py
+++ b/ckanext/ga_report/controller.py
@@ -218,7 +218,10 @@
                     'x':_get_unix_epoch(stat.period_name),
                     'y':float(stat.value)
                     })
-            graph = [ graph_dict[x[0]] for x in entries ]
+            stats_in_table = [x[0] for x in entries]
+            stats_not_in_table = set(graph_dict.keys()) - set(stats_in_table)
+            stats = stats_in_table + sorted(list(stats_not_in_table))
+            graph = [graph_dict[x] for x in stats]
             setattr(c, v+'_graph', json.dumps( _to_rickshaw(graph,percentageMode=True) ))
 
             # Get the total for each set of values and then set the value as
@@ -417,11 +420,14 @@
     # Create a consistent x-axis between all series
     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'] ]
+    x_axis = []
+    for series in data:
+        for point in series['data']:
+            x_axis.append(point['x'])
+    x_axis = sorted( list( set(x_axis) ) )
+    # Zero pad any missing values
     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)):
             series['data'].append( {'x':x, 'y':0} )
     if percentageMode:
@@ -430,9 +436,6 @@
             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 = get_totals(data)
@@ -445,12 +448,13 @@
                 fraction = float(point['y']) / totals[point['x']]
                 if not (series in data) and fraction>THRESHOLD:
                     data.append(series)
-        # Overwrite data with a set of intereting series
+        # Overwrite data with a set of interesting 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() ] 
-            })
+        if len(others):
+            data.append({ 
+                'name':'Other',
+                'data': [ {'x':x,'y':y} for x,y in get_totals(others).items() ] 
+                })
         # Turn each point into a percentage
         for series in data:
             for point in series['data']:

--- a/ckanext/ga_report/public/css/ga_report.css
+++ b/ckanext/ga_report/public/css/ga_report.css
@@ -2,6 +2,11 @@
   padding: 1px 0 0 0;
   width: 108px;
   text-align: center;
+  /* Hack to hide the momentary flash of text 
+   * before sparklines are fully rendered */
+  font-size: 1px;
+  color: transparent;
+  overflow: hidden;
 }
 .rickshaw_chart_container {
   position: relative;
@@ -18,6 +23,7 @@
 .rickshaw_legend {
   background: transparent;
   width: 100%;
+  padding-top: 4px;
 }
 .rickshaw_y_axis {
   position: absolute;
@@ -29,6 +35,10 @@
   background: transparent !important;
   color: #000000 !important;
   font-weight: normal !important;
+}
+.rickshaw_legend .instructions {
+  color: #000;
+  margin-bottom: 6px;
 }
 
 .rickshaw_legend .line .action {
@@ -44,4 +54,11 @@
   float: left;
   width: 200px;
 }
+.rickshaw_legend .line .label:hover {
+  text-decoration: underline;
+}
 
+.ga-reports-table .td-numeric {
+  text-align: center;
+}
+

--- a/ckanext/ga_report/public/scripts/ckanext_ga_reports.js
+++ b/ckanext/ga_report/public/scripts/ckanext_ga_reports.js
@@ -45,6 +45,7 @@
       graph: graph,
       legend: legend
     } );
+    myLegend.prepend('<div class="instructions">Click on a series below to isolate its graph:</div>');
     graph.render();
 };
 
@@ -54,24 +55,29 @@
    * Sparkline graphs should be drawn.
    * Note that they cannot be drawn sooner.
    */
+  var created = false;
   $('a[href="#totals"]').on(
     'shown', 
-    function() {
-      var sparkOptions = {
-        enableTagOptions: true,
-        type: 'line',
-        width: 100,
-        height: 26,
-        chartRangeMin: 0,
-        spotColor: '',
-        maxSpotColor: '',
-        minSpotColor: '',
-        highlightSpotColor: '000000',
-        lineColor: '3F8E6D',
-        fillColor: 'B7E66B'
-      };
-      $('.sparkline').sparkline('html',sparkOptions);
-    }
+      function() {
+        if (!created) {
+          var sparkOptions = {
+            enableTagOptions: true,
+            type: 'line',
+            width: 100,
+            height: 26,
+            chartRangeMin: 0,
+            spotColor: '',
+            maxSpotColor: '',
+            minSpotColor: '',
+            highlightSpotColor: '#000000',
+            lineColor: '#3F8E6D',
+            fillColor: '#B7E66B'
+          };
+          $('.sparkline').sparkline('html',sparkOptions);
+          created = true;
+        }
+        $.sparkline_display_visible();
+      }
   );
 };
 
@@ -81,7 +87,7 @@
    * Show the correct rickshaw graph in the sidebar. 
    * Not to be called before all graphs load.
    */
-  $('a[data-toggle="hashchange"]').on(
+  $('a[data-toggle="hashtab"]').on(
     'shown',
     function(e) {
       var href = $(e.target).attr('href');
@@ -94,9 +100,12 @@
       }
       legend_name = '#legend_'+legend_name;
       $('#graph-legend-container > *').hide();
+      $('#graph-legend-container .instructions').show();
       $(legend_name).show();
     }
   );
+  /* The first tab might already have been shown */
+  $('li.active > a[data-toggle="hashtab"]').trigger('shown');
 };
 
 CKAN.GA_Reports.bind_month_selector = function() {
@@ -111,22 +120,3 @@
   selectors.bind('change', handler);
 };
 
-/* 
- * Custom bootstrap plugin for handling data-toggle="hashchange".
- * Behaves like data-toggle="tab" but I respond to the hashchange.
- * Page state is memo-ized in the URL this way. Why doesn't Bootstrap do this?
- */
-$(function() {
-  var mapping = {};
-  $('a[data-toggle="hashchange"]').each(
-    function(i,link) {
-      link = $(link);
-      mapping[link.attr('href')] = link;
-    }
-  );
-  $(window).hashchange(function() {
-    var link = mapping[window.location.hash];
-    if (link) { link.tab('show'); }
-  });
-});
-

--- a/ckanext/ga_report/templates/ga_report/ga_util.html
+++ b/ckanext/ga_report/templates/ga_report/ga_util.html
@@ -14,7 +14,7 @@
 </select>
 
 
-<table py:def="social_table(items, with_source=False)" class="table table-condensed table-bordered table-striped">
+<table py:def="social_table(items, with_source=False)" class="ga-reports-table table table-condensed table-bordered table-striped">
     <tr>
         <th>Name</th>
         <th py:if="with_source">Source</th>
@@ -24,7 +24,7 @@
     <tr>
         <td><a href="${url}">${name}</a></td>
         <td py:if="with_source">${source}</td>
-        <td>${count}</td>
+        <td class="td-numeric">${count}</td>
     </tr>
     </py:for>
  </table>
@@ -45,15 +45,15 @@
 </div>
 
 
-<table py:def="stat_table(items, title='Views')" class="table table-condensed table-bordered table-striped">
+<table py:def="stat_table(items, title='Views')" class="ga-reports-table table table-condensed table-bordered table-striped">
     <tr>
         <th>Name</th>
-        <th>% ${title}</th>
+        <th class="td-numeric">% ${title}</th>
     </tr>
     <py:for each="name, value in items">
     <tr>
         <td>${name}</td>
-        <td>${value}</td>
+        <td class="td-numeric">${value}</td>
     </tr>
     </py:for>
  </table>

--- a/ckanext/ga_report/templates/ga_report/publisher/index.html
+++ b/ckanext/ga_report/templates/ga_report/publisher/index.html
@@ -24,8 +24,8 @@
 
   <py:def function="optional_head">
     <link rel="stylesheet" type="text/css" href="/scripts/vendor/rickshaw.min.css"/>
-    <link rel="stylesheet" type="text/css" href="/css/ga_report.css"/>
-    <script type="text/javascript" src="/scripts/ckanext_ga_reports.js"></script>
+    <link rel="stylesheet" type="text/css" href="/css/ga_report.css?1"/>
+    <script type="text/javascript" src="/scripts/ckanext_ga_reports.js?1"></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>
@@ -48,17 +48,17 @@
           </div>
        </form>
 
-       <table class="table table-condensed table-bordered table-striped">
+       <table class="ga-reports-table table table-condensed table-bordered table-striped">
 	 <tr>
 	   <th>Publisher</th>
-	   <th>Dataset Views</th>
+	   <th class="td-numeric">Dataset Views</th>
 	 </tr>
         <py:for each="publisher, views, visits in c.top_publishers">
 	  <tr>
 	    <td>
 	        ${h.link_to(publisher.title, h.url_for(controller='ckanext.ga_report.controller:GaDatasetReport', action='read_publisher', id=publisher.name) + (("?month=" + c.month) if c.month else ''))}
 	    </td>
-	    <td>${views}</td>
+	    <td class="td-numeric">${views}</td>
 	  </tr>
         </py:for>
        </table>

--- a/ckanext/ga_report/templates/ga_report/publisher/read.html
+++ b/ckanext/ga_report/templates/ga_report/publisher/read.html
@@ -9,8 +9,8 @@
 
   <py:def function="optional_head">
     <link rel="stylesheet" type="text/css" href="/scripts/vendor/rickshaw.min.css"/>
-    <link rel="stylesheet" type="text/css" href="/css/ga_report.css"/>
-    <script type="text/javascript" src="/scripts/ckanext_ga_reports.js"></script>
+    <link rel="stylesheet" type="text/css" href="/css/ga_report.css?1"/>
+    <script type="text/javascript" src="/scripts/ckanext_ga_reports.js?1"></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>
@@ -62,7 +62,7 @@
         </div>
      </form>
      <py:if test="c.top_packages">
-       <table class="table table-condensed table-bordered table-striped">
+       <table class="ga-reports-table table table-condensed table-bordered table-striped">
          <tr>
            <th>Dataset</th>
            <th>Views</th>
@@ -73,8 +73,8 @@
              <td>
                ${h.link_to(package.title or package.name, h.url_for(controller='package', action='read', id=package.name))}
              </td>
-             <td>${views}</td>
-             <td>${downloads}</td>
+             <td class="td-numeric">${views}</td>
+             <td class="td-numeric">${downloads}</td>
           </tr>
         </py:for>
       </table>

--- a/ckanext/ga_report/templates/ga_report/site/index.html
+++ b/ckanext/ga_report/templates/ga_report/site/index.html
@@ -9,8 +9,8 @@
 
   <py:def function="optional_head">
     <link rel="stylesheet" type="text/css" href="/scripts/vendor/rickshaw.min.css"/>
-    <link rel="stylesheet" type="text/css" href="/css/ga_report.css"/>
-    <script type="text/javascript" src="/scripts/ckanext_ga_reports.js"></script>
+    <link rel="stylesheet" type="text/css" href="/css/ga_report.css?1"/>
+    <script type="text/javascript" src="/scripts/ckanext_ga_reports.js?1"></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>
@@ -41,46 +41,50 @@
 
     <div class="tabbable">
       <ul class="nav nav-tabs">
-        <li><a href="#totals" data-toggle="hashchange">Totals</a></li>
+        <li class="active"><a href="#totals" data-toggle="hashtab">Totals</a></li>
         <li class="dropdown">
             <a href="#" class="dropdown-toggle" data-toggle="dropdown">Browsers
                 <b class="caret"></b></a>
             <ul class="dropdown-menu">
-                <li><a href="#browsers_names" data-toggle="hashchange">Browsers</a></li>
-                <li><a href="#browsers_versions" data-toggle="hashchange">Versions</a></li>
+                <li><a href="#browsers_names" data-toggle="hashtab">Browsers</a></li>
+                <li><a href="#browsers_versions" data-toggle="hashtab">Versions</a></li>
             </ul>
         </li>
         <li class="dropdown">
             <a href="#" class="dropdown-toggle" data-toggle="dropdown">Operating Systems
                 <b class="caret"></b></a>
             <ul class="dropdown-menu">
-                <li><a href="#os" data-toggle="hashchange">Operating Systems</a></li>
-                <li><a href="#os_versions" data-toggle="hashchange">Versions</a></li>
+                <li><a href="#os" data-toggle="hashtab">Operating Systems</a></li>
+                <li><a href="#os_versions" data-toggle="hashtab">Versions</a></li>
             </ul>
         </li>
         <li class="dropdown">
             <a href="#" class="dropdown-toggle" data-toggle="dropdown">Social
                 <b class="caret"></b></a>
             <ul class="dropdown-menu">
-                <li><a href="#social_networks" data-toggle="hashchange">All networks</a></li>
-                <li><a href="#social_referrals_totals" data-toggle="hashchange">Referral links</a></li>
+                <li><a href="#social_networks" data-toggle="hashtab">All networks</a></li>
+                <li><a href="#social_referrals_totals" data-toggle="hashtab">Referral links</a></li>
             </ul>
         </li>
-        <li><a href="#languages" data-toggle="hashchange">Languages</a></li>
-        <li><a href="#country" data-toggle="hashchange">Country</a></li>
+        <li><a href="#languages" data-toggle="hashtab">Languages</a></li>
+        <li><a href="#country" data-toggle="hashtab">Country</a></li>
       </ul>
       <div class="tab-content">
-        <div class="tab-pane" id="totals">
-             <table class="table table-condensed table-bordered table-striped">
+        <div class="tab-pane active" id="totals">
+             <form class="form-inline" action="${h.url_for(controller='ckanext.ga_report.controller:GaReport',action='index')}" method="get">
+               <label>Show stats table for:</label>
+               ${month_selector(c.month, c.months, c.day)}
+             </form>
+             <table class="ga-reports-table table table-condensed table-bordered table-striped">
             	 <tr>
             	   <th>Name</th>
-            	   <th>Value</th>
+            	   <th class="td-numeric">Value</th>
             	   <th>History</th>
             	 </tr>
                 <py:for each="name, value, graph in c.global_totals">
                     <tr>
                         <td>${name}</td>
-                        <td>${value}</td>
+                        <td class="td-numeric">${value}</td>
                         <td class="sparkline-cell">
                           <span class="sparkline" sparkTooltips="${','.join([x for x,y in graph])}">
                             ${','.join([y for x,y in graph])}
@@ -167,12 +171,6 @@
         CKAN.GA_Reports.bind_sparklines();
         CKAN.GA_Reports.bind_sidebar();
         CKAN.GA_Reports.bind_month_selector();
-        if (!window.location.hash) {
-          window.location.hash='totals';
-        }
-        else {
-          $(window).trigger('hashchange');
-        }
       });  
     </script>
   </py:def>