[403] Rickshaw graph always renders all series, no matter which month is selected.
[403] Rickshaw graph always renders all series, no matter which month is selected.

--- 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,7 +448,7 @@
                 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',

--- a/ckanext/ga_report/helpers.py
+++ b/ckanext/ga_report/helpers.py
@@ -80,7 +80,7 @@
     return base.render_snippet('ga_report/ga_popular_single.html', **context)
 
 
-def most_popular_datasets(publisher, count=20):
+def most_popular_datasets(publisher, count=20, preview_image=None):
 
     if not publisher:
         _log.error("No valid publisher passed to 'most_popular_datasets'")
@@ -92,7 +92,8 @@
         'dataset_count': len(results),
         'datasets': results,
 
-        'publisher': publisher
+        'publisher': publisher,
+        'preview_image': preview_image
     }
 
     return base.render_snippet('ga_report/publisher/popular.html', **ctx)

--- 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,6 +54,9 @@
   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
@@ -59,21 +59,41 @@
  </table>
 
 
-<div py:def="usage_nav(active_name)" id="minornavigation">
-    <div id="minornavigation-bg-left">
-    <div id="minornavigation-bg-right">
-      <ul class="nav nav-pills">
-        <?python querystring='?month='+c.month if c.month else '' ?>
-        <li py:attrs="{'class': 'active' if active_name=='Site-wide' else None}"><a py:attrs="{'class': 'active' if active_name=='Site-wide' else None}"  href="${h.url_for(controller='ckanext.ga_report.controller:GaReport',action='index')}${querystring}"><img src="/images/icons/page_white.png" height="16px" width="16px" alt="None" class="inline-icon "/> Site-wide</a></li>
-        <li py:attrs="{'class': 'active' if active_name=='Publishers' else None}">
-        <a py:attrs="{'class': 'active' if active_name=='Publishers' else None}"  href="${h.url_for(controller='ckanext.ga_report.controller:GaDatasetReport',action='publishers')}${querystring}"><img src="/images/icons/page_white.png" height="16px" width="16px" alt="None" class="inline-icon "/> Publishers</a>
-        </li>
-        <li py:attrs="{'class': 'active' if active_name=='Datasets' else None}">
-        <a py:attrs="{'class': 'active' if active_name=='Datasets' else None}"  href="${h.url_for(controller='ckanext.ga_report.controller:GaDatasetReport',action='read')}${querystring}"><img src="/images/icons/page_white.png" height="16px" width="16px" alt="None" class="inline-icon "/> Datasets</a>
-        </li>
+<div py:def="usage_nav(active_name)">
+  <span class="subheading">
+    <img src="/images/arrow-down-right.png" />
+    <a class="${if_(active_name=='Site-wide','active')}" href="${h.url_for(controller='ckanext.ga_report.controller:GaReport',action='index')}${if_(c.month,'?month='+c.month)}">Site-wide</a>
+    <span class="divider">|</span>
+    <a class="${if_(active_name=='Publishers','active')}" href="${h.url_for(controller='ckanext.ga_report.controller:GaDatasetReport',action='publishers')}${if_(c.month,'?month='+c.month)}">Publishers</a>
+    <span class="divider">|</span>
+    <a class="${if_(active_name=='Datasets','active')}" href="${h.url_for(controller='ckanext.ga_report.controller:GaDatasetReport',action='read')}${if_(c.month,'?month='+c.month)}">Datasets</a>
+  </span>
+</div>
+
+<div py:def="ga_sidebar(download_link)">
+  <div class="boxed">
+    <div class="widget-container widget_text">
+      <h4>Download</h4>
+      <p><center>
+        <a class="btn button btn-primary" href="${download_link}">Download as CSV</a></center>
+      </p>
+    </div>
+    <div class="widget-container widget_text">
+      <h4>Graph Legend</h4>
+      <div id="graph-legend-container">
+        <div style="display: none;" id="legend_none">(No graph is loaded)</div>
+      </div>
+    </div>
+    <div class="widget-container widget_text">
+      <h4>Notes</h4>
+      <ul>
+          <li>"Views" is the number of times a page was loaded in users' browsers.</li>
+          <li>"Downloads" is the number of times a user has clicked to download either an original or cached resource for a particular dataset. Download information is only available from 2nd December 2012; 'No data' is shown for records before that date.</li>
+          <li>These usage statistics are confined to users with javascript enabled, which excludes web crawlers and API calls.</li>
+          <li>The results are not shown when the number of views/visits is tiny. Where these relate to site pages, results are available in full in the CSV download. Where these relate to users' web browser information, results are not disclosed, for privacy reasons.</li>
       </ul>
     </div>
