[385] Use page_heading helper function consistently
--- a/ckanext/ga_report/controller.py
+++ b/ckanext/ga_report/controller.py
@@ -191,25 +191,11 @@
q = model.Session.query(GA_Stat).\
filter(GA_Stat.stat_name==k).\
order_by(GA_Stat.period_name)
- # Run the query on all months to gather graph data
- graph = {}
- for stat in q:
- graph[ stat.key ] = graph.get(stat.key,{
- 'name':stat.key,
- 'data': []
- })
- graph[ stat.key ]['data'].append({
- 'x':_get_unix_epoch(stat.period_name),
- 'y':float(stat.value)
- })
- setattr(c, v+'_graph', json.dumps( _to_rickshaw(graph.values(),percentageMode=True) ))
-
# 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)
@@ -218,6 +204,23 @@
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,
+ 'raw': {}
+ })
+ graph_dict[ stat.key ]['raw'][stat.period_name] = float(stat.value)
+ 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
# a percentage of the total
if k == 'Social sources':
@@ -246,7 +249,7 @@
writer = csv.writer(response)
writer.writerow(["Publisher Title", "Publisher Name", "Views", "Visits", "Period Name"])
- top_publishers, top_publishers_graph = _get_top_publishers(None)
+ top_publishers = _get_top_publishers(limit=None)
for publisher,view,visit in top_publishers:
writer.writerow([publisher.title.encode('utf-8'),
@@ -268,7 +271,7 @@
if not c.publisher:
abort(404, 'A publisher with that name could not be found')
- packages = self._get_packages(c.publisher)
+ packages = self._get_packages(publisher=c.publisher, month=c.month)
response.headers['Content-Type'] = "text/csv; charset=utf-8"
response.headers['Content-Disposition'] = \
str('attachment; filename=datasets_%s_%s.csv' % (c.publisher_name, month,))
@@ -297,15 +300,16 @@
if c.month:
c.month_desc = ''.join([m[1] for m in c.months if m[0]==c.month])
- c.top_publishers, graph_data = _get_top_publishers()
- c.top_publishers_graph = json.dumps( _to_rickshaw(graph_data.values()) )
+ c.top_publishers = _get_top_publishers()
+ graph_data = _get_top_publishers_graph()
+ 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):
+ def _get_packages(self, publisher=None, month='', count=-1):
'''Returns the datasets in order of views'''
have_download_data = True
- month = c.month or 'All'
+ month = month or 'All'
if month != 'All':
have_download_data = month >= DOWNLOADS_AVAILABLE_FROM
@@ -382,78 +386,71 @@
entry = q.filter(GA_Url.period_name==c.month).first()
c.publisher_page_views = entry.pageviews if entry else 0
- c.top_packages = self._get_packages(c.publisher, 20)
+ c.top_packages = self._get_packages(publisher=c.publisher, count=20, month=c.month)
# Graph query
- top_package_names = [ x[0].name for x in c.top_packages ]
+ top_packages_all_time = self._get_packages(publisher=c.publisher, count=20, month='All')
+ top_package_names = [ x[0].name for x in top_packages_all_time ]
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_data = {}
+ all_series = {}
for entry,package in graph_query:
if not package: continue
if entry.period_name=='All': continue
- graph_data[package.id] = graph_data.get(package.id,{
+ all_series[package.name] = all_series.get(package.name,{
'name':package.title,
- 'data':[]
+ 'raw': {}
})
- graph_data[package.id]['data'].append({
- 'x':_get_unix_epoch(entry.period_name),
- 'y':int(entry.pageviews),
- })
-
- c.graph_data = json.dumps( _to_rickshaw(graph_data.values()) )
+ all_series[package.name]['raw'][entry.period_name] = int(entry.pageviews)
+ graph = [ all_series[series_name] for series_name 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
- num_points = [ len(package['data']) for package 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'] ]
- 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)))
- if percentageMode:
- # 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']
- # Roll insignificant series into a catch-all
- THRESHOLD = 0.01
- significant_series = []
- for package in data:
- for point in package['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)
- # Turn each point into a percentage
- for package in data:
- for point in package['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'] )
- # Strip the latest month's incomplete analytics
- package['data'] = package['data'][:-1]
+ # x-axis is every month in c.months. Note that data might not exist
+ # for entire history, eg. for recently-added datasets
+ x_axis = [x[0] for x in c.months]
+ x_axis.reverse() # Ascending order
+ x_axis = x_axis[:-1] # Remove latest month
+ totals = {}
+ for series in data:
+ series['data'] = []
+ for x_string in x_axis:
+ x = _get_unix_epoch( x_string )
+ y = series['raw'].get(x_string,0)
+ series['data'].append({'x':x,'y':y})
+ totals[x] = totals.get(x,0)+y
+ if not percentageMode:
+ return data
+ # Turn all data into percentages
+ # Roll insignificant series into a catch-all
+ THRESHOLD = 1
+ raw_data = data
+ data = []
+ for series in raw_data:
+ for point in series['data']:
+ percentage = (100*float(point['y'])) / totals[point['x']]
+ if not (series in data) and percentage>THRESHOLD:
+ data.append(series)
+ point['y'] = percentage
+ others = [ x for x in raw_data if not (x in data) ]
+ if len(others):
+ data_other = []
+ for i in range(len(x_axis)):
+ x = _get_unix_epoch(x_axis[i])
+ y = 0
+ for series in others:
+ y += series['data'][i]['y']
+ data_other.append({'x':x,'y':y})
+ data.append({
+ 'name':'Other',
+ 'data': data_other
+ })
return data
@@ -478,35 +475,51 @@
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]))
-
- 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 )
- for dept_id,period_name,views in q:
- graph[dept_id] = graph.get( dept_id, {
- 'name' : model.Group.get(dept_id).title,
- 'data' : []
- })
- graph[dept_id]['data'].append({
- 'x': _get_unix_epoch(period_name),
- 'y': views
- })
- return top_publishers, graph
+ return top_publishers
+
+
+def _get_top_publishers_graph(limit=20):
+ '''
+ Returns a list of the top 20 publishers by dataset visits.
+ (The number to show can be varied with 'limit')
+ '''
+ connection = model.Session.connection()
+ q = """
+ select department_id, sum(pageviews::int) views
+ from ga_url
+ where department_id <> ''
+ and package_id <> ''
+ and url like '/dataset/%%'
+ and period_name='All'
+ group by department_id order by views desc
+ """
+ if limit:
+ q = q + " limit %s;" % (limit)
+
+ res = connection.execute(q)
+ department_ids = [ row[0] for row in res ]
+
+ # Query for a history graph of these department ids
+ 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.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,
+ 'raw' : {}
+ })
+ graph_dict[dept_id]['raw'][period_name] = views
+ return [ graph_dict[id] for id in department_ids ]
def _get_publishers():
--- a/ckanext/ga_report/public/scripts/vendor/rickshaw.min.js
+++ b/ckanext/ga_report/public/scripts/vendor/rickshaw.min.js
@@ -1,2 +1,3 @@
-var Rickshaw={namespace:function(a,b){var c=a.split("."),d=Rickshaw;for(var e=1,f=c.length;e<f;e++)currentPart=c[e],d[currentPart]=d[currentPart]||{},d=d[currentPart];return d},keys:function(a){var b=[];for(var c in a)b.push(c);return b},extend:function(a,b){for(var c in b)a[c]=b[c];return a}};if(typeof module!="undefined"&&module.exports){var d3=require("d3");module.exports=Rickshaw}(function(a){function j(a){return b.call(a)===i}function k(a,b){for(var c in b)b.hasOwnProperty(c)&&(a[c]=b[c]);return a}function l(a){if(m(a)!==h)throw new TypeError;var b=[];for(var c in a)a.hasOwnProperty(c)&&b.push(c);return b}function m(a){switch(a){case null:return c;case void 0:return d}var b=typeof a;switch(b){case"boolean":return e;case"number":return f;case"string":return g}return h}function n(a){return typeof a=="undefined"}function p(a){var b=a.toString().match(/^[\s\(]*function[^(]*\(([^)]*)\)/)[1].replace(/\/\/.*?[\r\n]|\/\*(?:.|[\r\n])*?\*\//g,"").replace(/\s+/g,"").split(",");return b.length==1&&!b[0]?[]:b}function q(a,b){var c=a;return function(){var a=r([t(c,this)],arguments);return b.apply(this,a)}}function r(a,b){var c=a.length,d=b.length;while(d--)a[c+d]=b[d];return a}function s(a,b){return a=o.call(a,0),r(a,b)}function t(a,b){if(arguments.length<2&&n(arguments[0]))return this;var c=a,d=o.call(arguments,2);return function(){var a=s(d,arguments);return c.apply(b,a)}}var b=Object.prototype.toString,c="Null",d="Undefined",e="Boolean",f="Number",g="String",h="Object",i="[object Function]",o=Array.prototype.slice,u=function(){},v=function(){function b(){}function c(){function d(){this.initialize.apply(this,arguments)}var a=null,c=[].slice.apply(arguments);j(c[0])&&(a=c.shift()),k(d,v.Methods),d.superclass=a,d.subclasses=[];if(a){b.prototype=a.prototype,d.prototype=new b;try{a.subclasses.push(d)}catch(e){}}for(var f=0,g=c.length;f<g;f++)d.addMethods(c[f]);return d.prototype.initialize||(d.prototype.initialize=u),d.prototype.constructor=d,d}function d(b){var c=this.superclass&&this.superclass.prototype,d=l(b);a&&(b.toString!=Object.prototype.toString&&d.push("toString"),b.valueOf!=Object.prototype.valueOf&&d.push("valueOf"));for(var e=0,f=d.length;e<f;e++){var g=d[e],h=b[g];if(c&&j(h)&&p(h)[0]=="$super"){var i=h;h=q(function(a){return function(){return c[a].apply(this,arguments)}}(g),i),h.valueOf=t(i.valueOf,i),h.toString=t(i.toString,i)}this.prototype[g]=h}return this}var a=function(){for(var a in{toString:1})if(a==="toString")return!1;return!0}();return{create:c,Methods:{addMethods:d}}}();a.exports?a.exports.Class=v:a.Class=v})(Rickshaw),Rickshaw.namespace("Rickshaw.Compat.ClassList"),Rickshaw.Compat.ClassList=function(){typeof document!="undefined"&&!("classList"in document.createElement("a"))&&function(a){"use strict";var b="classList",c="prototype",d=(a.HTMLElement||a.Element)[c],e=Object,f=String[c].trim||function(){return this.replace(/^\s+|\s+$/g,"")},g=Array[c].indexOf||function(a){var b=0,c=this.length;for(;b<c;b++)if(b in this&&this[b]===a)return b;return-1},h=function(a,b){this.name=a,this.code=DOMException[a],this.message=b},i=function(a,b){if(b==="")throw new h("SYNTAX_ERR","An invalid or illegal string was specified");if(/\s/.test(b))throw new h("INVALID_CHARACTER_ERR","String contains an invalid character");return g.call(a,b)},j=function(a){var b=f.call(a.className),c=b?b.split(/\s+/):[],d=0,e=c.length;for(;d<e;d++)this.push(c[d]);this._updateClassName=function(){a.className=this.toString()}},k=j[c]=[],l=function(){return new j(this)};h[c]=Error[c],k.item=function(a){return this[a]||null},k.contains=function(a){return a+="",i(this,a)!==-1},k.add=function(a){a+="",i(this,a)===-1&&(this.push(a),this._updateClassName())},k.remove=function(a){a+="";var b=i(this,a);b!==-1&&(this.splice(b,1),this._updateClassName())},k.toggle=function(a){a+="",i(this,a)===-1?this.add(a):this.remove(a)},k.toString=function(){return this.join(" ")};if(e.defineProperty){var m={get:l,enumerable:!0,configurable:!0};try{e.defineProperty(d,b,m)}catch(n){n.number===-2146823252&&(m.enumerable=!1,e.defineProperty(d,b,m))}}else e[c].__defineGetter__&&d.__defineGetter__(b,l)}(window)},(typeof RICKSHAW_NO_COMPAT!="undefined"&&!RICKSHAW_NO_COMPAT||typeof RICKSHAW_NO_COMPAT=="undefined")&&new Rickshaw.Compat.ClassList,Rickshaw.namespace("Rickshaw.Graph"),Rickshaw.Graph=function(a){this.element=a.element,this.series=a.series,this.defaults={interpolation:"cardinal",offset:"zero",min:undefined,max:undefined},Rickshaw.keys(this.defaults).forEach(function(b){this[b]=a[b]||this.defaults[b]},this),this.window={},this.updateCallbacks=[];var b=this;this.initialize=function(a){this.validateSeries(a.series),this.series.active=function(){return b.series.filter(function(a){return!a.disabled})},this.setSize({width:a.width,height:a.height}),this.element.classList.add("rickshaw_graph"),this.vis=d3.select(this.element).append("svg:svg").attr("width",this.width).attr("height",this.height);var c=[Rickshaw.Graph.Renderer.Stack,Rickshaw.Graph.Renderer.Line,Rickshaw.Graph.Renderer.Bar,Rickshaw.Graph.Renderer.Area,Rickshaw.Graph.Renderer.ScatterPlot];c.forEach(function(a){if(!a)return;b.registerRenderer(new a({graph:b}))}),this.setRenderer(a.renderer||"stack",a),this.discoverRange()},this.validateSeries=function(a){if(!(a instanceof Array||a instanceof Rickshaw.Series)){var b=Object.prototype.toString.apply(a);throw"series is not an array: "+b}var c;a.forEach(function(a){if(!(a instanceof Object))throw"series element is not an object: "+a;if(!a.data)throw"series has no data: "+JSON.stringify(a);if(!(a.data instanceof Array))throw"series data is not an array: "+JSON.stringify(a.data);c=c||a.data.length;if(c&&a.data.length!=c)throw"series cannot have differing numbers of points: "+c+" vs "+a.data.length+"; see Rickshaw.Series.zeroFill()";var b=typeof a.data[0].x,d=typeof a.data[0].y;if(b!="number"||d!="number")throw"x and y properties of points should be numbers instead of "+b+" and "+d})},this.dataDomain=function(){var a=this.series[0].data;return[a[0].x,a.slice(-1).shift().x]},this.discoverRange=function(){var a=this.renderer.domain();this.x=d3.scale.linear().domain(a.x).range([0,this.width]),this.y=d3.scale.linear().domain(a.y).range([this.height,0]),this.y.magnitude=d3.scale.linear().domain([a.y[0]-a.y[0],a.y[1]-a.y[0]]).range([0,this.height])},this.render=function(){var a=this.stackData();this.discoverRange(),this.renderer.render(),this.updateCallbacks.forEach(function(a){a()})},this.update=this.render,this.stackData=function(){var a=this.series.active().map(function(a){return a.data}).map(function(a){return a.filter(function(a){return this._slice(a)},this)},this);this.stackData.hooks.data.forEach(function(c){a=c.f.apply(b,[a])});var c=d3.layout.stack();c.offset(b.offset);var d=c(a);this.stackData.hooks.after.forEach(function(c){d=c.f.apply(b,[a])});var e=0;return this.series.forEach(function(a){if(a.disabled)return;a.stack=d[e++]}),this.stackedData=d,d},this.stackData.hooks={data:[],after:[]},this._slice=function(a){if(this.window.xMin||this.window.xMax){var b=!0;return this.window.xMin&&a.x<this.window.xMin&&(b=!1),this.window.xMax&&a.x>this.window.xMax&&(b=!1),b}return!0},this.onUpdate=function(a){this.updateCallbacks.push(a)},this.registerRenderer=function(a){this._renderers=this._renderers||{},this._renderers[a.name]=a},this.configure=function(a){(a.width||a.height)&&this.setSize(a),Rickshaw.keys(this.defaults).forEach(function(b){this[b]=b in a?a[b]:b in this?this[b]:this.defaults[b]},this),this.setRenderer(a.renderer||this.renderer.name,a)},this.setRenderer=function(a,b){if(!this._renderers[a])throw"couldn't find renderer "+a;this.renderer=this._renderers[a],typeof b=="object"&&this.renderer.configure(b)},this.setSize=function(a){a=a||{};if(typeof window!==undefined)var b=window.getComputedStyle(this.element,null),c=parseInt(b.getPropertyValue("width")),d=parseInt(b.getPropertyValue("height"));this.width=a.width||c||400,this.height=a.height||d||250,this.vis&&this.vis.attr("width",this.width).attr("height",this.height)},this.initialize(a)},Rickshaw.namespace("Rickshaw.Fixtures.Color"),Rickshaw.Fixtures.Color=function(){this.schemes={},this.schemes.spectrum14=["#ecb796","#dc8f70","#b2a470","#92875a","#716c49","#d2ed82","#bbe468","#a1d05d","#e7cbe6","#d8aad6","#a888c2","#9dc2d3","#649eb9","#387aa3"].reverse(),this.schemes.spectrum2000=["#57306f","#514c76","#646583","#738394","#6b9c7d","#84b665","#a7ca50","#bfe746","#e2f528","#fff726","#ecdd00","#d4b11d","#de8800","#de4800","#c91515","#9a0000","#7b0429","#580839","#31082b"],this.schemes.spectrum2001=["#2f243f","#3c2c55","#4a3768","#565270","#6b6b7c","#72957f","#86ad6e","#a1bc5e","#b8d954","#d3e04e","#ccad2a","#cc8412","#c1521d","#ad3821","#8a1010","#681717","#531e1e","#3d1818","#320a1b"],this.schemes.classic9=["#423d4f","#4a6860","#848f39","#a2b73c","#ddcb53","#c5a32f","#7d5836","#963b20","#7c2626","#491d37","#2f254a"].reverse(),this.schemes.httpStatus={503:"#ea5029",502:"#d23f14",500:"#bf3613",410:"#efacea",409:"#e291dc",403:"#f457e8",408:"#e121d2",401:"#b92dae",405:"#f47ceb",404:"#a82a9f",400:"#b263c6",301:"#6fa024",302:"#87c32b",307:"#a0d84c",304:"#28b55c",200:"#1a4f74",206:"#27839f",201:"#52adc9",202:"#7c979f",203:"#a5b8bd",204:"#c1cdd1"},this.schemes.colorwheel=["#b5b6a9","#858772","#785f43","#96557e","#4682b4","#65b9ac","#73c03a","#cb513a"].reverse(),this.schemes.cool=["#5e9d2f","#73c03a","#4682b4","#7bc3b8","#a9884e","#c1b266","#a47493","#c09fb5"],this.schemes.munin=["#00cc00","#0066b3","#ff8000","#ffcc00","#330099","#990099","#ccff00","#ff0000","#808080","#008f00","#00487d","#b35a00","#b38f00","#6b006b","#8fb300","#b30000","#bebebe","#80ff80","#80c9ff","#ffc080","#ffe680","#aa80ff","#ee00cc","#ff8080","#666600","#ffbfff","#00ffcc","#cc6699","#999900"]},Rickshaw.namespace("Rickshaw.Fixtures.RandomData"),Rickshaw.Fixtures.RandomData=function(a){var b;a=a||1;var c=200,d=Math.floor((new Date).getTime()/1e3);this.addData=function(b){var e=Math.random()*100+15+c,f=b[0].length,g=1;b.forEach(function(b){var c=Math.random()*20,h=e/25+g++ +(Math.cos(f*g*11/960)+2)*15+(Math.cos(f/7)+2)*7+(Math.cos(f/17)+2)*1;b.push({x:f*a+d,y:h+c})}),c=e*.85}},Rickshaw.namespace("Rickshaw.Fixtures.Time"),Rickshaw.Fixtures.Time=function(){var a=(new Date).getTimezoneOffset()*60,b=this;this.months=["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"],this.units=[{name:"decade",seconds:315576e3,formatter:function(a){return parseInt(a.getUTCFullYear()/10)*10}},{name:"year",seconds:31557600,formatter:function(a){return a.getUTCFullYear()}},{name:"month",seconds:2635200,formatter:function(a){return b.months[a.getUTCMonth()]}},{name:"week",seconds:604800,formatter:function(a){return b.formatDate(a)}},{name:"day",seconds:86400,formatter:function(a){return a.getUTCDate()}},{name:"6 hour",seconds:21600,formatter:function(a){return b.formatTime(a)}},{name:"hour",seconds:3600,formatter:function(a){return b.formatTime(a)}},{name:"15 minute",seconds:900,formatter:function(a){return b.formatTime(a)}},{name:"minute",seconds:60,formatter:function(a){return a.getUTCMinutes()}},{name:"15 second",seconds:15,formatter:function(a){return a.getUTCSeconds()+"s"}},{name:"second",seconds:1,formatter:function(a){return a.getUTCSeconds()+"s"}}],this.unit=function(a){return this.units.filter(function(b){return a==b.name}).shift()},this.formatDate=function(a){return a.toUTCString().match(/, (\w+ \w+ \w+)/)[1]},this.formatTime=function(a){return a.toUTCString().match(/(\d+:\d+):/)[1]},this.ceil=function(a,b){if(b.name=="month"){var c=new Date((a+b.seconds-1)*1e3),d=new Date(0);return d.setUTCFullYear(c.getUTCFullYear()),d.setUTCMonth(c.getUTCMonth()),d.setUTCDate(1),d.setUTCHours(0),d.setUTCMinutes(0),d.setUTCSeconds(0),d.setUTCMilliseconds(0),d.getTime()/1e3}if(b.name=="year"){var c=new Date((a+b.seconds-1)*1e3),d=new Date(0);return d.setUTCFullYear(c.getUTCFullYear()),d.setUTCMonth(0),d.setUTCDate(1),d.setUTCHours(0),d.setUTCMinutes(0),d.setUTCSeconds(0),d.setUTCMilliseconds(0),d.getTime()/1e3}return Math.ceil(a/b.seconds)*b.seconds}},Rickshaw.namespace("Rickshaw.Fixtures.Number"),Rickshaw.Fixtures.Number.formatKMBT=function(a){return a>=1e12?a/1e12+"T":a>=1e9?a/1e9+"B":a>=1e6?a/1e6+"M":a>=1e3?a/1e3+"K":a<1&&a>0?a.toFixed(2):a==0?"":a},Rickshaw.Fixtures.Number.formatBase1024KMGTP=function(a){return a>=0x4000000000000?a/0x4000000000000+"P":a>=1099511627776?a/1099511627776+"T":a>=1073741824?a/1073741824+"G":a>=1048576?a/1048576+"M":a>=1024?a/1024+"K":a<1&&a>0?a.toFixed(2):a==0?"":a},Rickshaw.namespace("Rickshaw.Color.Palette"),Rickshaw.Color.Palette=function(a){var b=new Rickshaw.Fixtures.Color;a=a||{},this.schemes={},this.scheme=b.schemes[a.scheme]||a.scheme||b.schemes.colorwheel,this.runningIndex=0,this.generatorIndex=0;if(a.interpolatedStopCount){var c=this.scheme.length-1,d,e,f=[];for(d=0;d<c;d++){f.push(this.scheme[d]);var g=d3.interpolateHsl(this.scheme[d],this.scheme[d+1]);for(e=1;e<a.interpolatedStopCount;e++)f.push(g(1/a.interpolatedStopCount*e))}f.push(this.scheme[this.scheme.length-1]),this.scheme=f}this.rotateCount=this.scheme.length,this.color=function(a){return this.scheme[a]||this.scheme[this.runningIndex++]||this.interpolateColor()||"#808080"},this.interpolateColor=function(){if(!Array.isArray(this.scheme))return;var a;return this.generatorIndex==this.rotateCount*2-1?(a=d3.interpolateHsl(this.scheme[this.generatorIndex],this.scheme[0])(.5),this.generatorIndex=0,this.rotateCount*=2):(a=d3.interpolateHsl(this.scheme[this.generatorIndex],this.scheme[this.generatorIndex+1])(.5),this.generatorIndex++),this.scheme.push(a),a}},Rickshaw.namespace("Rickshaw.Graph.Ajax"),Rickshaw.Graph.Ajax=Rickshaw.Class.create({initialize:function(a){this.dataURL=a.dataURL,this.onData=a.onData||function(a){return a},this.onComplete=a.onComplete||function(){},this.onError=a.onError||function(){},this.args=a,this.request()},request:function(){$.ajax({url:this.dataURL,dataType:"json",success:this.success.bind(this),error:this.error.bind(this)})},error:function(){console.log("error loading dataURL: "+this.dataURL),this.onError(this)},success:function(a,b){a=this.onData(a),this.args.series=this._splice({data:a,series:this.args.series}),this.graph=new Rickshaw.Graph(this.args),this.graph.render(),this.onComplete(this)},_splice:function(a){var b=a.data,c=a.series;return a.series?(c.forEach(function(a){var c=a.key||a.name;if(!c)throw"series needs a key or a name";b.forEach(function(b){var d=b.key||b.name;if(!d)throw"data needs a key or a name";if(c==d){var e=["color","name","data"];e.forEach(function(c){a[c]=a[c]||b[c]})}})}),c):b}}),Rickshaw.namespace("Rickshaw.Graph.Annotate"),Rickshaw.Graph.Annotate=function(a){var b=this.graph=a.graph;this.elements={timeline:a.element};var c=this;this.data={},this.elements.timeline.classList.add("rickshaw_annotation_timeline"),this.add=function(a,b,d){c.data[a]=c.data[a]||{boxes:[]},c.data[a].boxes.push({content:b,end:d})},this.update=function(){Rickshaw.keys(c.data).forEach(function(a){var b=c.data[a],d=c.graph.x(a);if(d<0||d>c.graph.x.range()[1]){b.element&&(b.line.classList.add("offscreen"),b.element.style.display="none"),b.boxes.forEach(function(a){a.rangeElement&&a.rangeElement.classList.add("offscreen")});return}if(!b.element){var e=b.element=document.createElement("div");e.classList.add("annotation"),this.elements.timeline.appe