[noticket] Hide momentary flash of text on sparkline cells.
--- a/.gitignore
+++ b/.gitignore
@@ -1,6 +1,7 @@
*.py[co]
*.py~
.gitignore
+ckan.log
# Packages
*.egg
--- a/ckanext/ga_report/command.py
+++ b/ckanext/ga_report/command.py
@@ -23,7 +23,7 @@
import ckan.model as model
model.Session.remove()
model.Session.configure(bind=model.meta.engine)
- log = logging.getLogger('ckanext.ga-report')
+ log = logging.getLogger('ckanext.ga_report')
import ga_model
ga_model.init_tables()
@@ -55,6 +55,36 @@
init_service('token.dat',
self.args[0] if self.args
else 'credentials.json')
+
+class FixTimePeriods(CkanCommand):
+ """
+ Fixes the 'All' records for GA_Urls
+
+ It is possible that older urls that haven't recently been visited
+ do not have All records. This command will traverse through those
+ records and generate valid All records for them.
+ """
+ summary = __doc__.split('\n')[0]
+ usage = __doc__
+ max_args = 0
+ min_args = 0
+
+ def __init__(self, name):
+ super(FixTimePeriods, self).__init__(name)
+
+ def command(self):
+ import ckan.model as model
+ from ga_model import post_update_url_stats
+ self._load_config()
+ model.Session.remove()
+ model.Session.configure(bind=model.meta.engine)
+
+ log = logging.getLogger('ckanext.ga_report')
+
+ log.info("Updating 'All' records for old URLs")
+ post_update_url_stats()
+ log.info("Processing complete")
+
class LoadAnalytics(CkanCommand):
--- a/ckanext/ga_report/controller.py
+++ b/ckanext/ga_report/controller.py
@@ -1,6 +1,7 @@
import re
import csv
import sys
+import json
import logging
import operator
import collections
@@ -13,6 +14,7 @@
log = logging.getLogger('ckanext.ga-report')
+DOWNLOADS_AVAILABLE_FROM = '2012-12'
def _get_month_name(strdate):
import calendar
@@ -20,8 +22,12 @@
d = strptime(strdate, '%Y-%m')
return '%s %s' % (calendar.month_name[d.tm_mon], d.tm_year)
-
-def _month_details(cls):
+def _get_unix_epoch(strdate):
+ from time import strptime,mktime
+ d = strptime(strdate, '%Y-%m')
+ return int(mktime(d))
+
+def _month_details(cls, stat_key=None):
'''
Returns a list of all the periods for which we have data, unfortunately
knows too much about the type of the cls being passed as GA_Url has a
@@ -32,9 +38,13 @@
months = []
day = None
- vals = model.Session.query(cls.period_name,cls.period_complete_day)\
- .filter(cls.period_name!='All').distinct(cls.period_name)\
- .order_by("period_name desc").all()
+ q = model.Session.query(cls.period_name,cls.period_complete_day)\
+ .filter(cls.period_name!='All').distinct(cls.period_name)
+ if stat_key:
+ q= q.filter(cls.stat_name==stat_key)
+
+ vals = q.order_by("period_name desc").all()
+
if vals and vals[0][1]:
day = int(vals[0][1])
ordinal = 'th' if 11 <= day <= 13 \
@@ -52,7 +62,7 @@
def csv(self, month):
import csv
- q = model.Session.query(GA_Stat)
+ q = model.Session.query(GA_Stat).filter(GA_Stat.stat_name!='Downloads')
if month != 'all':
q = q.filter(GA_Stat.period_name==month)
entries = q.order_by('GA_Stat.period_name, GA_Stat.stat_name, GA_Stat.key').all()
@@ -68,6 +78,7 @@
entry.stat_name.encode('utf-8'),
entry.key.encode('utf-8'),
entry.value.encode('utf-8')])
+
def index(self):
@@ -101,11 +112,26 @@
return key, val
+ # Query historic values for sparkline rendering
+ sparkline_query = model.Session.query(GA_Stat)\
+ .filter(GA_Stat.stat_name=='Totals')\
+ .order_by(GA_Stat.period_name)
+ sparkline_data = {}
+ for x in sparkline_query:
+ sparkline_data[x.key] = sparkline_data.get(x.key,[])
+ key, val = clean_key(x.key,float(x.value))
+ tooltip = '%s: %s' % (_get_month_name(x.period_name), val)
+ sparkline_data[x.key].append( (tooltip,x.value) )
+ # Trim the latest month, as it looks like a huge dropoff
+ for key in sparkline_data:
+ sparkline_data[key] = sparkline_data[key][:-1]
+
c.global_totals = []
if c.month:
for e in entries:
key, val = clean_key(e.key, e.value)
- c.global_totals.append((key, val))
+ sparkline = sparkline_data[e.key]
+ c.global_totals.append((key, val, sparkline))
else:
d = collections.defaultdict(list)
for e in entries:
@@ -114,11 +140,19 @@
if k in ['Total page views', 'Total visits']:
v = sum(v)
else:
- v = float(sum(v))/len(v)
+ v = float(sum(v))/float(len(v))
+ sparkline = sparkline_data[k]
key, val = clean_key(k,v)
- c.global_totals.append((key, val))
- c.global_totals = sorted(c.global_totals, key=operator.itemgetter(0))
+ c.global_totals.append((key, val, sparkline))
+ # Sort the global totals into a more pleasant order
+ def sort_func(x):
+ key = x[0]
+ total_order = ['Total page views','Total visits','Pages per visit']
+ if key in total_order:
+ return total_order.index(key)
+ return 999
+ c.global_totals = sorted(c.global_totals, key=sort_func)
keys = {
'Browser versions': 'browser_versions',
@@ -155,12 +189,13 @@
for k, v in keys.iteritems():
q = model.Session.query(GA_Stat).\
- filter(GA_Stat.stat_name==k)
+ filter(GA_Stat.stat_name==k).\
+ order_by(GA_Stat.period_name)
+ # Buffer the tabular data
if c.month:
entries = []
q = q.filter(GA_Stat.period_name==c.month).\
order_by('ga_stat.value::int desc')
-
d = collections.defaultdict(int)
for e in q.all():
d[e.key] += int(e.value)
@@ -169,10 +204,27 @@
entries.append((key,val,))
entries = sorted(entries, key=operator.itemgetter(1), reverse=True)
+ # Run a query on all months to gather graph data
+ graph_query = model.Session.query(GA_Stat).\
+ filter(GA_Stat.stat_name==k).\
+ order_by(GA_Stat.period_name)
+ graph_dict = {}
+ for stat in graph_query:
+ graph_dict[ stat.key ] = graph_dict.get(stat.key,{
+ 'name':stat.key,
+ 'data': []
+ })
+ graph_dict[ stat.key ]['data'].append({
+ 'x':_get_unix_epoch(stat.period_name),
+ 'y':float(stat.value)
+ })
+ graph = [ graph_dict[x[0]] for x in entries ]
+ 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
# a percentage of the total
if k == 'Social sources':
- total = sum([x for n,x in c.global_totals if n == 'Total visits'])
+ total = sum([x for n,x,graph in c.global_totals if n == 'Total visits'])
else:
total = sum([num for _,num in entries])
setattr(c, v, [(k,_percent(v,total)) for k,v in entries ])
@@ -197,7 +249,9 @@
writer = csv.writer(response)
writer.writerow(["Publisher Title", "Publisher Name", "Views", "Visits", "Period Name"])
- for publisher,view,visit in _get_top_publishers(None):
+ top_publishers, top_publishers_graph = _get_top_publishers(None)
+
+ for publisher,view,visit in top_publishers:
writer.writerow([publisher.title.encode('utf-8'),
publisher.name.encode('utf-8'),
view,
@@ -223,13 +277,14 @@
str('attachment; filename=datasets_%s_%s.csv' % (c.publisher_name, month,))
writer = csv.writer(response)
- writer.writerow(["Dataset Title", "Dataset Name", "Views", "Visits", "Period Name"])
-
- for package,view,visit in packages:
+ writer.writerow(["Dataset Title", "Dataset Name", "Views", "Visits", "Resource downloads", "Period Name"])
+
+ for package,view,visit,downloads in packages:
writer.writerow([package.title.encode('utf-8'),
package.name.encode('utf-8'),
view,
visit,
+ downloads,
month])
def publishers(self):
@@ -245,15 +300,17 @@
if c.month:
c.month_desc = ''.join([m[1] for m in c.months if m[0]==c.month])
- c.top_publishers = _get_top_publishers()
+ c.top_publishers, graph_data = _get_top_publishers()
+ c.top_publishers_graph = json.dumps( _to_rickshaw(graph_data) )
+
return render('ga_report/publisher/index.html')
def _get_packages(self, publisher=None, count=-1):
'''Returns the datasets in order of views'''
- if count == -1:
- count = sys.maxint
-
+ have_download_data = True
month = c.month or 'All'
+ if month != 'All':
+ have_download_data = month >= DOWNLOADS_AVAILABLE_FROM
q = model.Session.query(GA_Url,model.Package)\
.filter(model.Package.name==GA_Url.package_id)\
@@ -263,9 +320,26 @@
q = q.filter(GA_Url.period_name==month)
q = q.order_by('ga_url.pageviews::int desc')
top_packages = []
- for entry,package in q.limit(count):
+ if count == -1:
+ entries = q.all()
+ else:
+ entries = q.limit(count)
+
+ for entry,package in entries:
if package:
- top_packages.append((package, entry.pageviews, entry.visits))
+ # Downloads ....
+ if have_download_data:
+ dls = model.Session.query(GA_Stat).\
+ filter(GA_Stat.stat_name=='Downloads').\
+ filter(GA_Stat.key==package.name)
+ if month != 'All': # Fetch everything unless the month is specific
+ dls = dls.filter(GA_Stat.period_name==month)
+ downloads = 0
+ for x in dls:
+ downloads += int(x.value)
+ else:
+ downloads = 'No data'
+ top_packages.append((package, entry.pageviews, entry.visits, downloads))
else:
log.warning('Could not find package associated package')
@@ -313,7 +387,81 @@
c.top_packages = self._get_packages(c.publisher, 20)
+ # Graph query
+ top_package_names = [ x[0].name for x in c.top_packages ]
+ graph_query = model.Session.query(GA_Url,model.Package)\
+ .filter(model.Package.name==GA_Url.package_id)\
+ .filter(GA_Url.url.like('/dataset/%'))\
+ .filter(GA_Url.package_id.in_(top_package_names))
+ graph_dict = {}
+ for entry,package in graph_query:
+ if not package: continue
+ if entry.period_name=='All': continue
+ graph_dict[package.name] = graph_dict.get(package.name,{
+ 'name':package.title,
+ 'data':[]
+ })
+ graph_dict[package.name]['data'].append({
+ 'x':_get_unix_epoch(entry.period_name),
+ 'y':int(entry.pageviews),
+ })
+ graph = [ graph_dict[x] for x in top_package_names ]
+
+ c.graph_data = json.dumps( _to_rickshaw(graph) )
+
return render('ga_report/publisher/read.html')
+
+def _to_rickshaw(data, percentageMode=False):
+ if data==[]:
+ return data
+ # 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 = []
+ 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'] ]
+ for x in set(x_axis).difference(set(xs)):
+ series['data'].append( {'x':x, 'y':0} )
+ 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']
+ return totals
+ # Transform data into percentage stacks
+ totals = get_totals(data)
+ # Roll insignificant series into a catch-all
+ THRESHOLD = 0.01
+ raw_data = data
+ data = []
+ for series in raw_data:
+ for point in series['data']:
+ 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
+ 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 series in data:
+ for point in series['data']:
+ point['y'] = (point['y']*100) / totals[point['x']]
+ # Sort the points
+ for series in data:
+ series['data'] = sorted( series['data'], key=lambda x:x['x'] )
+ # Strip the latest month's incomplete analytics
+ series['data'] = series['data'][:-1]
+ return data
+
def _get_top_publishers(limit=20):
'''
@@ -336,11 +484,39 @@
top_publishers = []
res = connection.execute(q, month)
+ department_ids = []
for row in res:
g = model.Group.get(row[0])
if g:
+ department_ids.append(row[0])
top_publishers.append((g, row[1], row[2]))
- return top_publishers
+
+ graph = []
+ if limit is not None:
+ # Query for a history graph of these publishers
+ q = model.Session.query(
+ GA_Url.department_id,
+ GA_Url.period_name,
+ func.sum(cast(GA_Url.pageviews,sqlalchemy.types.INT)))\
+ .filter( GA_Url.department_id.in_(department_ids) )\
+ .filter( GA_Url.period_name!='All' )\
+ .filter( GA_Url.url.like('/dataset/%') )\
+ .filter( GA_Url.package_id!='' )\
+ .group_by( GA_Url.department_id, GA_Url.period_name )
+ graph_dict = {}
+ for dept_id,period_name,views in q:
+ graph_dict[dept_id] = graph_dict.get( dept_id, {
+ 'name' : model.Group.get(dept_id).title,
+ 'data' : []
+ })
+ graph_dict[dept_id]['data'].append({
+ 'x': _get_unix_epoch(period_name),
+ 'y': views
+ })
+ # Sort dict into ordered list
+ for id in department_ids:
+ graph.append( graph_dict[id] )
+ return top_publishers, graph
def _get_publishers():
--- a/ckanext/ga_report/download_analytics.py
+++ b/ckanext/ga_report/download_analytics.py
@@ -13,6 +13,7 @@
FORMAT_MONTH = '%Y-%m'
MIN_VIEWS = 50
MIN_VISITS = 20
+MIN_DOWNLOADS = 10
class DownloadAnalytics(object):
'''Downloads and stores analytics info'''
@@ -31,6 +32,11 @@
first_of_this_month = datetime.datetime(date.year, date.month, 1)
_, last_day_of_month = calendar.monthrange(int(date.year), int(date.month))
last_of_this_month = datetime.datetime(date.year, date.month, last_day_of_month)
+ # if this is the latest month, note that it is only up until today
+ now = datetime.datetime.now()
+ if now.year == date.year and now.month == date.month:
+ last_day_of_month = now.day
+ last_of_this_month = now
periods = ((date.strftime(FORMAT_MONTH),
last_day_of_month,
first_of_this_month, last_of_this_month),)
@@ -122,8 +128,12 @@
log.info('Storing publisher views (%i rows)', len(data.get('url')))
self.store(period_name, period_complete_day, data,)
- log.info('Aggregating datasets by publisher')
+ # Make sure the All records are correct.
+ ga_model.post_update_url_stats()
+
+ log.info('Associating datasets with their publisher')
ga_model.update_publisher_stats(period_name) # about 30 seconds.
+
log.info('Downloading and storing analytics for site-wide stats')
self.sitewide_stats( period_name, period_complete_day )
@@ -179,6 +189,7 @@
end_date=end_date).execute()
packages = []
+ log.info("There are %d results" % results['totalResults'])
for entry in results.get('rows'):
(loc,pageviews,visits) = entry
url = _normalize_url('http:/' + loc) # strips off domain e.g. www.data.gov.uk or data.gov.uk
@@ -203,7 +214,7 @@
start_date = '%s-01' % period_name
end_date = '%s-%s' % (period_name, last_day_of_month)
funcs = ['_totals_stats', '_social_stats', '_os_stats',
- '_locale_stats', '_browser_stats', '_mobile_stats']
+ '_locale_stats', '_browser_stats', '_mobile_stats', '_download_stats']
for f in funcs:
log.info('Downloading analytics for %s' % f.split('_')[1])
getattr(self, f)(start_date, end_date, period_name, period_complete_day)
@@ -250,7 +261,7 @@
ids='ga:' + self.profile_id,
filters='ga:pagePath==%s' % (path,),
start_date=start_date,
- metrics='ga:bounces,ga:pageviews',
+ metrics='ga:visitBounceRate',
dimensions='ga:pagePath',
max_results=10000,
end_date=end_date).execute()
@@ -260,10 +271,10 @@
path, result_data)
return
results = result_data[0]
- bounces, total = [float(x) for x in result_data[0][1:]]
- pct = 100 * bounces/total
- log.info('%d bounces from %d total == %s', bounces, total, pct)
- ga_model.update_sitewide_stats(period_name, "Totals", {'Bounce rate (home page)': pct},
+ bounces = float(results[1])
+ # visitBounceRate is already a %
+ log.info('Google reports visitBounceRate as %s', bounces)
+ ga_model.update_sitewide_stats(period_name, "Totals", {'Bounce rate (home page)': float(bounces)},
period_complete_day)
@@ -290,6 +301,74 @@
self._filter_out_long_tail(data, MIN_VIEWS)
ga_model.update_sitewide_stats(period_name, "Country", data, period_complete_day)
+
+ def _download_stats(self, start_date, end_date, period_name, period_complete_day):
+ """ Fetches stats about data downloads """
+ import ckan.model as model
+
+ data = {}
+
+ results = self.service.data().ga().get(
+ ids='ga:' + self.profile_id,
+ start_date=start_date,
+ filters='ga:eventAction==download',
+ metrics='ga:totalEvents',
+ sort='-ga:totalEvents',
+ dimensions="ga:eventLabel",
+ max_results=10000,
+ end_date=end_date).execute()
+ result_data = results.get('rows')
+ if not result_data:
+ # We may not have data for this time period, so we need to bail
+ # early.
+ log.info("There is no download data for this time period")
+ return
+
+ def process_result_data(result_data, cached=False):
+ progress_total = len(result_data)
+ progress_count = 0
+ resources_not_matched = []
+ for result in result_data:
+ progress_count += 1
+ if progress_count % 100 == 0:
+ log.debug('.. %d/%d done so far', progress_count, progress_total)
+
+ url = result[0].strip()
+
+ # Get package id associated with the resource that has this URL.
+ q = model.Session.query(model.Resource)
+ if cached:
+ r = q.filter(model.Resource.cache_url.like("%s%%" % url)).first()
+ else:
+ r = q.filter(model.Resource.url.like("%s%%" % url)).first()
+
+ package_name = r.resource_group.package.name if r else ""
+ if package_name:
+ data[package_name] = data.get(package_name, 0) + int(result[1])
+ else:
+ resources_not_matched.append(url)
+ continue
+ if resources_not_matched:
+ log.debug('Could not match %i or %i resource URLs to datasets. e.g. %r',
+ len(resources_not_matched), progress_total, resources_not_matched[:3])
+
+ log.info('Associating downloads of resource URLs with their respective datasets')
+ process_result_data(results.get('rows'))
+
+ results = self.service.data().ga().get(
+ ids='ga:' + self.profile_id,
+ start_date=start_date,
+ filters='ga:eventAction==download-cache',
+ metrics='ga:totalEvents',
+ sort='-ga:totalEvents',
+ dimensions="ga:eventLabel",
+ max_results=10000,
+ end_date=end_date).execute()
+ log.info('Associating downloads of cache resource URLs with their respective datasets')
+ process_result_data(results.get('rows'), cached=False)
+
+ self._filter_out_long_tail(data, MIN_DOWNLOADS)
+ ga_model.update_sitewide_stats(period_name, "Downloads", data, period_complete_day)
def _social_stats(self, start_date, end_date, period_name, period_complete_day):
""" Finds out which social sites people are referred from """
--- a/ckanext/ga_report/ga_model.py
+++ b/ckanext/ga_report/ga_model.py
@@ -161,21 +161,64 @@
def pre_update_url_stats(period_name):
- log.debug("Deleting '%s' records" % period_name)
- model.Session.query(GA_Url).\
- filter(GA_Url.period_name==period_name).delete()
-
- count = model.Session.query(GA_Url).\
- filter(GA_Url.period_name == 'All').count()
- log.debug("Deleting %d 'All' records" % count)
- model.Session.query(GA_Url).\
- filter(GA_Url.period_name == 'All').delete()
- log.debug("Query: %s" % model.Session.query(GA_Url).
- filter(GA_Url.period_name == 'All'))
+ q = model.Session.query(GA_Url).\
+ filter(GA_Url.period_name==period_name)
+ log.debug("Deleting %d '%s' records" % (q.count(), period_name))
+ q.delete()
+
+ q = model.Session.query(GA_Url).\
+ filter(GA_Url.period_name == 'All')
+ log.debug("Deleting %d 'All' records..." % q.count())
+ q.delete()
model.Session.flush()
model.Session.commit()
model.repo.commit_and_remove()
+ log.debug('...done')
+
+def post_update_url_stats():
+
+ """ Check the distinct url field in ga_url and make sure
+ it has an All record. If not then create one.
+
+ After running this then every URL should have an All
+ record regardless of whether the URL has an entry for
+ the month being currently processed.
+ """
+ log.debug('Post-processing "All" records...')
+ query = """select url, pageviews::int, visits::int
+ from ga_url
+ where url not in (select url from ga_url where period_name ='All')"""
+ connection = model.Session.connection()
+ res = connection.execute(query)
+
+ views, visits = {}, {}
+ # url, views, visits
+ for row in res:
+ views[row[0]] = views.get(row[0], 0) + row[1]
+ visits[row[0]] = visits.get(row[0], 0) + row[2]
+
+ progress_total = len(views.keys())
+ progress_count = 0
+ for key in views.keys():
+ progress_count += 1
+ if progress_count % 100 == 0:
+ log.debug('.. %d/%d done so far', progress_count, progress_total)
+
+ package, publisher = _get_package_and_publisher(key)
+
+ values = {'id': make_uuid(),
+ 'period_name': "All",
+ 'period_complete_day': 0,
+ 'url': key,
+ 'pageviews': views[key],
+ 'visits': visits[key],
+ 'department_id': publisher,
+ 'package_id': package
+ }
+ model.Session.add(GA_Url(**values))
+ model.Session.commit()
+ log.debug('..done')
def update_url_stats(period_name, period_complete_day, url_data):
@@ -184,9 +227,14 @@
stores them in GA_Url under the period and recalculates the totals for
the 'All' period.
'''
+ progress_total = len(url_data)
+ progress_count = 0
for url, views, visits in url_data:
+ progress_count += 1
+ if progress_count % 100 == 0:
+ log.debug('.. %d/%d done so far', progress_count, progress_total)
+
package, publisher = _get_package_and_publisher(url)
-
item = model.Session.query(GA_Url).\
filter(GA_Url.period_name==period_name).\
--- a/ckanext/ga_report/helpers.py
+++ b/ckanext/ga_report/helpers.py
@@ -106,6 +106,10 @@
for entry in entries:
if len(datasets) < count:
p = model.Package.get(entry.url[len('/dataset/'):])
+ if not p:
+ _log.warning("Could not find Package for {url}".format(url=entry.url))
+ continue
+
if not p in datasets:
datasets[p] = {'views':0, 'visits': 0}
datasets[p]['views'] = datasets[p]['views'] + int(entry.pageviews)
--- a/ckanext/ga_report/plugin.py
+++ b/ckanext/ga_report/plugin.py
@@ -42,6 +42,16 @@
controller='ckanext.ga_report.controller:GaReport',
action='csv'
)
+ map.connect(
+ '/data/site-usage/downloads',
+ controller='ckanext.ga_report.controller:GaReport',
+ action='downloads'
+ )
+ map.connect(
+ '/data/site-usage/downloads_{month}.csv',
+ controller='ckanext.ga_report.controller:GaReport',
+ action='csv_downloads'
+ )
# GaDatasetReport
map.connect(
--- /dev/null
+++ b/ckanext/ga_report/public/css/ga_report.css
@@ -1,1 +1,64 @@
+.table-condensed td.sparkline-cell {
+ 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;
+ height: 350px;
+ margin: 0 auto 20px auto;
+}
+.rickshaw_chart {
+ position: absolute;
+ left: 40px;
+ width: 500px;
+ top: 0;
+ bottom: 0;
+}
+.rickshaw_legend {
+ background: transparent;
+ width: 100%;
+ padding-top: 4px;
+}
+.rickshaw_y_axis {
+ position: absolute;
+ top: 0;
+ bottom: 0;
+ width: 40px;
+}
+.rickshaw_legend .label {
+ background: transparent !important;
+ color: #000000 !important;
+ font-weight: normal !important;
+}
+.rickshaw_legend .instructions {
+ color: #000;
+ margin-bottom: 6px;
+}
+.rickshaw_legend .line .action {
+ display: none;
+}
+.rickshaw_legend .line .swatch {
+ display: block;
+ float: left;
+}
+.rickshaw_legend .line .label {
+ display: block;
+ white-space: normal;
+ float: left;
+ width: 200px;
+}
+.rickshaw_legend .line .label:hover {
+ text-decoration: underline;
+}
+
+.ga-reports-table .td-numeric {
+ text-align: center;
+}
+
--- /dev/null
+++ b/ckanext/ga_report/public/scripts/ckanext_ga_reports.js
@@ -1,1 +1,122 @@
+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 } );
+ $.each(data, function(i, object) {
+ object['color'] = palette.color();
+ });
+ // Rickshaw renders the legend in reverse order...
+ data.reverse();
+
+ var graphElement = document.querySelector("#chart_"+css_name);
+
+ var graph = new Rickshaw.Graph( {
+ element: document.querySelector("#chart_"+css_name),
+ renderer: mode,
+ series: data ,
+ height: 328
+ });
+ var x_axis = new Rickshaw.Graph.Axis.Time( { graph: graph } );
+ var y_axis = new Rickshaw.Graph.Axis.Y( {
+ graph: graph,
+ orientation: 'left',
+ tickFormat: Rickshaw.Fixtures.Number.formatKMBT,
+ element: document.getElementById('y_axis_'+css_name)
+ } );