-    </div>
+  </div>
 </div>
 
 

--- a/ckanext/ga_report/templates/ga_report/notes.html
+++ /dev/null
@@ -1,16 +1,1 @@
-<html xmlns:py="http://genshi.edgewall.org/"
-  xmlns:i18n="http://genshi.edgewall.org/i18n"
-  xmlns:xi="http://www.w3.org/2001/XInclude"
-  py:strip="">
 
-    <li class="widget-container boxed widget_text">
-      <h4>Notes</h4>
-      <ul>
-          <li>"Views" is the number of times a page was loaded in users' browsers.</li>
-          <li>"Downloads" is the number of times a user has clicked to download either an original or cached resource for a particular dataset. Download information is only available from 2nd December 2012; 'No data' is shown for records before that date.</li>
-          <li>These usage statistics are confined to users with javascript enabled, which excludes web crawlers and API calls.</li>
-          <li>The results are not shown when the number of views/visits is tiny. Where these relate to site pages, results are available in full in the CSV download. Where these relate to users' web browser information, results are not disclosed, for privacy reasons.</li>
-      </ul>
-    </li>
-</html>
-

--- a/ckanext/ga_report/templates/ga_report/publisher/index.html
+++ b/ckanext/ga_report/templates/ga_report/publisher/index.html
@@ -7,19 +7,9 @@
 
   <py:def function="page_title">Usage by Publisher</py:def>
 
+
   <py:match path="primarysidebar">
-    <li class="widget-container boxed widget_text">
-      <h4>Download</h4>
-      <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" />
+    ${ga_sidebar(download_link=h.url_for(controller='ckanext.ga_report.controller:GaDatasetReport',action='publisher_csv',month=c.month or 'all'))}
   </py:match>
 
   <py:def function="optional_head">
@@ -35,13 +25,12 @@
 
   <div py:match="content">
 
-      <h1>Site Usage</h1>
+    <h1>Site Usage ${usage_nav('Publishers')}</h1>
 
-      ${usage_nav('Publishers')}
-
+    <div class="boxed">
 
        ${rickshaw_graph(c.top_publishers_graph,'publishers')}
-      <form class="form-inline" action="${h.url_for(controller='ckanext.ga_report.controller:GaDatasetReport',action='publishers')}" method="get">
+       <form class="form-inline" action="${h.url_for(controller='ckanext.ga_report.controller:GaDatasetReport',action='publishers')}" method="get">
           <div class="controls">
             <label>Show stats table for:</label>
             ${month_selector(c.month, c.months, c.day)}
@@ -49,22 +38,21 @@
        </form>
 
        <table class="ga-reports-table table table-condensed table-bordered table-striped">
-	 <tr>
-	   <th>Publisher</th>
-	   <th class="td-numeric">Dataset Views</th>
-	 </tr>
+         <tr>
+           <th>Publisher</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 class="td-numeric">${views}</td>
-	  </tr>
+          <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 class="td-numeric">${views}</td>
+          </tr>
         </py:for>
        </table>
-
-
-  </div>
+    </div><!--/boxed-->
+  </div><!--/content-->
 
   <py:def function="optional_footer">
     <script type="text/javascript">

--- a/ckanext/ga_report/templates/ga_report/publisher/popular.html
+++ b/ckanext/ga_report/templates/ga_report/publisher/popular.html
@@ -15,7 +15,12 @@
                 </li>
             </py:for>
         </ul>
-      <p class="">${h.link_to("More usage data for " + publisher.title, h.url_for(controller='ckanext.ga_report.controller:GaDatasetReport',action='read_publisher',id=publisher.name))}</p>
+      <p>
+        <a href="${h.url_for(controller='ckanext.ga_report.controller:GaDatasetReport',action='read_publisher',id=publisher.name)}">
+          <img py:if="preview_image" src="${preview_image}" /><br/>
+          More usage data for ${publisher.title}
+        </a>
+      </p>
      </div>
   </py:if>
 </html>

--- a/ckanext/ga_report/templates/ga_report/publisher/read.html
+++ b/ckanext/ga_report/templates/ga_report/publisher/read.html
@@ -19,27 +19,14 @@
   </py:def>
 
   <py:match path="primarysidebar">
-    <li class="widget-container boxed widget_text">
-      <h4>Download</h4>
-      <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" />
+    ${ga_sidebar(download_link=h.url_for(controller='ckanext.ga_report.controller:GaDatasetReport',action='dataset_csv',id=c.publisher_name or 'all',month=c.month or 'all'))}
   </py:match>
 
 
   <div py:match="content">
-     <h1>Site Usage</h1>
+    <h1>Site Usage ${usage_nav('Datasets')}</h1>
 
-     ${usage_nav('Datasets')}
-
-
+    <div class="boxed">
      <h3 py:if="c.publisher"><a href="${h.url_for(controller='ckanext.dgu.controllers.publisher:PublisherController',action='read',id=c.publisher.name)}">${c.publisher.title}</a></h3>
 
      <p py:if="not c.top_packages">No page views in this period</p>
@@ -80,6 +67,7 @@
       </table>
     </py:if>
 
+  </div>
 
   </div>
 

--- a/ckanext/ga_report/templates/ga_report/site/downloads.html
+++ b/ckanext/ga_report/templates/ga_report/site/downloads.html
@@ -8,19 +8,13 @@
   <py:def function="page_title">Downloads</py:def>
 
   <py:match path="primarysidebar">
-    <li py:if="c.downloads" class="widget-container boxed widget_text">
-      <h4>Download</h4>
-      <p><center>
-          <a class="btn button btn-primary" href="${h.url_for(controller='ckanext.ga_report.controller:GaReport',action='csv_downloads',month=c.month or 'all')}">Download as CSV</a></center>
-      </p>
-    </li>
-    <xi:include href="../notes.html" />
-
+    ${ga_sidebar(download_link=h.url_for(controller='ckanext.ga_report.controller:GaReport',action='csv_downloads',month=c.month or 'all'))}
   </py:match>
 
   <div py:match="content">
-      <h1>Downloads</h1>
-      ${usage_nav('Downloads')}
+      <h1>Downloads ${usage_nav('Downloads')}</h1>
+
+    <div class="boxed">
 
       <form class="form-inline" action="${h.url_for(controller='ckanext.ga_report.controller:GaReport',action='downloads')}" method="get">
           <div class="controls">
@@ -38,6 +32,7 @@
          <h4>No data</h4>
          <p>There is no download data available for this month</p>
        </py:if>
+     </div>
   </div>
 
   <xi:include href="../../layout.html" />

--- a/ckanext/ga_report/templates/ga_report/site/index.html
+++ b/ckanext/ga_report/templates/ga_report/site/index.html
@@ -19,58 +19,49 @@
   </py:def>
 
   <py:match path="primarysidebar">
-    <li class="widget-container boxed widget_text">
-      <h4>Download</h4>
-      <p><center>
-          <a class="btn button btn-primary" href="${h.url_for(controller='ckanext.ga_report.controller:GaReport',action='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 id="legend_none">(No graph loaded)</div>
-      </div>
-    </li>
-    <xi:include href="../notes.html" />
-
+    ${ga_sidebar(download_link=h.url_for(controller='ckanext.ga_report.controller:GaReport',action='csv',month=c.month or 'all'))}
   </py:match>
 
   <div py:match="content">
-      <h1>Site Usage</h1>
-      ${usage_nav('Site-wide')}
+      <h1>Site Usage ${usage_nav('Site-wide')}</h1>
 
+    <div class="boxed">
     <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">
+        <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>
@@ -158,6 +149,7 @@
         </div>
        </div>
      </div>
+   </div>
   </div>
 
 
@@ -167,12 +159,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>