Done integrating graphs onto site_usage/publishers and site_usage/datasets. Including some interesting queries.
Done integrating graphs onto site_usage/publishers and site_usage/datasets. Including some interesting queries.

file:a/.gitignore -> file:b/.gitignore
*.py[co] *.py[co]
*.py~ *.py~
.gitignore .gitignore
  ckan.log
   
# Packages # Packages
*.egg *.egg
*.egg-info *.egg-info
dist dist
build build
eggs eggs
parts parts
bin bin
var var
sdist sdist
develop-eggs develop-eggs
.installed.cfg .installed.cfg
   
# Private info # Private info
credentials.json credentials.json
token.dat token.dat
   
# Installer logs # Installer logs
pip-log.txt pip-log.txt
   
# Unit test / coverage reports # Unit test / coverage reports
.coverage .coverage
.tox .tox
   
#Translations #Translations
*.mo *.mo
   
#Mr Developer #Mr Developer
.mr.developer.cfg .mr.developer.cfg
   
file:a/README.rst -> file:b/README.rst
ckanext-ga-report ckanext-ga-report
================= =================
   
**Status:** Development **Status:** Development
   
**CKAN Version:** 1.7.1+ **CKAN Version:** 1.7.1+
   
   
Overview Overview
-------- --------
   
For creating detailed reports of CKAN analytics, including totals per group. For creating detailed reports of CKAN analytics, including totals per group.
   
Whereas ckanext-googleanalytics focusses on providing page view stats a recent period and for all time (aimed at end users), ckanext-ga-report is more interested in building regular periodic reports (more for site managers to monitor). Whereas ckanext-googleanalytics focusses on providing page view stats a recent period and for all time (aimed at end users), ckanext-ga-report is more interested in building regular periodic reports (more for site managers to monitor).
   
Contents of this extension: Contents of this extension:
   
* Use the CLI tool to download Google Analytics data for each time period into this extension's database tables * Use the CLI tool to download Google Analytics data for each time period into this extension's database tables
   
* Users can view the data as web page reports * Users can view the data as web page reports
   
   
Installation Installation
------------ ------------
   
1. Activate you CKAN python environment and install this extension's software:: 1. Activate you CKAN python environment and install this extension's software::
   
$ pyenv/bin/activate $ pyenv/bin/activate
$ pip install -e git+https://github.com/datagovuk/ckanext-ga-report.git#egg=ckanext-ga-report $ pip install -e git+https://github.com/datagovuk/ckanext-ga-report.git#egg=ckanext-ga-report
   
2. Ensure you development.ini (or similar) contains the info about your Google Analytics account and configuration:: 2. Ensure you development.ini (or similar) contains the info about your Google Analytics account and configuration::
   
googleanalytics.id = UA-1010101-1 googleanalytics.id = UA-1010101-1
googleanalytics.account = Account name (i.e. data.gov.uk, see top level item at https://www.google.com/analytics) googleanalytics.account = Account name (e.g. data.gov.uk, see top level item at https://www.google.com/analytics)
  googleanalytics.token.filepath = ~/pyenv/token.dat
ga-report.period = monthly ga-report.period = monthly
  ga-report.bounce_url = /
   
Note that your credentials will be readable by system administrators on your server. Rather than use sensitive account details, it is suggested you give access to the GA account to a new Google account that you create just for this purpose. The ga-report.bounce_url specifies a particular path to record the bounce rate for. Typically it is / (the home page).
   
3. Set up this extension's database tables using a paster command. (Ensure your CKAN pyenv is still activated, run the command from ``src/ckanext-ga-report``, alter the ``--config`` option to point to your site config file):: 3. Set up this extension's database tables using a paster command. (Ensure your CKAN pyenv is still activated, run the command from ``src/ckanext-ga-report``, alter the ``--config`` option to point to your site config file)::
   
$ paster initdb --config=../ckan/development.ini $ paster initdb --config=../ckan/development.ini
   
4. Enable the extension in your CKAN config file by adding it to ``ckan.plugins``:: 4. Enable the extension in your CKAN config file by adding it to ``ckan.plugins``::
   
ckan.plugins = ga-report ckan.plugins = ga-report
   
  Problem shooting
  ----------------
   
  * ``(ProgrammingError) relation "ga_url" does not exist``
  This means that the ``paster initdb`` step has not been run successfully. Refer to the installation instructions for this extension.
   
   
Authorization Authorization
-------------- --------------
   
Before you can access the data, you need to set up the OAUTH details which you can do by following the `instructions <https://developers.google.com/analytics/resources/tutorials/hello-analytics-api>`_ the outcome of which will be a file called credentials.json which should look like credentials.json.template with the relevant fields completed. These steps are below for convenience: Before you can access the data, you need to set up the OAUTH details which you can do by following the `instructions <https://developers.google.com/analytics/resources/tutorials/hello-analytics-api>`_ the outcome of which will be a file called credentials.json which should look like credentials.json.template with the relevant fields completed. These steps are below for convenience:
   
1. Visit the `Google APIs Console <https://code.google.com/apis/console>`_ 1. Visit the `Google APIs Console <https://code.google.com/apis/console>`_
   
2. Sign-in and create a project or use an existing project. 2. Sign-in and create a project or use an existing project.
   
3. In the `Services pane <https://code.google.com/apis/console#:services>`_ , activate Analytics API for your project. If prompted, read and accept the terms of service. 3. In the `Services pane <https://code.google.com/apis/console#:services>`_ , activate Analytics API for your project. If prompted, read and accept the terms of service.
   
4. Go to the `API Access pane <https://code.google.com/apis/console/#:access>`_ 4. Go to the `API Access pane <https://code.google.com/apis/console/#:access>`_
   
5. Click Create an OAuth 2.0 client ID.... 5. Click Create an OAuth 2.0 client ID....
   
6. Fill out the Branding Information fields and click Next. 6. Fill out the Branding Information fields and click Next.
   
7. In Client ID Settings, set Application type to Installed application. 7. In Client ID Settings, set Application type to Installed application.
   
8. Click Create client ID 8. Click Create client ID
   
9. The details you need below are Client ID, Client secret, and Redirect URIs 9. The details you need below are Client ID, Client secret, and Redirect URIs
   
   
Once you have set up your credentials.json file you can generate an oauth token file by using the Once you have set up your credentials.json file you can generate an oauth token file by using the
following command, which will store your oauth token in a file called token.dat once you have finished following command, which will store your oauth token in a file called token.dat once you have finished
giving permission in the browser:: giving permission in the browser::
   
$ paster getauthtoken --config=../ckan/development.ini $ paster getauthtoken --config=../ckan/development.ini
   
  Now ensure you reference the correct path to your token.dat in your CKAN config file (e.g. development.ini)::
   
  googleanalytics.token.filepath = ~/pyenv/token.dat
   
   
Tutorial Tutorial
-------- --------
   
Download some GA data and store it in CKAN's db. (Ensure your CKAN pyenv is still activated, run the command from ``src/ckanext-ga-report``, alter the ``--config`` option to point to your site config file) and specifying the name of your auth file (token.dat by default) from the previous step:: Download some GA data and store it in CKAN's database. (Ensure your CKAN pyenv is still activated, run the command from ``src/ckanext-ga-report``, alter the ``--config`` option to point to your site config file) and specifying the name of your auth file (token.dat by default) from the previous step::
   
$ paster loadanalytics token.dat latest --config=../ckan/development.ini $ paster loadanalytics latest --config=../ckan/development.ini
   
The value after the token file is how much data you want to retrieve, this can be The value after the token file is how much data you want to retrieve, this can be
   
* **all** - data for all time (since 2010) * **all** - data for all time (since 2010)
   
* **latest** - (default) just the 'latest' data * **latest** - (default) just the 'latest' data
   
* **YYYY-MM-DD** - just data for all time periods going back to (and including) this date * **YYYY-MM-DD** - just data for all time periods going back to (and including) this date
   
   
   
Software Licence Software Licence
================ ================
   
This software is developed by Cabinet Office. It is Crown Copyright and opened up under the Open Government Licence (OGL) (which is compatible with Creative Commons Attibution License). This software is developed by Cabinet Office. It is Crown Copyright and opened up under the Open Government Licence (OGL) (which is compatible with Creative Commons Attibution License).
   
OGL terms: http://www.nationalarchives.gov.uk/doc/open-government-licence/ OGL terms: http://www.nationalarchives.gov.uk/doc/open-government-licence/
   
import logging import logging
import datetime import datetime
  import os
   
  from pylons import config
   
from ckan.lib.cli import CkanCommand from ckan.lib.cli import CkanCommand
# No other CKAN imports allowed until _load_config is run, # No other CKAN imports allowed until _load_config is run,
# or logging is disabled # or logging is disabled
   
   
class InitDB(CkanCommand): class InitDB(CkanCommand):
"""Initialise the extension's database tables """Initialise the extension's database tables
""" """
summary = __doc__.split('\n')[0] summary = __doc__.split('\n')[0]
usage = __doc__ usage = __doc__
max_args = 0 max_args = 0
min_args = 0 min_args = 0
   
def command(self): def command(self):
self._load_config() self._load_config()
   
import ckan.model as model import ckan.model as model
model.Session.remove() model.Session.remove()
model.Session.configure(bind=model.meta.engine) model.Session.configure(bind=model.meta.engine)
log = logging.getLogger('ckanext.ga-report') log = logging.getLogger('ckanext.ga-report')
   
import ga_model import ga_model
ga_model.init_tables() ga_model.init_tables()
log.info("DB tables are setup") log.info("DB tables are setup")
   
   
class GetAuthToken(CkanCommand): class GetAuthToken(CkanCommand):
""" Get's the Google auth token """ Get's the Google auth token
   
Usage: paster getauthtoken <credentials_file> Usage: paster getauthtoken <credentials_file>
   
Where <credentials_file> is the file name containing the details Where <credentials_file> is the file name containing the details
for the service (obtained from https://code.google.com/apis/console). for the service (obtained from https://code.google.com/apis/console).
By default this is set to credentials.json By default this is set to credentials.json
""" """
summary = __doc__.split('\n')[0] summary = __doc__.split('\n')[0]
usage = __doc__ usage = __doc__
max_args = 0 max_args = 0
min_args = 0 min_args = 0
   
def command(self): def command(self):
""" """
In this case we don't want a valid service, but rather just to In this case we don't want a valid service, but rather just to
force the user through the auth flow. We allow this to complete to force the user through the auth flow. We allow this to complete to
act as a form of verification instead of just getting the token and act as a form of verification instead of just getting the token and
assuming it is correct. assuming it is correct.
""" """
from ga_auth import init_service from ga_auth import init_service
init_service('token.dat', init_service('token.dat',
self.args[0] if self.args self.args[0] if self.args
else 'credentials.json') 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): class LoadAnalytics(CkanCommand):
"""Get data from Google Analytics API and save it """Get data from Google Analytics API and save it
in the ga_model in the ga_model
   
Usage: paster loadanalytics <tokenfile> <time-period> Usage: paster loadanalytics <time-period>
   
Where <tokenfile> is the name of the auth token file from Where <time-period> is:
the getauthtoken step.  
   
And where <time-period> is:  
all - data for all time all - data for all time
latest - (default) just the 'latest' data latest - (default) just the 'latest' data
YYYY-MM - just data for the specific month YYYY-MM - just data for the specific month
""" """
summary = __doc__.split('\n')[0] summary = __doc__.split('\n')[0]
usage = __doc__ usage = __doc__
max_args = 2 max_args = 1
min_args = 1 min_args = 0
   
  def __init__(self, name):
  super(LoadAnalytics, self).__init__(name)
  self.parser.add_option('-d', '--delete-first',
  action='store_true',
  default=False,
  dest='delete_first',
  help='Delete data for the period first')
  self.parser.add_option('-s', '--skip_url_stats',
  action='store_true',
  default=False,
  dest='skip_url_stats',
  help='Skip the download of URL data - just do site-wide stats')
   
def command(self): def command(self):
self._load_config() self._load_config()
   
from download_analytics import DownloadAnalytics from download_analytics import DownloadAnalytics
from ga_auth import (init_service, get_profile_id) from ga_auth import (init_service, get_profile_id)
   
  ga_token_filepath = os.path.expanduser(config.get('googleanalytics.token.filepath', ''))
  if not ga_token_filepath:
  print 'ERROR: In the CKAN config you need to specify the filepath of the ' \
  'Google Analytics token file under key: googleanalytics.token.filepath'
  return
   
try: try:
svc = init_service(self.args[0], None) svc = init_service(ga_token_filepath, None)
except TypeError: except TypeError:
print ('Have you correctly run the getauthtoken task and ' print ('Have you correctly run the getauthtoken task and '
'specified the correct file here') 'specified the correct token file in the CKAN config under '
  '"googleanalytics.token.filepath"?')
return return
   
downloader = DownloadAnalytics(svc, profile_id=get_profile_id(svc)) downloader = DownloadAnalytics(svc, profile_id=get_profile_id(svc),
  delete_first=self.options.delete_first,
  skip_url_stats=self.options.skip_url_stats)
   
time_period = self.args[1] if self.args and len(self.args) > 1 \ time_period = self.args[0] if self.args else 'latest'
else 'latest'  
if time_period == 'all': if time_period == 'all':
downloader.all_() downloader.all_()
elif time_period == 'latest': elif time_period == 'latest':
downloader.latest() downloader.latest()
else: else:
# The month to use # The month to use
for_date = datetime.datetime.strptime(time_period, '%Y-%m') for_date = datetime.datetime.strptime(time_period, '%Y-%m')
downloader.specific_month(for_date) downloader.specific_month(for_date)
   
  import re
  import csv
  import sys
  import json
import logging import logging
import operator import operator
from ckan.lib.base import BaseController, c, render, request, response import collections
  from ckan.lib.base import (BaseController, c, g, render, request, response, abort)
   
import sqlalchemy import sqlalchemy
from sqlalchemy import func, cast, Integer from sqlalchemy import func, cast, Integer
import ckan.model as model import ckan.model as model
from ga_model import GA_Url, GA_Stat from ga_model import GA_Url, GA_Stat, GA_ReferralStat, GA_Publisher
   
log = logging.getLogger('ckanext.ga-report') log = logging.getLogger('ckanext.ga-report')
   
  DOWNLOADS_AVAILABLE_FROM = '2012-12'
   
def _get_month_name(strdate): def _get_month_name(strdate):
import calendar import calendar
from time import strptime from time import strptime
d = strptime(strdate, '%Y-%m') d = strptime(strdate, '%Y-%m')
return '%s %s' % (calendar.month_name[d.tm_mon], d.tm_year) return '%s %s' % (calendar.month_name[d.tm_mon], d.tm_year)
   
  def _get_unix_epoch(strdate):
def _month_details(cls): 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
  more complex query
   
  This may need extending if we add a period_name to the stats
  '''
months = [] months = []
vals = model.Session.query(cls.period_name).distinct().all() day = None
   
  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 \
  else {1:'st',2:'nd',3:'rd'}.get(day % 10, 'th')
  day = "{day}{ordinal}".format(day=day, ordinal=ordinal)
   
for m in vals: for m in vals:
months.append( (m[0], _get_month_name(m[0]))) months.append( (m[0], _get_month_name(m[0])))
return sorted(months, key=operator.itemgetter(0), reverse=True)  
  return months, day
   
   
class GaReport(BaseController): class GaReport(BaseController):
   
def csv(self, month): def csv(self, month):
import csv import csv
   
entries = model.Session.query(GA_Stat).\ q = model.Session.query(GA_Stat).filter(GA_Stat.stat_name!='Downloads')
filter(GA_Stat.period_name==month).\ if month != 'all':
order_by('GA_Stat.stat_name, GA_Stat.key').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()
   
response.headers['Content-Type'] = "text/csv; charset=utf-8" response.headers['Content-Type'] = "text/csv; charset=utf-8"
  response.headers['Content-Disposition'] = str('attachment; filename=stats_%s.csv' % (month,))
   
writer = csv.writer(response) writer = csv.writer(response)
writer.writerow(["Period", "Statistic", "Key", "Value"]) writer.writerow(["Period", "Statistic", "Key", "Value"])
   
for entry in entries: for entry in entries:
writer.writerow([entry.period_name.encode('utf-8'), writer.writerow([entry.period_name.encode('utf-8'),
entry.stat_name.encode('utf-8'), entry.stat_name.encode('utf-8'),
entry.key.encode('utf-8'), entry.key.encode('utf-8'),
entry.value.encode('utf-8')]) entry.value.encode('utf-8')])
   
   
def index(self): def index(self):
   
# Get the month details by fetching distinct values and determining the # Get the month details by fetching distinct values and determining the
# month names from the values. # month names from the values.
c.months = _month_details(GA_Stat) c.months, c.day = _month_details(GA_Stat)
   
# Work out which month to show, based on query params of the first item # Work out which month to show, based on query params of the first item
c.month = request.params.get('month', c.months[0][0] if c.months else '') c.month_desc = 'all months'
c.month_desc = ''.join([m[1] for m in c.months if m[0]==c.month]) c.month = request.params.get('month', '')
  if c.month:
entries = model.Session.query(GA_Stat).\ c.month_desc = ''.join([m[1] for m in c.months if m[0]==c.month])
filter(GA_Stat.stat_name=='Totals').\  
filter(GA_Stat.period_name==c.month).\ q = model.Session.query(GA_Stat).\
order_by('ga_stat.key').all() filter(GA_Stat.stat_name=='Totals')
  if c.month:
c.global_totals = [] q = q.filter(GA_Stat.period_name==c.month)
for e in entries: entries = q.order_by('ga_stat.key').all()
val = e.value  
if e.key in ['Average time on site', 'Pages per visit', 'Percent new visits']: def clean_key(key, val):
val = "%.2f" % round(float(e.value), 2) if key in ['Average time on site', 'Pages per visit', 'New visits', 'Bounce rate (home page)']:
if e.key == 'Average time on site': val = "%.2f" % round(float(val), 2)
  if key == 'Average time on site':
mins, secs = divmod(float(val), 60) mins, secs = divmod(float(val), 60)
hours, mins = divmod(mins, 60) hours, mins = divmod(mins, 60)
val = '%02d:%02d:%02d (%s seconds) ' % (hours, mins, secs, val) val = '%02d:%02d:%02d (%s seconds) ' % (hours, mins, secs, val)
e.key = '%s *' % e.key if key in ['New visits','Bounce rate (home page)']:
c.global_totals.append((e.key, val)) val = "%s%%" % val
  if key in ['Total page views', 'Total visits']:
  val = int(val)
   
  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)
  sparkline = sparkline_data[e.key]
  c.global_totals.append((key, val, sparkline))
  else:
  d = collections.defaultdict(list)
  for e in entries:
  d[e.key].append(float(e.value))
  for k, v in d.iteritems():
  if k in ['Total page views', 'Total visits']:
  v = sum(v)
  else:
  v = float(sum(v))/float(len(v))
  sparkline = sparkline_data[k]
  key, val = clean_key(k,v)
   
  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 = { keys = {
'Browser versions': 'browsers', 'Browser versions': 'browser_versions',
'Operating Systems versions': 'os', 'Browsers': 'browsers',
  'Operating Systems versions': 'os_versions',
  'Operating Systems': 'os',
'Social sources': 'social_networks', 'Social sources': 'social_networks',
'Languages': 'languages', 'Languages': 'languages',
'Country': 'country' 'Country': 'country'
} }
   
  def shorten_name(name, length=60):
  return (name[:length] + '..') if len(name) > 60 else name
   
  def fill_out_url(url):
  import urlparse
  return urlparse.urljoin(g.site_url, url)
   
  c.social_referrer_totals, c.social_referrers = [], []
  q = model.Session.query(GA_ReferralStat)
  q = q.filter(GA_ReferralStat.period_name==c.month) if c.month else q
  q = q.order_by('ga_referrer.count::int desc')
  for entry in q.all():
  c.social_referrers.append((shorten_name(entry.url), fill_out_url(entry.url),
  entry.source,entry.count))
   
  q = model.Session.query(GA_ReferralStat.url,
  func.sum(GA_ReferralStat.count).label('count'))
  q = q.filter(GA_ReferralStat.period_name==c.month) if c.month else q
  q = q.order_by('count desc').group_by(GA_ReferralStat.url)
  for entry in q.all():
  c.social_referrer_totals.append((shorten_name(entry[0]), fill_out_url(entry[0]),'',
  entry[1]))
   
for k, v in keys.iteritems(): for k, v in keys.iteritems():
entries = model.Session.query(GA_Stat).\ q = model.Session.query(GA_Stat).\
filter(GA_Stat.stat_name==k).\ filter(GA_Stat.stat_name==k).\
filter(GA_Stat.period_name==c.month).\ order_by(GA_Stat.period_name)
order_by('ga_stat.value::int desc').all() # Run the query on all months to gather graph data
setattr(c, v, [(s.key, s.value) for s in entries ]) series = {}
  x_axis = set()
  for stat in q:
  x_val = _get_unix_epoch(stat.period_name)
  series[ stat.key ] = series.get(stat.key,{})
  series[ stat.key ][x_val] = float(stat.value)
  x_axis.add(x_val)
  # Common x-axis for all series. Exclude this month (incomplete data)
  x_axis = sorted(list(x_axis))[:-1]
  # Buffer a rickshaw dataset from the series
  def create_graph(series_name, series_data):
  return {
  'name':series_name,
  'data':[ {'x':x,'y':series_data.get(x,0)} for x in x_axis ]
  }
  rickshaw = [ create_graph(name,data) for name, data in series.items() ]
  rickshaw = sorted(rickshaw,key=lambda x:x['data'][-1]['y'])
  setattr(c, v+'_graph', json.dumps(rickshaw))
   
  # 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)
  entries = []
  for key, val in d.iteritems():
  entries.append((key,val,))
  entries = sorted(entries, key=operator.itemgetter(1), reverse=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,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 ])
   
return render('ga_report/site/index.html') return render('ga_report/site/index.html')
   
   
class GaPublisherReport(BaseController): class GaDatasetReport(BaseController):
""" """
Displays the pageview and visit count for specific publishers based on Displays the pageview and visit count for datasets
the datasets associated with the publisher. with options to filter by publisher and time period.
""" """
  def publisher_csv(self, month):
def index(self): '''
  Returns a CSV of each publisher with the total number of dataset
  views & visits.
  '''
  c.month = month if not month == 'all' else ''
  response.headers['Content-Type'] = "text/csv; charset=utf-8"
  response.headers['Content-Disposition'] = str('attachment; filename=publishers_%s.csv' % (month,))
   
  writer = csv.writer(response)
  writer.writerow(["Publisher Title", "Publisher Name", "Views", "Visits", "Period Name"])
   
  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,
  visit,
  month])
   
  def dataset_csv(self, id='all', month='all'):
  '''
  Returns a CSV with the number of views & visits for each dataset.
   
  :param id: A Publisher ID or None if you want for all
  :param month: The time period, or 'all'
  '''
  c.month = month if not month == 'all' else ''
  if id != 'all':
  c.publisher = model.Group.get(id)
  if not c.publisher:
  abort(404, 'A publisher with that name could not be found')
   
  packages = self._get_packages(c.publisher)
  response.headers['Content-Type'] = "text/csv; charset=utf-8"
  response.headers['Content-Disposition'] = \
  str('attachment; filename=datasets_%s_%s.csv' % (c.publisher_name, month,))
   
  writer = csv.writer(response)
  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):
  '''A list of publishers and the number of views/visits for each'''
   
# Get the month details by fetching distinct values and determining the # Get the month details by fetching distinct values and determining the
# month names from the values. # month names from the values.
c.months = _month_details(GA_Url) c.months, c.day = _month_details(GA_Url)
   
# Work out which month to show, based on query params of the first item # Work out which month to show, based on query params of the first item
c.month = request.params.get('month', c.months[0][0] if c.months else '') c.month = request.params.get('month', '')
c.month_desc = ''.join([m[1] for m in c.months if m[0]==c.month]) c.month_desc = 'all months'
  if c.month:
connection = model.Session.connection() c.month_desc = ''.join([m[1] for m in c.months if m[0]==c.month])
q = """  
select department_id, sum(pageviews::int) views, sum(visitors::int) visits c.top_publishers, graph_data = _get_top_publishers()
from ga_url c.top_publishers_graph = json.dumps( _to_rickshaw(graph_data.values()) )
where department_id <> ''  
and period_name=%s  
group by department_id order by views desc limit 20;  
"""  
# Add this back (before and period_name =%s) if you want to ignore publisher  
# homepage views  
# and not url like '/publisher/%%'  
   
c.top_publishers = []  
res = connection.execute(q, c.month)  
for row in res:  
c.top_publishers.append((model.Group.get(row[0]), row[1], row[2]))  
   
return render('ga_report/publisher/index.html') return render('ga_report/publisher/index.html')
   
  def _get_packages(self, publisher=None, count=-1):
def read(self, id): '''Returns the datasets in order of views'''
c.publisher = model.Group.get(id) 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)\
  .filter(GA_Url.url.like('/dataset/%'))
  if publisher:
  q = q.filter(GA_Url.department_id==publisher.name)
  q = q.filter(GA_Url.period_name==month)
  q = q.order_by('ga_url.pageviews::int desc')
  top_packages = []
  if count == -1:
  entries = q.all()
  else:
  entries = q.limit(count)
   
  for entry,package in entries:
  if package:
  # 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')
   
  return top_packages
   
  def read(self):
  '''
  Lists the most popular datasets across all publishers
  '''
  return self.read_publisher(None)
   
  def read_publisher(self, id):
  '''
  Lists the most popular datasets for a publisher (or across all publishers)
  '''
  count = 20
   
  c.publishers = _get_publishers()
   
  id = request.params.get('publisher', id)
  if id and id != 'all':
  c.publisher = model.Group.get(id)
  if not c.publisher:
  abort(404, 'A publisher with that name could not be found')
  c.publisher_name = c.publisher.name
c.top_packages = [] # package, dataset_views in c.top_packages c.top_packages = [] # package, dataset_views in c.top_packages
   
# Get the month details by fetching distinct values and determining the # Get the month details by fetching distinct values and determining the
# month names from the values. # month names from the values.
c.months = _month_details(GA_Url) c.months, c.day = _month_details(GA_Url)
   
# Work out which month to show, based on query params of the first item # Work out which month to show, based on query params of the first item
c.month = request.params.get('month', c.months[0][0] if c.months else '') c.month = request.params.get('month', '')
c.month_desc = ''.join([m[1] for m in c.months if m[0]==c.month]) if not c.month:
  c.month_desc = 'all months'
entry = model.Session.query(GA_Url).\ else:
filter(GA_Url.url=='/publisher/%s' % c.publisher.name).\ c.month_desc = ''.join([m[1] for m in c.months if m[0]==c.month])
filter(GA_Url.period_name==c.month).first()  
  month = c.month or 'All'
  c.publisher_page_views = 0
  q = model.Session.query(GA_Url).\
  filter(GA_Url.url=='/publisher/%s' % c.publisher_name)
  entry = q.filter(GA_Url.period_name==c.month).first()
c.publisher_page_views = entry.pageviews if entry else 0 c.publisher_page_views = entry.pageviews if entry else 0
   
entries = model.Session.query(GA_Url).\ c.top_packages = self._get_packages(c.publisher, 20)
filter(GA_Url.department_id==c.publisher.name).\  
filter(GA_Url.period_name==c.month).\ # Graph query
order_by('ga_url.pageviews::int desc')[:20] top_package_names = [ x[0].name for x in c.top_packages ]
for entry in entries: graph_query = model.Session.query(GA_Url,model.Package)\
if entry.url.startswith('/dataset/'): .filter(model.Package.name==GA_Url.package_id)\
p = model.Package.get(entry.url[len('/dataset/'):]) .filter(GA_Url.url.like('/dataset/%'))\
c.top_packages.append((p,entry.pageviews,entry.visitors)) .filter(GA_Url.package_id.in_(top_package_names))
  graph_data = {}
  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,{
  'name':package.title,
  'data':[]
  })
  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()) )
   
return render('ga_report/publisher/read.html') return render('ga_report/publisher/read.html')
   
  def _to_rickshaw(data):
  num_points = []
  for package in data:
  package['data'] = sorted( package['data'], key=lambda x:x['x'] )
  num_points.append( len(package['data']) )
  if len(set(num_points))>1:
  example = num_points[ num_points.index(max(num_points)) ]
  for package in data:
  while len(package['data'])<example:
  package['data'].insert(0, package['data'][0])
  return data
   
  def _get_top_publishers(limit=20):
  '''
  Returns a list of the top 20 publishers by dataset visits.
  (The number to show can be varied with 'limit')
  '''
  month = c.month or 'All'
  connection = model.Session.connection()
  q = """
  select department_id, sum(pageviews::int) views, sum(visits::int) visits
  from ga_url
  where department_id <> ''
  and package_id <> ''
  and url like '/dataset/%%'
  and period_name=%s
  group by department_id order by views desc
  """
  if limit:
  q = q + " limit %s;" % (limit)
   
  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
   
   
  def _get_publishers():
  '''
  Returns a list of all publishers. Each item is a tuple:
  (name, title)
  '''
  publishers = []
  for pub in model.Session.query(model.Group).\
  filter(model.Group.type=='publisher').\
  filter(model.Group.state=='active').\
  order_by(model.Group.name):
  publishers.append((pub.name, pub.title))
  return publishers
   
  def _percent(num, total):
  p = 100 * float(num)/float(total)
  return "%.2f%%" % round(p, 2)
   
import os import os
import logging import logging
import datetime import datetime
  import collections
from pylons import config from pylons import config
  from ga_model import _normalize_url
import ga_model import ga_model
   
#from ga_client import GA #from ga_client import GA
   
log = logging.getLogger('ckanext.ga-report') log = logging.getLogger('ckanext.ga-report')
   
FORMAT_MONTH = '%Y-%m' FORMAT_MONTH = '%Y-%m'
  MIN_VIEWS = 50
  MIN_VISITS = 20
  MIN_DOWNLOADS = 10
   
class DownloadAnalytics(object): class DownloadAnalytics(object):
'''Downloads and stores analytics info''' '''Downloads and stores analytics info'''
   
def __init__(self, service=None, profile_id=None): def __init__(self, service=None, profile_id=None, delete_first=False,
  skip_url_stats=False):
self.period = config['ga-report.period'] self.period = config['ga-report.period']
self.service = service self.service = service
self.profile_id = profile_id self.profile_id = profile_id
  self.delete_first = delete_first
  self.skip_url_stats = skip_url_stats
   
def specific_month(self, date): def specific_month(self, date):
import calendar import calendar
   
first_of_this_month = datetime.datetime(date.year, date.month, 1) first_of_this_month = datetime.datetime(date.year, date.month, 1)
_, last_day_of_month = calendar.monthrange(int(date.year), int(date.month)) _, 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) last_of_this_month = datetime.datetime(date.year, date.month, last_day_of_month)
periods = ((date.strftime(FORMAT_MONTH), periods = ((date.strftime(FORMAT_MONTH),
last_day_of_month, last_day_of_month,
first_of_this_month, last_of_this_month),) first_of_this_month, last_of_this_month),)
self.download_and_store(periods) self.download_and_store(periods)
   
   
def latest(self): def latest(self):
if self.period == 'monthly': if self.period == 'monthly':
# from first of this month to today # from first of this month to today
now = datetime.datetime.now() now = datetime.datetime.now()
first_of_this_month = datetime.datetime(now.year, now.month, 1) first_of_this_month = datetime.datetime(now.year, now.month, 1)
periods = ((now.strftime(FORMAT_MONTH), periods = ((now.strftime(FORMAT_MONTH),
now.day, now.day,
first_of_this_month, now),) first_of_this_month, now),)
else: else:
raise NotImplementedError raise NotImplementedError
self.download_and_store(periods) self.download_and_store(periods)
   
   
def for_date(self, for_date): def for_date(self, for_date):
assert isinstance(since_date, datetime.datetime) assert isinstance(since_date, datetime.datetime)
periods = [] # (period_name, period_complete_day, start_date, end_date) periods = [] # (period_name, period_complete_day, start_date, end_date)
if self.period == 'monthly': if self.period == 'monthly':
first_of_the_months_until_now = [] first_of_the_months_until_now = []
year = for_date.year year = for_date.year
month = for_date.month month = for_date.month
now = datetime.datetime.now() now = datetime.datetime.now()
first_of_this_month = datetime.datetime(now.year, now.month, 1) first_of_this_month = datetime.datetime(now.year, now.month, 1)
while True: while True:
first_of_the_month = datetime.datetime(year, month, 1) first_of_the_month = datetime.datetime(year, month, 1)
if first_of_the_month == first_of_this_month: if first_of_the_month == first_of_this_month:
periods.append((now.strftime(FORMAT_MONTH), periods.append((now.strftime(FORMAT_MONTH),
now.day, now.day,
first_of_this_month, now)) first_of_this_month, now))
break break
elif first_of_the_month < first_of_this_month: elif first_of_the_month < first_of_this_month:
in_the_next_month = first_of_the_month + datetime.timedelta(40) in_the_next_month = first_of_the_month + datetime.timedelta(40)
last_of_the_month = datetime.datetime(in_the_next_month.year, last_of_the_month = datetime.datetime(in_the_next_month.year,
in_the_next_month.month, 1)\ in_the_next_month.month, 1)\
- datetime.timedelta(1) - datetime.timedelta(1)
periods.append((now.strftime(FORMAT_MONTH), 0, periods.append((now.strftime(FORMAT_MONTH), 0,
first_of_the_month, last_of_the_month)) first_of_the_month, last_of_the_month))
else: else:
# first_of_the_month has got to the future somehow # first_of_the_month has got to the future somehow
break break
month += 1 month += 1
if month > 12: if month > 12:
year += 1 year += 1
month = 1 month = 1
else: else:
raise NotImplementedError raise NotImplementedError
self.download_and_store(periods) self.download_and_store(periods)
   
@staticmethod @staticmethod
def get_full_period_name(period_name, period_complete_day): def get_full_period_name(period_name, period_complete_day):
if period_complete_day: if period_complete_day:
return period_name + ' (up to %ith)' % period_complete_day return period_name + ' (up to %ith)' % period_complete_day
else: else:
return period_name return period_name
   
   
def download_and_store(self, periods): def download_and_store(self, periods):
for period_name, period_complete_day, start_date, end_date in periods: for period_name, period_complete_day, start_date, end_date in periods:
log.info('Downloading Analytics for period "%s" (%s - %s)', log.info('Period "%s" (%s - %s)',
self.get_full_period_name(period_name, period_complete_day), self.get_full_period_name(period_name, period_complete_day),
start_date.strftime('%Y %m %d'), start_date.strftime('%Y-%m-%d'),
end_date.strftime('%Y %m %d')) end_date.strftime('%Y-%m-%d'))
   
data = self.download(start_date, end_date, '~/dataset/[a-z0-9-_]+') if self.delete_first:
log.info('Storing Dataset Analytics for period "%s"', log.info('Deleting existing Analytics for this period "%s"',
self.get_full_period_name(period_name, period_complete_day)) period_name)
self.store(period_name, period_complete_day, data, ) ga_model.delete(period_name)
   
data = self.download(start_date, end_date, '~/publisher/[a-z0-9-_]+') if not self.skip_url_stats:
log.info('Storing Publisher Analytics for period "%s"', # Clean out old url data before storing the new
self.get_full_period_name(period_name, period_complete_day)) ga_model.pre_update_url_stats(period_name)
self.store(period_name, period_complete_day, data,)  
  accountName = config.get('googleanalytics.account')
ga_model.update_publisher_stats(period_name) # about 30 seconds.  
self.sitewide_stats( period_name ) log.info('Downloading analytics for dataset views')
  data = self.download(start_date, end_date, '~/%s/dataset/[a-z0-9-_]+' % accountName)
   
def download(self, start_date, end_date, path='~/dataset/[a-z0-9-_]+'): log.info('Storing dataset views (%i rows)', len(data.get('url')))
  self.store(period_name, period_complete_day, data, )
   
  log.info('Downloading analytics for publisher views')
  data = self.download(start_date, end_date, '~/%s/publisher/[a-z0-9-_]+' % accountName)
   
  log.info('Storing publisher views (%i rows)', len(data.get('url')))
  self.store(period_name, period_complete_day, data,)
   
  # Make sure the All records are correct.
  ga_model.post_update_url_stats()
   
  log.info('Aggregating datasets by 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 )
   
  log.info('Downloading and storing analytics for social networks')
  self.update_social_info(period_name, start_date, end_date)
   
   
  def update_social_info(self, period_name, start_date, end_date):
  start_date = start_date.strftime('%Y-%m-%d')
  end_date = end_date.strftime('%Y-%m-%d')
  query = 'ga:hasSocialSourceReferral=~Yes$'
  metrics = 'ga:entrances'
  sort = '-ga:entrances'
   
  # Supported query params at
  # https://developers.google.com/analytics/devguides/reporting/core/v3/reference
  results = self.service.data().ga().get(
  ids='ga:' + self.profile_id,
  filters=query,
  start_date=start_date,
  metrics=metrics,
  sort=sort,
  dimensions="ga:landingPagePath,ga:socialNetwork",
  max_results=10000,
  end_date=end_date).execute()
  data = collections.defaultdict(list)
  rows = results.get('rows',[])
  for row in rows:
  url = _normalize_url('http:/' + row[0])
  data[url].append( (row[1], int(row[2]),) )
  ga_model.update_social(period_name, data)
   
   
  def download(self, start_date, end_date, path=None):
'''Get data from GA for a given time period''' '''Get data from GA for a given time period'''
start_date = start_date.strftime('%Y-%m-%d') start_date = start_date.strftime('%Y-%m-%d')
end_date = end_date.strftime('%Y-%m-%d') end_date = end_date.strftime('%Y-%m-%d')
query = 'ga:pagePath=%s$' % path query = 'ga:pagePath=%s$' % path
metrics = 'ga:uniquePageviews, ga:visitors' metrics = 'ga:pageviews, ga:visits'
sort = '-ga:uniquePageviews' sort = '-ga:pageviews'
   
# Supported query params at # Supported query params at
# https://developers.google.com/analytics/devguides/reporting/core/v3/reference # https://developers.google.com/analytics/devguides/reporting/core/v3/reference
results = self.service.data().ga().get( results = self.service.data().ga().get(
ids='ga:' + self.profile_id, ids='ga:' + self.profile_id,
filters=query, filters=query,
start_date=start_date, start_date=start_date,
metrics=metrics, metrics=metrics,
sort=sort, sort=sort,
dimensions="ga:pagePath", dimensions="ga:pagePath",
max_results=10000, max_results=10000,
end_date=end_date).execute() end_date=end_date).execute()
   
if os.getenv('DEBUG'):  
import pprint  
pprint.pprint(results)  
print 'Total results: %s' % results.get('totalResults')  
   
packages = [] packages = []
  log.info("There are %d results" % results['totalResults'])
for entry in results.get('rows'): for entry in results.get('rows'):
(loc,pageviews,visits) = entry (loc,pageviews,visits) = entry
packages.append( ('http:/' + loc, pageviews, visits,) ) # Temporary hack url = _normalize_url('http:/' + loc) # strips off domain e.g. www.data.gov.uk or data.gov.uk
   
  if not url.startswith('/dataset/') and not url.startswith('/publisher/'):
  # filter out strays like:
  # /data/user/login?came_from=http://data.gov.uk/dataset/os-code-point-open
  # /403.html?page=/about&from=http://data.gov.uk/publisher/planning-inspectorate
  continue
  packages.append( (url, pageviews, visits,) ) # Temporary hack
return dict(url=packages) return dict(url=packages)
   
def store(self, period_name, period_complete_day, data): def store(self, period_name, period_complete_day, data):
if 'url' in data: if 'url' in data:
ga_model.update_url_stats(period_name, period_complete_day, data['url']) ga_model.update_url_stats(period_name, period_complete_day, data['url'])
   
def sitewide_stats(self, period_name): def sitewide_stats(self, period_name, period_complete_day):
import calendar import calendar
year, month = period_name.split('-') year, month = period_name.split('-')
_, last_day_of_month = calendar.monthrange(int(year), int(month)) _, last_day_of_month = calendar.monthrange(int(year), int(month))
   
start_date = '%s-01' % period_name start_date = '%s-01' % period_name
end_date = '%s-%s' % (period_name, last_day_of_month) end_date = '%s-%s' % (period_name, last_day_of_month)
print 'Sitewide_stats for %s (%s -> %s)' % (period_name, start_date, end_date)  
   
funcs = ['_totals_stats', '_social_stats', '_os_stats', 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: for f in funcs:
print ' + Fetching %s stats' % f.split('_')[1] log.info('Downloading analytics for %s' % f.split('_')[1])
getattr(self, f)(start_date, end_date, period_name) getattr(self, f)(start_date, end_date, period_name, period_complete_day)
   
def _get_results(result_data, f): def _get_results(result_data, f):
data = {} data = {}
for result in result_data: for result in result_data:
key = f(result) key = f(result)
data[key] = data.get(key,0) + result[1] data[key] = data.get(key,0) + result[1]
return data return data
   
def _totals_stats(self, start_date, end_date, period_name): def _totals_stats(self, start_date, end_date, period_name, period_complete_day):
""" Fetches distinct totals, total pageviews etc """ """ Fetches distinct totals, total pageviews etc """
results = self.service.data().ga().get( results = self.service.data().ga().get(
ids='ga:' + self.profile_id, ids='ga:' + self.profile_id,
start_date=start_date, start_date=start_date,
metrics='ga:uniquePageviews', metrics='ga:pageviews',
sort='-ga:uniquePageviews', sort='-ga:pageviews',
max_results=10000, max_results=10000,
end_date=end_date).execute() end_date=end_date).execute()
result_data = results.get('rows') result_data = results.get('rows')
ga_model.update_sitewide_stats(period_name, "Totals", {'Total pageviews': result_data[0][0]}) ga_model.update_sitewide_stats(period_name, "Totals", {'Total page views': result_data[0][0]},
  period_complete_day)
results = self.service.data().ga().get(  
ids='ga:' + self.profile_id, results = self.service.data().ga().get(
start_date=start_date, ids='ga:' + self.profile_id,
metrics='ga:pageviewsPerVisit,ga:bounces,ga:avgTimeOnSite,ga:percentNewVisits', start_date=start_date,
  metrics='ga:pageviewsPerVisit,ga:avgTimeOnSite,ga:percentNewVisits,ga:visits',
max_results=10000, max_results=10000,
end_date=end_date).execute() end_date=end_date).execute()
result_data = results.get('rows') result_data = results.get('rows')
data = { data = {
'Pages per visit': result_data[0][0], 'Pages per visit': result_data[0][0],
'Bounces': result_data[0][1], 'Average time on site': result_data[0][1],
'Average time on site': result_data[0][2], 'New visits': result_data[0][2],
'Percent new visits': result_data[0][3], 'Total visits': result_data[0][3],
} }
ga_model.update_sitewide_stats(period_name, "Totals", data) ga_model.update_sitewide_stats(period_name, "Totals", data, period_complete_day)
   
  # Bounces from / or another configurable page.
def _locale_stats(self, start_date, end_date, period_name): path = '/%s%s' % (config.get('googleanalytics.account'),
  config.get('ga-report.bounce_url', '/'))
  results = self.service.data().ga().get(
  ids='ga:' + self.profile_id,
  filters='ga:pagePath==%s' % (path,),
  start_date=start_date,
  metrics='ga:visitBounceRate',
  dimensions='ga:pagePath',
  max_results=10000,
  end_date=end_date).execute()
  result_data = results.get('rows')
  if not result_data or len(result_data) != 1:
  log.error('Could not pinpoint the bounces for path: %s. Got results: %r',
  path, result_data)
  return
  results = result_data[0]
  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)
   
   
  def _locale_stats(self, start_date, end_date, period_name, period_complete_day):
""" Fetches stats about language and country """ """ Fetches stats about language and country """
results = self.service.data().ga().get( results = self.service.data().ga().get(
ids='ga:' + self.profile_id, ids='ga:' + self.profile_id,
start_date=start_date, start_date=start_date,
metrics='ga:uniquePageviews', metrics='ga:pageviews',
sort='-ga:uniquePageviews', sort='-ga:pageviews',
dimensions="ga:language,ga:country", dimensions="ga:language,ga:country",
max_results=10000, max_results=10000,
end_date=end_date).execute() end_date=end_date).execute()
result_data = results.get('rows') result_data = results.get('rows')
data = {} data = {}
for result in result_data: for result in result_data:
data[result[0]] = data.get(result[0], 0) + int(result[2]) data[result[0]] = data.get(result[0], 0) + int(result[2])
ga_model.update_sitewide_stats(period_name, "Languages", data) self._filter_out_long_tail(data, MIN_VIEWS)
  ga_model.update_sitewide_stats(period_name, "Languages", data, period_complete_day)
   
data = {} data = {}
for result in result_data: for result in result_data:
data[result[1]] = data.get(result[1], 0) + int(result[2]) data[result[1]] = data.get(result[1], 0) + int(result[2])
ga_model.update_sitewide_stats(period_name, "Country", data) self._filter_out_long_tail(data, MIN_VIEWS)
  ga_model.update_sitewide_stats(period_name, "Country", data, period_complete_day)
   
def _social_stats(self, start_date, end_date, period_name):  
  def _download_stats(self, start_date, end_date, period_name, period_complete_day):
  """ Fetches stats about language and country """
  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):
  for result in result_data:
  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:
  log.warning(u"Could not find resource for URL: {url}".format(url=url))
  continue
   
  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()
  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 """ """ Finds out which social sites people are referred from """
results = self.service.data().ga().get( results = self.service.data().ga().get(
ids='ga:' + self.profile_id, ids='ga:' + self.profile_id,
start_date=start_date, start_date=start_date,
metrics='ga:uniquePageviews', metrics='ga:pageviews',
sort='-ga:uniquePageviews', sort='-ga:pageviews',
dimensions="ga:socialNetwork,ga:referralPath", dimensions="ga:socialNetwork,ga:referralPath",
max_results=10000, max_results=10000,
end_date=end_date).execute() end_date=end_date).execute()
result_data = results.get('rows') result_data = results.get('rows')
twitter_links = []  
data = {} data = {}
for result in result_data: for result in result_data:
if not result[0] == '(not set)': if not result[0] == '(not set)':
data[result[0]] = data.get(result[0], 0) + int(result[2]) data[result[0]] = data.get(result[0], 0) + int(result[2])
if result[0] == 'Twitter': self._filter_out_long_tail(data, 3)
twitter_links.append(result[1]) ga_model.update_sitewide_stats(period_name, "Social sources", data, period_complete_day)
ga_model.update_sitewide_stats(period_name, "Social sources", data)  
   
  def _os_stats(self, start_date, end_date, period_name, period_complete_day):
def _os_stats(self, start_date, end_date, period_name):  
""" Operating system stats """ """ Operating system stats """
results = self.service.data().ga().get( results = self.service.data().ga().get(
ids='ga:' + self.profile_id, ids='ga:' + self.profile_id,
start_date=start_date, start_date=start_date,
metrics='ga:uniquePageviews', metrics='ga:pageviews',
sort='-ga:uniquePageviews', sort='-ga:pageviews',
dimensions="ga:operatingSystem,ga:operatingSystemVersion", dimensions="ga:operatingSystem,ga:operatingSystemVersion",
max_results=10000, max_results=10000,
end_date=end_date).execute() end_date=end_date).execute()
result_data = results.get('rows') result_data = results.get('rows')
data = {} data = {}
for result in result_data: for result in result_data:
data[result[0]] = data.get(result[0], 0) + int(result[2]) data[result[0]] = data.get(result[0], 0) + int(result[2])
ga_model.update_sitewide_stats(period_name, "Operating Systems", data) self._filter_out_long_tail(data, MIN_VIEWS)
  ga_model.update_sitewide_stats(period_name, "Operating Systems", data, period_complete_day)
data = {}  
for result in result_data: data = {}
key = "%s (%s)" % (result[0],result[1]) for result in result_data:
data[key] = result[2] if int(result[2]) >= MIN_VIEWS:
ga_model.update_sitewide_stats(period_name, "Operating Systems versions", data) key = "%s %s" % (result[0],result[1])
  data[key] = result[2]
  ga_model.update_sitewide_stats(period_name, "Operating Systems versions", data, period_complete_day)
def _browser_stats(self, start_date, end_date, period_name):  
   
  def _browser_stats(self, start_date, end_date, period_name, period_complete_day):
""" Information about browsers and browser versions """ """ Information about browsers and browser versions """
results = self.service.data().ga().get( results = self.service.data().ga().get(
ids='ga:' + self.profile_id, ids='ga:' + self.profile_id,
start_date=start_date, start_date=start_date,
metrics='ga:uniquePageviews', metrics='ga:pageviews',
sort='-ga:uniquePageviews', sort='-ga:pageviews',
dimensions="ga:browser,ga:browserVersion", dimensions="ga:browser,ga:browserVersion",
max_results=10000, max_results=10000,
end_date=end_date).execute() end_date=end_date).execute()
result_data = results.get('rows') result_data = results.get('rows')
  # e.g. [u'Firefox', u'19.0', u'20']
   
data = {} data = {}
for result in result_data: for result in result_data:
data[result[0]] = data.get(result[0], 0) + int(result[2]) data[result[0]] = data.get(result[0], 0) + int(result[2])
ga_model.update_sitewide_stats(period_name, "Browsers", data) self._filter_out_long_tail(data, MIN_VIEWS)
  ga_model.update_sitewide_stats(period_name, "Browsers", data, period_complete_day)
data = {}  
for result in result_data: data = {}
key = "%s (%s)" % (result[0], result[1]) for result in result_data:
data[key] = result[2] key = "%s %s" % (result[0], self._filter_browser_version(result[0], result[1]))
ga_model.update_sitewide_stats(period_name, "Browser versions", data) data[key] = data.get(key, 0) + int(result[2])
  self._filter_out_long_tail(data, MIN_VIEWS)
  ga_model.update_sitewide_stats(period_name, "Browser versions", data, period_complete_day)
def _mobile_stats(self, start_date, end_date, period_name):  
  @classmethod
  def _filter_browser_version(cls, browser, version_str):
  '''
  Simplifies a browser version string if it is detailed.
  i.e. groups together Firefox 3.5.1 and 3.5.2 to be just 3.
  This is helpful when viewing stats and good to protect privacy.
  '''
  ver = version_str
  parts = ver.split('.')
  if len(parts) > 1:
  if parts[1][0] == '0':
  ver = parts[0]
  else:
  ver = "%s" % (parts[0])
  # Special case complex version nums
  if browser in ['Safari', 'Android Browser']:
  ver = parts[0]
  if len(ver) > 2:
  num_hidden_digits = len(ver) - 2
  ver = ver[0] + ver[1] + 'X' * num_hidden_digits
  return ver
   
  def _mobile_stats(self, start_date, end_date, period_name, period_complete_day):
""" Info about mobile devices """ """ Info about mobile devices """
   
results = self.service.data().ga().get( results = self.service.data().ga().get(
ids='ga:' + self.profile_id, ids='ga:' + self.profile_id,
start_date=start_date, start_date=start_date,
metrics='ga:uniquePageviews', metrics='ga:pageviews',
sort='-ga:uniquePageviews', sort='-ga:pageviews',
dimensions="ga:mobileDeviceBranding, ga:mobileDeviceInfo", dimensions="ga:mobileDeviceBranding, ga:mobileDeviceInfo",
max_results=10000, max_results=10000,
end_date=end_date).execute() end_date=end_date).execute()
   
result_data = results.get('rows') result_data = results.get('rows')
data = {} data = {}
for result in result_data: for result in result_data:
data[result[0]] = data.get(result[0], 0) + int(result[2]) data[result[0]] = data.get(result[0], 0) + int(result[2])
ga_model.update_sitewide_stats(period_name, "Mobile brands", data) self._filter_out_long_tail(data, MIN_VIEWS)
  ga_model.update_sitewide_stats(period_name, "Mobile brands", data, period_complete_day)
   
data = {} data = {}
for result in result_data: for result in result_data:
data[result[1]] = data.get(result[1], 0) + int(result[2]) data[result[1]] = data.get(result[1], 0) + int(result[2])
ga_model.update_sitewide_stats(period_name, "Mobile devices", data) self._filter_out_long_tail(data, MIN_VIEWS)
  ga_model.update_sitewide_stats(period_name, "Mobile devices", data, period_complete_day)
   
  @classmethod
  def _filter_out_long_tail(cls, data, threshold=10):
  '''
  Given data which is a frequency distribution, filter out
  results which are below a threshold count. This is good to protect
  privacy.
  '''
  for key, value in data.items():
  if value < threshold:
  del data[key]
   
import os import os
import httplib2 import httplib2
from apiclient.discovery import build from apiclient.discovery import build
from oauth2client.client import flow_from_clientsecrets from oauth2client.client import flow_from_clientsecrets
from oauth2client.file import Storage from oauth2client.file import Storage
from oauth2client.tools import run from oauth2client.tools import run
   
from pylons import config from pylons import config
   
   
def _prepare_credentials(token_filename, credentials_filename): def _prepare_credentials(token_filename, credentials_filename):
""" """
Either returns the user's oauth credentials or uses the credentials Either returns the user's oauth credentials or uses the credentials
file to generate a token (by forcing the user to login in the browser) file to generate a token (by forcing the user to login in the browser)
""" """
storage = Storage(token_filename) storage = Storage(token_filename)
credentials = storage.get() credentials = storage.get()
   
if credentials is None or credentials.invalid: if credentials is None or credentials.invalid:
flow = flow_from_clientsecrets(credentials_filename, flow = flow_from_clientsecrets(credentials_filename,
scope='https://www.googleapis.com/auth/analytics.readonly', scope='https://www.googleapis.com/auth/analytics.readonly',
message="Can't find the credentials file") message="Can't find the credentials file")
credentials = run(flow, storage) credentials = run(flow, storage)
   
return credentials return credentials
   
   
def init_service(token_file, credentials_file): def init_service(token_file, credentials_file):
""" """
Given a file containing the user's oauth token (and another with Given a file containing the user's oauth token (and another with
credentials in case we need to generate the token) will return a credentials in case we need to generate the token) will return a
service object representing the analytics API. service object representing the analytics API.
""" """
http = httplib2.Http() http = httplib2.Http()
   
credentials = _prepare_credentials(token_file, credentials_file) credentials = _prepare_credentials(token_file, credentials_file)
http = credentials.authorize(http) # authorize the http object http = credentials.authorize(http) # authorize the http object
   
return build('analytics', 'v3', http=http) return build('analytics', 'v3', http=http)
   
   
def get_profile_id(service): def get_profile_id(service):
""" """
Get the profile ID for this user and the service specified by the Get the profile ID for this user and the service specified by the
'googleanalytics.id' configuration option. This function iterates 'googleanalytics.id' configuration option. This function iterates
over all of the accounts available to the user who invoked the over all of the accounts available to the user who invoked the
service to find one where the account name matches (in case the service to find one where the account name matches (in case the
user has several). user has several).
""" """
accounts = service.management().accounts().list().execute() accounts = service.management().accounts().list().execute()
   
if not accounts.get('items'): if not accounts.get('items'):
return None return None
   
accountName = config.get('googleanalytics.account') accountName = config.get('googleanalytics.account')
  if not accountName:
  raise Exception('googleanalytics.account needs to be configured')
webPropertyId = config.get('googleanalytics.id') webPropertyId = config.get('googleanalytics.id')
  if not webPropertyId:
  raise Exception('googleanalytics.id needs to be configured')
for acc in accounts.get('items'): for acc in accounts.get('items'):
if acc.get('name') == accountName: if acc.get('name') == accountName:
accountId = acc.get('id') accountId = acc.get('id')
   
webproperties = service.management().webproperties().list(accountId=accountId).execute() webproperties = service.management().webproperties().list(accountId=accountId).execute()
   
profiles = service.management().profiles().list( profiles = service.management().profiles().list(
accountId=accountId, webPropertyId=webPropertyId).execute() accountId=accountId, webPropertyId=webPropertyId).execute()
   
if profiles.get('items'): if profiles.get('items'):
return profiles.get('items')[0].get('id') return profiles.get('items')[0].get('id')
   
return None return None
   
import re import re
import uuid import uuid
   
from sqlalchemy import Table, Column, MetaData from sqlalchemy import Table, Column, MetaData, ForeignKey
from sqlalchemy import types from sqlalchemy import types
from sqlalchemy.sql import select from sqlalchemy.sql import select
from sqlalchemy.orm import mapper from sqlalchemy.orm import mapper, relation
from sqlalchemy import func from sqlalchemy import func
   
import ckan.model as model import ckan.model as model
from ckan.lib.base import * from ckan.lib.base import *
   
  log = __import__('logging').getLogger(__name__)
   
def make_uuid(): def make_uuid():
return unicode(uuid.uuid4()) return unicode(uuid.uuid4())
   
  metadata = MetaData()
   
class GA_Url(object): class GA_Url(object):
   
def __init__(self, **kwargs): def __init__(self, **kwargs):
for k,v in kwargs.items(): for k,v in kwargs.items():
setattr(self, k, v) setattr(self, k, v)
   
class GA_Stat(object):  
   
def __init__(self, **kwargs):  
for k,v in kwargs.items():  
setattr(self, k, v)  
   
class GA_Publisher(object):  
   
def __init__(self, **kwargs):  
for k,v in kwargs.items():  
setattr(self, k, v)  
   
   
metadata = MetaData()  
url_table = Table('ga_url', metadata, url_table = Table('ga_url', metadata,
Column('id', types.UnicodeText, primary_key=True, Column('id', types.UnicodeText, primary_key=True,
default=make_uuid), default=make_uuid),
Column('period_name', types.UnicodeText), Column('period_name', types.UnicodeText),
Column('period_complete_day', types.Integer), Column('period_complete_day', types.Integer),
Column('pageviews', types.UnicodeText), Column('pageviews', types.UnicodeText),
Column('visitors', types.UnicodeText), Column('visits', types.UnicodeText),
Column('url', types.UnicodeText), Column('url', types.UnicodeText),
Column('department_id', types.UnicodeText), Column('department_id', types.UnicodeText),
  Column('package_id', types.UnicodeText),
) )
mapper(GA_Url, url_table) mapper(GA_Url, url_table)
   
   
  class GA_Stat(object):
   
  def __init__(self, **kwargs):
  for k,v in kwargs.items():
  setattr(self, k, v)
   
stat_table = Table('ga_stat', metadata, stat_table = Table('ga_stat', metadata,
Column('id', types.UnicodeText, primary_key=True, Column('id', types.UnicodeText, primary_key=True,
default=make_uuid), default=make_uuid),
Column('period_name', types.UnicodeText), Column('period_name', types.UnicodeText),
  Column('period_complete_day', types.UnicodeText),
Column('stat_name', types.UnicodeText), Column('stat_name', types.UnicodeText),
Column('key', types.UnicodeText), Column('key', types.UnicodeText),
Column('value', types.UnicodeText), ) Column('value', types.UnicodeText), )
mapper(GA_Stat, stat_table) mapper(GA_Stat, stat_table)
   
   
  class GA_Publisher(object):
   
  def __init__(self, **kwargs):
  for k,v in kwargs.items():
  setattr(self, k, v)
   
pub_table = Table('ga_publisher', metadata, pub_table = Table('ga_publisher', metadata,
Column('id', types.UnicodeText, primary_key=True, Column('id', types.UnicodeText, primary_key=True,
default=make_uuid), default=make_uuid),
Column('period_name', types.UnicodeText), Column('period_name', types.UnicodeText),
Column('publisher_name', types.UnicodeText), Column('publisher_name', types.UnicodeText),
Column('views', types.UnicodeText), Column('views', types.UnicodeText),
Column('visitors', types.UnicodeText), Column('visits', types.UnicodeText),
Column('toplevel', types.Boolean, default=False), Column('toplevel', types.Boolean, default=False),
Column('subpublishercount', types.Integer, default=0), Column('subpublishercount', types.Integer, default=0),
Column('parent', types.UnicodeText), Column('parent', types.UnicodeText),
) )
mapper(GA_Publisher, pub_table) mapper(GA_Publisher, pub_table)
   
   
  class GA_ReferralStat(object):
   
  def __init__(self, **kwargs):
  for k,v in kwargs.items():
  setattr(self, k, v)
   
  referrer_table = Table('ga_referrer', metadata,
  Column('id', types.UnicodeText, primary_key=True,
  default=make_uuid),
  Column('period_name', types.UnicodeText),
  Column('source', types.UnicodeText),
  Column('url', types.UnicodeText),
  Column('count', types.Integer),
  )
  mapper(GA_ReferralStat, referrer_table)
   
   
   
def init_tables(): def init_tables():
metadata.create_all(model.meta.engine) metadata.create_all(model.meta.engine)
   
   
cached_tables = {} cached_tables = {}
   
   
def get_table(name): def get_table(name):
if name not in cached_tables: if name not in cached_tables:
meta = MetaData() meta = MetaData()
meta.reflect(bind=model.meta.engine) meta.reflect(bind=model.meta.engine)
table = meta.tables[name] table = meta.tables[name]
cached_tables[name] = table cached_tables[name] = table
return cached_tables[name] return cached_tables[name]
   
   
def _normalize_url(url): def _normalize_url(url):
'''Strip off the hostname etc. Do this before storing it. '''Strip off the hostname etc. Do this before storing it.
   
>>> normalize_url('http://data.gov.uk/dataset/weekly_fuel_prices') >>> normalize_url('http://data.gov.uk/dataset/weekly_fuel_prices')
'/dataset/weekly_fuel_prices' '/dataset/weekly_fuel_prices'
''' '''
url = re.sub('https?://(www\.)?data.gov.uk', '', url) return '/' + '/'.join(url.split('/')[3:])
return url  
   
  def _get_package_and_publisher(url):
def _get_department_id_of_url(url):  
# e.g. /dataset/fuel_prices # e.g. /dataset/fuel_prices
# e.g. /dataset/fuel_prices/resource/e63380d4 # e.g. /dataset/fuel_prices/resource/e63380d4
dataset_match = re.match('/dataset/([^/]+)(/.*)?', url) dataset_match = re.match('/dataset/([^/]+)(/.*)?', url)
if dataset_match: if dataset_match:
dataset_ref = dataset_match.groups()[0] dataset_ref = dataset_match.groups()[0]
dataset = model.Package.get(dataset_ref) dataset = model.Package.get(dataset_ref)
if dataset: if dataset:
publisher_groups = dataset.get_groups('publisher') publisher_groups = dataset.get_groups('publisher')
if publisher_groups: if publisher_groups:
return publisher_groups[0].name return dataset_ref,publisher_groups[0].name
  return dataset_ref, None
else: else:
publisher_match = re.match('/publisher/([^/]+)(/.*)?', url) publisher_match = re.match('/publisher/([^/]+)(/.*)?', url)
if publisher_match: if publisher_match:
return publisher_match.groups()[0] return None, publisher_match.groups()[0]
  return None, None
   
def update_sitewide_stats(period_name, stat_name, data): def update_sitewide_stats(period_name, stat_name, data, period_complete_day):
for k,v in data.iteritems(): for k,v in data.iteritems():
item = model.Session.query(GA_Stat).\ item = model.Session.query(GA_Stat).\
filter(GA_Stat.period_name==period_name).\ filter(GA_Stat.period_name==period_name).\
filter(GA_Stat.key==k).\ filter(GA_Stat.key==k).\
filter(GA_Stat.stat_name==stat_name).first() filter(GA_Stat.stat_name==stat_name).first()
if item: if item:
item.period_name = period_name item.period_name = period_name
item.key = k item.key = k
item.value = v item.value = v
  item.period_complete_day = period_complete_day
model.Session.add(item) model.Session.add(item)
else: else:
# create the row # create the row
values = {'id': make_uuid(), values = {'id': make_uuid(),
'period_name': period_name, 'period_name': period_name,
  'period_complete_day': period_complete_day,
'key': k, 'key': k,
'value': v, 'value': v,
'stat_name': stat_name 'stat_name': stat_name
} }
model.Session.add(GA_Stat(**values)) model.Session.add(GA_Stat(**values))
model.Session.commit() model.Session.commit()
   
   
  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)
  count = model.Session.query(GA_Url).\
  filter(GA_Url.period_name == 'All').delete()
  log.debug("Deleted %d 'All' records" % count)
   
  model.Session.flush()
  model.Session.commit()
  model.repo.commit_and_remove()
   
  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.
  """
  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]
   
  for key in views.keys():
  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': publisher
  }
  model.Session.add(GA_Url(**values))
  model.Session.commit()
   
   
def update_url_stats(period_name, period_complete_day, url_data): def update_url_stats(period_name, period_complete_day, url_data):
for url, views, visitors in url_data: '''
url = _normalize_url(url) Given a list of urls and number of hits for each during a given period,
department_id = _get_department_id_of_url(url) stores them in GA_Url under the period and recalculates the totals for
  the 'All' period.
# see if the row for this url & month is in the table already '''
  for url, views, visits in url_data:
  package, publisher = _get_package_and_publisher(url)
   
   
item = model.Session.query(GA_Url).\ item = model.Session.query(GA_Url).\
filter(GA_Url.period_name==period_name).\ filter(GA_Url.period_name==period_name).\
filter(GA_Url.url==url).first() filter(GA_Url.url==url).first()
if item: if item:
item.period_name = period_name item.pageviews = item.pageviews + views
item.pageviews = views item.visits = item.visits + visits
item.visitors = visitors if not item.package_id:
item.department_id = department_id item.package_id = package
  if not item.department_id:
  item.department_id = publisher
model.Session.add(item) model.Session.add(item)
else: else:
# create the row  
values = {'id': make_uuid(), values = {'id': make_uuid(),
'period_name': period_name, 'period_name': period_name,
'period_complete_day': period_complete_day, 'period_complete_day': period_complete_day,
'url': url, 'url': url,
'pageviews': views, 'pageviews': views,
'visitors': visitors, 'visits': visits,
'department_id': department_id 'department_id': publisher,
  'package_id': package
} }
model.Session.add(GA_Url(**values)) model.Session.add(GA_Url(**values))
model.Session.commit() model.Session.commit()
   
  if package:
  old_pageviews, old_visits = 0, 0
  old = model.Session.query(GA_Url).\
  filter(GA_Url.period_name=='All').\
  filter(GA_Url.url==url).all()
  old_pageviews = sum([int(o.pageviews) for o in old])
  old_visits = sum([int(o.visits) for o in old])
   
  entries = model.Session.query(GA_Url).\
  filter(GA_Url.period_name!='All').\
  filter(GA_Url.url==url).all()
  values = {'id': make_uuid(),
  'period_name': 'All',
  'period_complete_day': 0,
  'url': url,
  'pageviews': sum([int(e.pageviews) for e in entries]) + int(old_pageviews),
  'visits': sum([int(e.visits or 0) for e in entries]) + int(old_visits),
  'department_id': publisher,
  'package_id': package
  }
   
  model.Session.add(GA_Url(**values))
  model.Session.commit()
   
   
   
   
  def update_social(period_name, data):
  # Clean up first.
  model.Session.query(GA_ReferralStat).\
  filter(GA_ReferralStat.period_name==period_name).delete()
   
  for url,data in data.iteritems():
  for entry in data:
  source = entry[0]
  count = entry[1]
   
  item = model.Session.query(GA_ReferralStat).\
  filter(GA_ReferralStat.period_name==period_name).\
  filter(GA_ReferralStat.source==source).\
  filter(GA_ReferralStat.url==url).first()
  if item:
  item.count = item.count + count
  model.Session.add(item)
  else:
  # create the row
  values = {'id': make_uuid(),
  'period_name': period_name,
  'source': source,
  'url': url,
  'count': count,
  }
  model.Session.add(GA_ReferralStat(**values))
  model.Session.commit()
   
def update_publisher_stats(period_name): def update_publisher_stats(period_name):
""" """
Updates the publisher stats from the data retrieved for /dataset/* Updates the publisher stats from the data retrieved for /dataset/*
and /publisher/*. Will run against each dataset and generates the and /publisher/*. Will run against each dataset and generates the
totals for the entire tree beneath each publisher. totals for the entire tree beneath each publisher.
""" """
toplevel = get_top_level() toplevel = get_top_level()
publishers = model.Session.query(model.Group).\ publishers = model.Session.query(model.Group).\
filter(model.Group.type=='publisher').\ filter(model.Group.type=='publisher').\
filter(model.Group.state=='active').all() filter(model.Group.state=='active').all()
for publisher in publishers: for publisher in publishers:
views, visitors, subpub = update_publisher(period_name, publisher, publisher.name) views, visits, subpub = update_publisher(period_name, publisher, publisher.name)
parent, parents = '', publisher.get_groups('publisher') parent, parents = '', publisher.get_groups('publisher')
if parents: if parents:
parent = parents[0].name parent = parents[0].name
item = model.Session.query(GA_Publisher).\ item = model.Session.query(GA_Publisher).\
filter(GA_Publisher.period_name==period_name).\ filter(GA_Publisher.period_name==period_name).\
filter(GA_Publisher.publisher_name==publisher.name).first() filter(GA_Publisher.publisher_name==publisher.name).first()
if item: if item:
item.views = views item.views = views
item.visitors = visitors item.visits = visits
item.publisher_name = publisher.name item.publisher_name = publisher.name
item.toplevel = publisher in toplevel item.toplevel = publisher in toplevel
item.subpublishercount = subpub item.subpublishercount = subpub
item.parent = parent item.parent = parent
model.Session.add(item) model.Session.add(item)
else: else:
# create the row # create the row
values = {'id': make_uuid(), values = {'id': make_uuid(),
'period_name': period_name, 'period_name': period_name,
'publisher_name': publisher.name, 'publisher_name': publisher.name,
'views': views, 'views': views,
'visitors': visitors, 'visits': visits,
'toplevel': publisher in toplevel, 'toplevel': publisher in toplevel,
'subpublishercount': subpub, 'subpublishercount': subpub,
'parent': parent 'parent': parent
} }
model.Session.add(GA_Publisher(**values)) model.Session.add(GA_Publisher(**values))
model.Session.commit() model.Session.commit()
   
   
def update_publisher(period_name, pub, part=''): def update_publisher(period_name, pub, part=''):
views,visitors,subpub = 0, 0, 0 views,visits,subpub = 0, 0, 0
for publisher in go_down_tree(pub): for publisher in go_down_tree(pub):
subpub = subpub + 1 subpub = subpub + 1
items = model.Session.query(GA_Url).\ items = model.Session.query(GA_Url).\
filter(GA_Url.period_name==period_name).\ filter(GA_Url.period_name==period_name).\
filter(GA_Url.department_id==publisher.name).all() filter(GA_Url.department_id==publisher.name).all()
for item in items: for item in items:
views = views + int(item.pageviews) views = views + int(item.pageviews)
visitors = visitors + int(item.visitors) visits = visits + int(item.visits)
   
return views, visitors, (subpub-1) return views, visits, (subpub-1)
   
   
def get_top_level(): def get_top_level():
'''Returns the top level publishers.''' '''Returns the top level publishers.'''
return model.Session.query(model.Group).\ return model.Session.query(model.Group).\
outerjoin(model.Member, model.Member.table_id == model.Group.id and \ outerjoin(model.Member, model.Member.table_id == model.Group.id and \
model.Member.table_name == 'group' and \ model.Member.table_name == 'group' and \
model.Member.state == 'active').\ model.Member.state == 'active').\
filter(model.Member.id==None).\ filter(model.Member.id==None).\
filter(model.Group.type=='publisher').\ filter(model.Group.type=='publisher').\
order_by(model.Group.name).all() order_by(model.Group.name).all()
   
def get_children(publisher): def get_children(publisher):
'''Finds child publishers for the given publisher (object). (Not recursive)''' '''Finds child publishers for the given publisher (object). (Not recursive)'''
from ckan.model.group import HIERARCHY_CTE from ckan.model.group import HIERARCHY_CTE
return model.Session.query(model.Group).\ return model.Session.query(model.Group).\
from_statement(HIERARCHY_CTE).params(id=publisher.id, type='publisher').\ from_statement(HIERARCHY_CTE).params(id=publisher.id, type='publisher').\
all() all()
   
def go_down_tree(publisher): def go_down_tree(publisher):
'''Provided with a publisher object, it walks down the hierarchy and yields each publisher, '''Provided with a publisher object, it walks down the hierarchy and yields each publisher,
including the one you supply.''' including the one you supply.'''
yield publisher yield publisher
for child in get_children(publisher): for child in get_children(publisher):
for grandchild in go_down_tree(child): for grandchild in go_down_tree(child):
yield grandchild yield grandchild
   
  def delete(period_name):
  '''
  Deletes table data for the specified period, or specify 'all'
  for all periods.
  '''
  for object_type in (GA_Url, GA_Stat, GA_Publisher, GA_ReferralStat):
  q = model.Session.query(object_type)
  if period_name != 'All':
  q = q.filter_by(period_name=period_name)
  q.delete()
  model.repo.commit_and_remove()
   
  def get_score_for_dataset(dataset_name):
  '''
  Returns a "current popularity" score for a dataset,
  based on how many views it has had recently.
  '''
  import datetime
  now = datetime.datetime.now()
  last_month = now - datetime.timedelta(days=30)
  period_names = ['%s-%02d' % (last_month.year, last_month.month),
  '%s-%02d' % (now.year, now.month),
  ]
   
  score = 0
  for period_name in period_names:
  score /= 2 # previous periods are discounted by 50%
  entry = model.Session.query(GA_Url)\
  .filter(GA_Url.period_name==period_name)\
  .filter(GA_Url.package_id==dataset_name).first()
  # score
  if entry:
  views = float(entry.pageviews)
  if entry.period_complete_day:
  views_per_day = views / entry.period_complete_day
  else:
  views_per_day = views / 15 # guess
  score += views_per_day
   
  score = int(score * 100)
  log.debug('Popularity %s: %s', score, dataset_name)
  return score
   
  import logging
  import operator
 
  import ckan.lib.base as base
  import ckan.model as model
  from ckan.logic import get_action
 
  from ckanext.ga_report.ga_model import GA_Url, GA_Publisher
  from ckanext.ga_report.controller import _get_publishers
  _log = logging.getLogger(__name__)
 
  def popular_datasets(count=10):
  import random
 
  publisher = None
  publishers = _get_publishers(30)
  total = len(publishers)
  while not publisher or not datasets:
  rand = random.randrange(0, total)
  publisher = publishers[rand][0]
  if not publisher.state == 'active':
  publisher = None
  continue
  datasets = _datasets_for_publisher(publisher, 10)[:count]
 
  ctx = {
  'datasets': datasets,
  'publisher': publisher
  }
  return base.render_snippet('ga_report/ga_popular_datasets.html', **ctx)
 
  def single_popular_dataset(top=20):
  '''Returns a random dataset from the most popular ones.
 
  :param top: the number of top datasets to select from
  '''
  import random
 
  top_datasets = model.Session.query(GA_Url).\
  filter(GA_Url.url.like('/dataset/%')).\
  order_by('ga_url.pageviews::int desc')
  num_top_datasets = top_datasets.count()
 
  dataset = None
  if num_top_datasets:
  count = 0
  while not dataset:
  rand = random.randrange(0, min(top, num_top_datasets))
  ga_url = top_datasets[rand]
  dataset = model.Package.get(ga_url.url[len('/dataset/'):])
  if dataset and not dataset.state == 'active':
  dataset = None
  # When testing, it is possible that top datasets are not available
  # so only go round this loop a few times before falling back on
  # a random dataset.
  count += 1
  if count > 10:
  break
  if not dataset:
  # fallback
  dataset = model.Session.query(model.Package)\
  .filter_by(state='active').first()
  if not dataset:
  return None
  dataset_dict = get_action('package_show')({'model': model,
  'session': model.Session,
  'validate': False},
  {'id':dataset.id})
  return dataset_dict
 
  def single_popular_dataset_html(top=20):
  dataset_dict = single_popular_dataset(top)
  groups = package.get('groups', [])
  publishers = [ g for g in groups if g.get('type') == 'publisher' ]
  publisher = publishers[0] if publishers else {'name':'', 'title': ''}
  context = {
  'dataset': dataset_dict,
  'publisher': publisher_dict
  }
  return base.render_snippet('ga_report/ga_popular_single.html', **context)
 
 
  def most_popular_datasets(publisher, count=20):
 
  if not publisher:
  _log.error("No valid publisher passed to 'most_popular_datasets'")
  return ""
 
  results = _datasets_for_publisher(publisher, count)
 
  ctx = {
  'dataset_count': len(results),
  'datasets': results,
 
  'publisher': publisher
  }
 
  return base.render_snippet('ga_report/publisher/popular.html', **ctx)
 
  def _datasets_for_publisher(publisher, count):
  datasets = {}
  entries = model.Session.query(GA_Url).\
  filter(GA_Url.department_id==publisher.name).\
  filter(GA_Url.url.like('/dataset/%')).\
  order_by('ga_url.pageviews::int desc').all()
  for entry in entries:
  if len(datasets) < count:
  p = model.Package.get(entry.url[len('/dataset/'):])
  if not p in datasets:
  datasets[p] = {'views':0, 'visits': 0}
  datasets[p]['views'] = datasets[p]['views'] + int(entry.pageviews)
  datasets[p]['visits'] = datasets[p]['visits'] + int(entry.visits)
 
  results = []
  for k, v in datasets.iteritems():
  results.append((k,v['views'],v['visits']))
 
  return sorted(results, key=operator.itemgetter(1), reverse=True)
 
import logging import logging
import ckan.lib.helpers as h import ckan.lib.helpers as h
import ckan.plugins as p import ckan.plugins as p
from ckan.plugins import implements, toolkit from ckan.plugins import implements, toolkit
#import gasnippet  
#import commands from ckanext.ga_report.helpers import (most_popular_datasets,
#import dbutil popular_datasets,
  single_popular_dataset)
   
log = logging.getLogger('ckanext.ga-report') log = logging.getLogger('ckanext.ga-report')
   
class GAReportPlugin(p.SingletonPlugin): class GAReportPlugin(p.SingletonPlugin):
implements(p.IConfigurer, inherit=True) implements(p.IConfigurer, inherit=True)
implements(p.IRoutes, inherit=True) implements(p.IRoutes, inherit=True)
  implements(p.ITemplateHelpers, inherit=True)
   
def update_config(self, config): def update_config(self, config):
toolkit.add_template_directory(config, 'templates') toolkit.add_template_directory(config, 'templates')
toolkit.add_public_directory(config, 'public') toolkit.add_public_directory(config, 'public')
   
  def get_helpers(self):
  """
  A dictionary of extra helpers that will be available to provide
  ga report info to templates.
  """
  return {
  'ga_report_installed': lambda: True,
  'popular_datasets': popular_datasets,
  'most_popular_datasets': most_popular_datasets,
  'single_popular_dataset': single_popular_dataset
  }
   
def after_map(self, map): def after_map(self, map):
  # GaReport
map.connect( map.connect(
'/data/analytics/publisher', '/data/site-usage',
controller='ckanext.ga_report.controller:GaPublisherReport',  
action='index'  
)  
map.connect(  
'/data/analytics/publisher/{id}',  
controller='ckanext.ga_report.controller:GaPublisherReport',  
action='read'  
)  
map.connect(  
'/data/analytics',  
controller='ckanext.ga_report.controller:GaReport', controller='ckanext.ga_report.controller:GaReport',
action='index' action='index'
) )
map.connect( map.connect(
'/data/analytics/data_{month}.csv', '/data/site-usage/data_{month}.csv',
controller='ckanext.ga_report.controller:GaReport', controller='ckanext.ga_report.controller:GaReport',
action='csv' 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(
  '/data/site-usage/publisher',
  controller='ckanext.ga_report.controller:GaDatasetReport',
  action='publishers'
  )
  map.connect(
  '/data/site-usage/publishers_{month}.csv',
  controller='ckanext.ga_report.controller:GaDatasetReport',
  action='publisher_csv'
  )
  map.connect(
  '/data/site-usage/dataset/datasets_{id}_{month}.csv',
  controller='ckanext.ga_report.controller:GaDatasetReport',
  action='dataset_csv'
  )
  map.connect(
  '/data/site-usage/dataset',
  controller='ckanext.ga_report.controller:GaDatasetReport',
  action='read'
  )
  map.connect(
  '/data/site-usage/dataset/{id}',
  controller='ckanext.ga_report.controller:GaDatasetReport',
  action='read_publisher'
) )
return map return map
   
   
  .table-condensed td.sparkline-cell {
  padding: 1px 0 0 0;
  width: 108px;
  text-align: center;
  }
  .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 {
  position: absolute;
  right: 0;
  top: 0;
  margin-left: 15px;
  padding: 0 5px;
  background: transparent;
  max-width: 150px;
  overflow: hidden;
  background: rgba(0,0,0,0.05);
  border-radius:5px;
  }
  .rickshaw_y_axis {
  position: absolute;
  top: 0;
  bottom: 0;
  width: 40px;
  }
  .rickshaw_legend .label {
  background: transparent !important;
  color: #000000 !important;
  font-weight: normal !important;
  }
 
  (function(){function a(a){var b=a.source,d=a.target,e=c(b,d),f=[b];while(b!==e)b=b.parent,f.push(b);var g=f.length;while(d!==e)f.splice(g,0,d),d=d.parent;return f}function b(a){var b=[],c=a.parent;while(c!=null)b.push(a),a=c,c=c.parent;return b.push(a),b}function c(a,c){if(a===c)return a;var d=b(a),e=b(c),f=d.pop(),g=e.pop(),h=null;while(f===g)h=f,f=d.pop(),g=e.pop();return h}function g(a){a.fixed|=2}function h(a){a!==f&&(a.fixed&=1)}function i(){j(),f.fixed&=1,e=f=null}function j(){f.px+=d3.event.dx,f.py+=d3.event.dy,e.resume()}function k(a,b,c){var d=0,e=0;a.charge=0;if(!a.leaf){var f=a.nodes,g=f.length,h=-1,i;while(++h<g){i=f[h];if(i==null)continue;k(i,b,c),a.charge+=i.charge,d+=i.charge*i.cx,e+=i.charge*i.cy}}if(a.point){a.leaf||(a.point.x+=Math.random()-.5,a.point.y+=Math.random()-.5);var j=b*c[a.point.index];a.charge+=a.pointCharge=j,d+=j*a.point.x,e+=j*a.point.y}a.cx=d/a.charge,a.cy=e/a.charge}function l(a){return 20}function m(a){return 1}function o(a){return a.x}function p(a){return a.y}function q(a,b,c){a.y0=b,a.y=c}function t(a){var b=1,c=0,d=a[0][1],e,f=a.length;for(;b<f;++b)(e=a[b][1])>d&&(c=b,d=e);return c}function u(a){return a.reduce(v,0)}function v(a,b){return a+b[1]}function w(a,b){return x(a,Math.ceil(Math.log(b.length)/Math.LN2+1))}function x(a,b){var c=-1,d=+a[0],e=(a[1]-d)/b,f=[];while(++c<=b)f[c]=e*c+d;return f}function y(a){return[d3.min(a),d3.max(a)]}function z(a,b){return a.sort=d3.rebind(a,b.sort),a.children=d3.rebind(a,b.children),a.links=D,a.value=d3.rebind(a,b.value),a.nodes=function(b){return E=!0,(a.nodes=a)(b)},a}function A(a){return a.children}function B(a){return a.value}function C(a,b){return b.value-a.value}function D(a){return d3.merge(a.map(function(a){return(a.children||[]).map(function(b){return{source:a,target:b}})}))}function F(a,b){return a.value-b.value}function G(a,b){var c=a._pack_next;a._pack_next=b,b._pack_prev=a,b._pack_next=c,c._pack_prev=b}function H(a,b){a._pack_next=b,b._pack_prev=a}function I(a,b){var c=b.x-a.x,d=b.y-a.y,e=a.r+b.r;return e*e-c*c-d*d>.001}function J(a){function l(a){b=Math.min(a.x-a.r,b),c=Math.max(a.x+a.r,c),d=Math.min(a.y-a.r,d),e=Math.max(a.y+a.r,e)}var b=Infinity,c=-Infinity,d=Infinity,e=-Infinity,f=a.length,g,h,i,j,k;a.forEach(K),g=a[0],g.x=-g.r,g.y=0,l(g);if(f>1){h=a[1],h.x=h.r,h.y=0,l(h);if(f>2){i=a[2],O(g,h,i),l(i),G(g,i),g._pack_prev=i,G(i,h),h=g._pack_next;for(var m=3;m<f;m++){O(g,h,i=a[m]);var n=0,o=1,p=1;for(j=h._pack_next;j!==h;j=j._pack_next,o++)if(I(j,i)){n=1;break}if(n==1)for(k=g._pack_prev;k!==j._pack_prev;k=k._pack_prev,p++)if(I(k,i)){p<o&&(n=-1,j=k);break}n==0?(G(g,i),h=i,l(i)):n>0?(H(g,j),h=j,m--):(H(j,h),g=j,m--)}}}var q=(b+c)/2,r=(d+e)/2,s=0;for(var m=0;m<f;m++){var t=a[m];t.x-=q,t.y-=r,s=Math.max(s,t.r+Math.sqrt(t.x*t.x+t.y*t.y))}return a.forEach(L),s}function K(a){a._pack_next=a._pack_prev=a}function L(a){delete a._pack_next,delete a._pack_prev}function M(a){var b=a.children;b&&b.length?(b.forEach(M),a.r=J(b)):a.r=Math.sqrt(a.value)}function N(a,b,c,d){var e=a.children;a.x=b+=d*a.x,a.y=c+=d*a.y,a.r*=d;if(e){var f=-1,g=e.length;while(++f<g)N(e[f],b,c,d)}}function O(a,b,c){var d=a.r+c.r,e=b.x-a.x,f=b.y-a.y;if(d&&(e||f)){var g=b.r+c.r,h=Math.sqrt(e*e+f*f),i=Math.max(-1,Math.min(1,(d*d+h*h-g*g)/(2*d*h))),j=Math.acos(i),k=i*(d/=h),l=Math.sin(j)*d;c.x=a.x+k*e+l*f,c.y=a.y+k*f-l*e}else c.x=a.x+d,c.y=a.y}function P(a){return 1+d3.max(a,function(a){return a.y})}function Q(a){return a.reduce(function(a,b){return a+b.x},0)/a.length}function R(a){var b=a.children;return b&&b.length?R(b[0]):a}function S(a){var b=a.children,c;return b&&(c=b.length)?S(b[c-1]):a}function T(a,b){return a.parent==b.parent?1:2}function U(a){var b=a.children;return b&&b.length?b[0]:a._tree.thread}function V(a){var b=a.children,c;return b&&(c=b.length)?b[c-1]:a._tree.thread}function W(a,b){var c=a.children;if(c&&(e=c.length)){var d,e,f=-1;while(++f<e)b(d=W(c[f],b),a)>0&&(a=d)}return a}function X(a,b){return a.x-b.x}function Y(a,b){return b.x-a.x}function Z(a,b){return a.depth-b.depth}function $(a,b){function c(a,d){var e=a.children;if(e&&(i=e.length)){var f,g=null,h=-1,i;while(++h<i)f=e[h],c(f,g),g=f}b(a,d)}c(a,null)}function _(a){var b=0,c=0,d=a.children,e=d.length,f;while(--e>=0)f=d[e]._tree,f.prelim+=b,f.mod+=b,b+=f.shift+(c+=f.change)}function ba(a,b,c){a=a._tree,b=b._tree;var d=c/(b.number-a.number);a.change+=d,b.change-=d,b.shift+=c,b.prelim+=c,b.mod+=c}function bb(a,b,c){return a._tree.ancestor.parent==b.parent?a._tree.ancestor:c}function bc(a){return{x:a.x,y:a.y,dx:a.dx,dy:a.dy}}function bd(a,b){var c=a.x+b[3],d=a.y+b[0],e=a.dx-b[1]-b[3],f=a.dy-b[0]-b[2];return e<0&&(c+=e/2,e=0),f<0&&(d+=f/2,f=0),{x:c,y:d,dx:e,dy:f}}d3.layout={},d3.layout.bundle=function(){return function(b){var c=[],d=-1,e=b.length;while(++d<e)c.push(a(b[d]));return c}},d3.layout.chord=function(){function j(){var a={},j=[],l=d3.range(e),m=[],n,o,p,q,r;b=[],c=[],n=0,q=-1;while(++q<e){o=0,r=-1;while(++r<e)o+=d[q][r];j.push(o),m.push(d3.range(e)),n+=o}g&&l.sort(function(a,b){return g(j[a],j[b])}),h&&m.forEach(function(a,b){a.sort(function(a,c){return h(d[b][a],d[b][c])})}),n=(2*Math.PI-f*e)/n,o=0,q=-1;while(++q<e){p=o,r=-1;while(++r<e){var s=l[q],t=m[s][r],u=d[s][t],v=o,w=o+=u*n;a[s+"-"+t]={index:s,subindex:t,startAngle:v,endAngle:w,value:u}}c.push({index:s,startAngle:p,endAngle:o,value:(o-p)/n}),o+=f}q=-1;while(++q<e){r=q-1;while(++r<e){var x=a[q+"-"+r],y=a[r+"-"+q];(x.value||y.value)&&b.push(x.value<y.value?{source:y,target:x}:{source:x,target:y})}}i&&k()}function k(){b.sort(function(a,b){return i((a.source.value+a.target.value)/2,(b.source.value+b.target.value)/2)})}var a={},b,c,d,e,f=0,g,h,i;return a.matrix=function(f){return arguments.length?(e=(d=f)&&d.length,b=c=null,a):d},a.padding=function(d){return arguments.length?(f=d,b=c=null,a):f},a.sortGroups=function(d){return arguments.length?(g=d,b=c=null,a):g},a.sortSubgroups=function(c){return arguments.length?(h=c,b=null,a):h},a.sortChords=function(c){return arguments.length?(i=c,b&&k(),a):i},a.chords=function(){return b||j(),b},a.groups=function(){return c||j(),c},a},d3.layout.force=function(){function A(a){return function(b,c,d,e,f){if(b.point!==a){var g=b.cx-a.x,h=b.cy-a.y,i=1/Math.sqrt(g*g+h*h);if((e-c)*i<t){var j=b.charge*i*i;return a.px-=g*j,a.py-=h*j,!0}if(b.point&&isFinite(i)){var j=b.pointCharge*i*i;a.px-=g*j,a.py-=h*j}}return!b.charge}}function B(){var a=v.length,d=w.length,e,f,g,h,i,j,l,m,p;for(f=0;f<d;++f){g=w[f],h=g.source,i=g.target,m=i.x-h.x,p=i.y-h.y;if(j=m*m+p*p)j=n*y[f]*((j=Math.sqrt(j))-x[f])/j,m*=j,p*=j,i.x-=m*(l=h.weight/(i.weight+h.weight)),i.y-=p*l,h.x+=m*(l=1-l),h.y+=p*l}if(l=n*s){m=c[0]/2,p=c[1]/2,f=-1;if(l)while(++f<a)g=v[f],g.x+=(m-g.x)*l,g.y+=(p-g.y)*l}if(r){k(e=d3.geom.quadtree(v),n,z),f=-1;while(++f<a)(g=v[f]).fixed||e.visit(A(g))}f=-1;while(++f<a)g=v[f],g.fixed?(g.x=g.px,g.y=g.py):(g.x-=(g.px-(g.px=g.x))*o,g.y-=(g.py-(g.py=g.y))*o);return b.tick({type:"tick",alpha:n}),(n*=.99)<.005}function C(b){g(f=b),e=a}var a={},b=d3.dispatch("tick"),c=[1,1],d,n,o=.9,p=l,q=m,r=-30,s=.1,t=.8,u,v=[],w=[],x,y,z;return a.on=function(c,d){return b.on(c,d),a},a.nodes=function(b){return arguments.length?(v=b,a):v},a.links=function(b){return arguments.length?(w=b,a):w},a.size=function(b){return arguments.length?(c=b,a):c},a.linkDistance=function(b){return arguments.length?(p=d3.functor(b),a):p},a.distance=a.linkDistance,a.linkStrength=function(b){return arguments.length?(q=d3.functor(b),a):q},a.friction=function(b){return arguments.length?(o=b,a):o},a.charge=function(b){return arguments.length?(r=typeof b=="function"?b:+b,a):r},a.gravity=function(b){return arguments.length?(s=b,a):s},a.theta=function(b){return arguments.length?(t=b,a):t},a.start=function(){function k(a,c){var d=l(b),e=-1,f=d.length,g;while(++e<f)if(!isNaN(g=d[e][a]))return g;return Math.random()*c}function l(){if(!i){i=[];for(d=0;d<e;++d)i[d]=[];for(d=0;d<f;++d){var a=w[d];i[a.source.index].push(a.target),i[a.target.index].push(a.source)}}return i[b]}var b,d,e=v.length,f=w.length,g=c[0],h=c[1],i,j;for(b=0;b<e;++b)(j=v[b]).index=b,j.weight=0;x=[],y=[];for(b=0;b<f;++b)j=w[b],typeof j.source=="number"&&(j.source=v[j.source]),typeof j.target=="number"&&(j.target=v[j.target]),x[b]=p.call(this,j,b),y[b]=q.call(this,j,b),++j.source.weight,++j.target.weight;for(b=0;b<e;++b)j=v[b],isNaN(j.x)&&(j.x=k("x",g)),isNaN(j.y)&&(j.y=k("y",h)),isNaN(j.px)&&(j.px=j.x),isNaN(j.py)&&(j.py=j.y);z=[];if(typeof r=="function")for(b=0;b<e;++b)z[b]=+r.call(this,v[b],b);else for(b=0;b<e;++b)z[b]=r;return a.resume()},a.resume=function(){return n=.1,d3.timer(B),a},a.stop=function(){return n=0,a},a.drag=function(){d||(d=d3.behavior.drag().on("dragstart",C).on("drag",j).on("dragend",i)),this.on("mouseover.force",g).on("mouseout.force",h).call(d)},a};var e,f;d3.layout.partition=function(){function c(a,b,d,e){var f=a.children;a.x=b,a.y=a.depth*e,a.dx=d,a.dy=e;if(f&&(h=f.length)){var g=-1,h,i,j;d=a.value?d/a.value:0;while(++g<h)c(i=f[g],b,j=i.value*d,e),b+=j}}function d(a){var b=a.children,c=0;if(b&&(f=b.length)){var e=-1,f;while(++e<f)c=Math.max(c,d(b[e]))}return 1+c}function e(e,f){var g=a.call(this,e,f);return c(g[0],0,b[0],b[1]/d(g[0])),g}var a=d3.layout.hierarchy(),b=[1,1];return e.size=function(a){return arguments.length?(b=a,e):b},z(e,a)},d3.layout.pie=function(){function f(g,h){var i=g.map(function(b,c){return+a.call(f,b,c)}),j=+(typeof c=="function"?c.apply(this,arguments):c),k=((typeof e=="function"?e.apply(this,arguments):e)-c)/d3.sum(i),l=d3.range(g.length);b!=null&&l.sort(b===n?function(a,b){return i[b]-i[a]}:function(a,c){return b(g[a],g[c])});var m=l.map(function(a){return{data:g[a],value:d=i[a],startAngle:j,endAngle:j+=d*k}});return g.map(function(a,b){return m[l[b]]})}var a=Number,b=n,c=0,e=2*Math.PI;return f.value=function(b){return arguments.length?(a=b,f):a},f.sort=function(a){return arguments.length?(b=a,f):b},f.startAngle=function(a){return arguments.length?(c=a,f):c},f.endAngle=function(a){return arguments.length?(e=a,f):e},f};var n={};d3.layout.stack=function(){function g(h,i){var j=h.map(function(b,c){return a.call(g,b,c)}),k=j.map(function(a,b){return a.map(function(a,b){return[e.call(g,a,b),f.call(g,a,b)]})}),l=b.call(g,k,i);j=d3.permute(j,l),k=d3.permute(k,l);var m=c.call(g,k,i),n=j.length,o=j[0].length,p,q,r;for(q=0;q<o;++q){d.call(g,j[0][q],r=m[q],k[0][q][1]);for(p=1;p<n;++p)d.call(g,j[p][q],r+=k[p-1][q][1],k[p][q][1])}return h}var a=Object,b=r["default"],c=s.zero,d=q,e=o,f=p;return g.values=function(b){return arguments.length?(a=b,g):a},g.order=function(a){return arguments.length?(b=typeof a=="function"?a:r[a],g):b},g.offset=function(a){return arguments.length?(c=typeof a=="function"?a:s[a],g):c},g.x=function(a){return arguments.length?(e=a,g):e},g.y=function(a){return arguments.length?(f=a,g):f},g.out=function(a){return arguments.length?(d=a,g):d},g};var r={"inside-out":function(a){var b=a.length,c,d,e=a.map(t),f=a.map(u),g=d3.range(b).sort(function(a,b){return e[a]-e[b]}),h=0,i=0,j=[],k=[];for(c=0;c<b;++c)d=g[c],h<i?(h+=f[d],j.push(d)):(i+=f[d],k.push(d));return k.reverse().concat(j)},reverse:function(a){return d3.range(a.length).reverse()},"default":function(a){return d3.range(a.length)}},s={silhouette:function(a){var b=a.length,c=a[0].length,d=[],e=0,f,g,h,i=[];for(g=0;g<c;++g){for(f=0,h=0;f<b;f++)h+=a[f][g][1];h>e&&(e=h),d.push(h)}for(g=0;g<c;++g)i[g]=(e-d[g])/2;return i},wiggle:function(a){var b=a.length,c=a[0],d=c.length,e=0,f,g,h,i,j,k,l,m,n,o=[];o[0]=m=n=0;for(g=1;g<d;++g){for(f=0,i=0;f<b;++f)i+=a[f][g][1];for(f=0,j=0,l=c[g][0]-c[g-1][0];f<b;++f){for(h=0,k=(a[f][g][1]-a[f][g-1][1])/(2*l);h<f;++h)k+=(a[h][g][1]-a[h][g-1][1])/l;j+=k*a[f][g][1]}o[g]=m-=i?j/i*l:0,m<n&&(n=m)}for(g=0;g<d;++g)o[g]-=n;return o},expand:function(a){var b=a.length,c=a[0].length,d=1/b,e,f,g,h=[];for(f=0;f<c;++f){for(e=0,g=0;e<b;e++)g+=a[e][f][1];if(g)for(e=0;e<b;e++)a[e][f][1]/=g;else for(e=0;e<b;e++)a[e][f][1]=d}for(f=0;f<c;++f)h[f]=0;return h},zero:function(a){var b=-1,c=a[0].length,d=[];while(++b<c)d[b]=0;return d}};d3.layout.histogram=function(){function e(e,f){var g=[],h=e.map(b,this),i=c.call(this,h,f),j=d.call(this,i,h,f),k,f=-1,l=h.length,m=j.length-1,n=a?1:1/l,o;while(++f<m)k=g[f]=[],k.dx=j[f+1]-(k.x=j[f]),k.y=0;f=-1;while(++f<l)o=h[f],o>=i[0]&&o<=i[1]&&(k=g[d3.bisect(j,o,1,m)-1],k.y+=n,k.push(e[f]));return g}var a=!0,b=Number,c=y,d=w;return e.value=function(a){return arguments.length?(b=a,e):b},e.range=function(a){return arguments.length?(c=d3.functor(a),e):c},e.bins=function(a){return arguments.length?(d=typeof a=="number"?function(b){return x(b,a)}:d3.functor(a),e):d},e.frequency=function(b){return arguments.length?(a=!!b,e):a},e},d3.layout.hierarchy=function(){function e(f,h,i){var j=b.call(g,f,h),k=E?f:{data:f};k.depth=h,i.push(k);if(j&&(m=j.length)){var l=-1,m,n=k.children=[],o=0,p=h+1;while(++l<m)d=e(j[l],p,i),d.parent=k,n.push(d),o+=d.value;a&&n.sort(a),c&&(k.value=o)}else c&&(k.value=+c.call(g,f,h)||0);return k}function f(a,b){var d=a.children,e=0;if(d&&(i=d.length)){var h=-1,i,j=b+1;while(++h<i)e+=f(d[h],j)}else c&&(e=+c.call(g,E?a:a.data,b)||0);return c&&(a.value=e),e}function g(a){var b=[];return e(a,0,b),b}var a=C,b=A,c=B;return g.sort=function(b){return arguments.length?(a=b,g):a},g.children=function(a){return arguments.length?(b=a,g):b},g.value=function(a){return arguments.length?(c=a,g):c},g.revalue=function(a){return f(a,0),a},g};var E=!1;d3.layout.pack=function(){function c(c,d){var e=a.call(this,c,d),f=e[0];f.x=0,f.y=0,M(f);var g=b[0],h=b[1],i=1/Math.max(2*f.r/g,2*f.r/h);return N(f,g/2,h/2,i),e}var a=d3.layout.hierarchy().sort(F),b=[1,1];return c.size=function(a){return arguments.length?(b=a,c):b},z(c,a)},d3.layout.cluster=function(){function d(d,e){var f=a.call(this,d,e),g=f[0],h,i=0,j,k;$(g,function(a){var c=a.children;c&&c.length?(a.x=Q(c),a.y=P(c)):(a.x=h?i+=b(a,h):0,a.y=0,h=a)});var l=R(g),m=S(g),n=l.x-b(l,m)/2,o=m.x+b(m,l)/2;return $(g,function(a){a.x=(a.x-n)/(o-n)*c[0],a.y=(1-a.y/g.y)*c[1]}),f}var a=d3.layout.hierarchy().sort(null).value(null),b=T,c=[1,1];return d.separation=function(a){return arguments.length?(b=a,d):b},d.size=function(a){return arguments.length?(c=a,d):c},z(d,a)},d3.layout.tree=function(){function d(d,e){function h(a,c){var d=a.children,e=a._tree;if(d&&(f=d.length)){var f,g=d[0],i,k=g,l,m=-1;while(++m<f)l=d[m],h(l,i),k=j(l,i,k),i=l;_(a);var n=.5*(g._tree.prelim+l._tree.prelim);c?(e.prelim=c._tree.prelim+b(a,c),e.mod=e.prelim-n):e.prelim=n}else c&&(e.prelim=c._tree.prelim+b(a,c))}function i(a,b){a.x=a._tree.prelim+b;var c=a.children;if(c&&(e=c.length)){var d=-1,e;b+=a._tree.mod;while(++d<e)i(c[d],b)}}function j(a,c,d){if(c){var e=a,f=a,g=c,h=a.parent.children[0],i=e._tree.mod,j=f._tree.mod,k=g._tree.mod,l=h._tree.mod,m;while(g=V(g),e=U(e),g&&e)h=U(h),f=V(f),f._tree.ancestor=a,m=g._tree.prelim+k-e._tree.prelim-i+b(g,e),m>0&&(ba(bb(g,a,d),a,m),i+=m,j+=m),k+=g._tree.mod,i+=e._tree.mod,l+=h._tree.mod,j+=f._tree.mod;g&&!V(f)&&(f._tree.thread=g,f._tree.mod+=k-j),e&&!U(h)&&(h._tree.thread=e,h._tree.mod+=i-l,d=a)}return d}var f=a.call(this,d,e),g=f[0];$(g,function(a,b){a._tree={ancestor:a,prelim:0,mod:0,change:0,shift:0,number:b?b._tree.number+1:0}}),h(g),i(g,-g._tree.prelim);var k=W(g,Y),l=W(g,X),m=W(g,Z),n=k.x-b(k,l)/2,o=l.x+b(l,k)/2,p=m.depth||1;return $(g,function(a){a.x=(a.x-n)/(o-n)*c[0],a.y=a.depth/p*c[1],delete a._tree}),f}var a=d3.layout.hierarchy().sort(null).value(null),b=T,c=[1,1];return d.separation=function(a){return arguments.length?(b=a,d):b},d.size=function(a){return arguments.length?(c=a,d):c},z(d,a)},d3.layout.treemap=function(){function i(a,b){var c=-1,d=a.length,e,f;while(++c<d)f=(e=a[c]).value*(b<0?0:b),e.area=isNaN(f)||f<=0?0:f}function j(a){var b=a.children;if(b&&b.length){var c=e(a),d=[],f=b.slice(),g,h=Infinity,k,n=Math.min(c.dx,c.dy),o;i(f,c.dx*c.dy/a.value),d.area=0;while((o=f.length)>0)d.push(g=f[o-1]),d.area+=g.area,(k=l(d,n))<=h?(f.pop(),h=k):(d.area-=d.pop().area,m(d,n,c,!1),n=Math.min(c.dx,c.dy),d.length=d.area=0,h=Infinity);d.length&&(m(d,n,c,!0),d.length=d.area=0),b.forEach(j)}}function k(a){var b=a.children;if(b&&b.length){var c=e(a),d=b.slice(),f,g=[];i(d,c.dx*c.dy/a.value),g.area=0;while(f=d.pop())g.push(f),g.area+=f.area,f.z!=null&&(m(g,f.z?c.dx:c.dy,c,!d.length),g.length=g.area=0);b.forEach(k)}}function l(a,b){var c=a.area,d,e=0,f=Infinity,g=-1,i=a.length;while(++g<i){if(!(d=a[g].area))continue;d<f&&(f=d),d>e&&(e=d)}return c*=c,b*=b,c?Math.max(b*e*h/c,c/(b*f*h)):Infinity}function m(a,c,d,e){var f=-1,g=a.length,h=d.x,i=d.y,j=c?b(a.area/c):0,k;if(c==d.dx){if(e||j>d.dy)j=j?d.dy:0;while(++f<g)k=a[f],k.x=h,k.y=i,k.dy=j,h+=k.dx=j?b(k.area/j):0;k.z=!0,k.dx+=d.x+d.dx-h,d.y+=j,d.dy-=j}else{if(e||j>d.dx)j=j?d.dx:0;while(++f<g)k=a[f],k.x=h,k.y=i,k.dx=j,i+=k.dy=j?b(k.area/j):0;k.z=!1,k.dy+=d.y+d.dy-i,d.x+=j,d.dx-=j}}function n(b){var d=g||a(b),e=d[0];return e.x=0,e.y=0,e.dx=c[0],e.dy=c[1],g&&a.revalue(e),i([e],e.dx*e.dy/e.value),(g?k:j)(e),f&&(g=d),d}var a=d3.layout.hierarchy(),b=Math.round,c=[1,1],d=null,e=bc,f=!1,g,h=.5*(1+Math.sqrt(5));return n.size=function(a){return arguments.length?(c=a,n):c},n.padding=function(a){function b(b){var c=a.call(n,b,b.depth);return c==null?bc(b):bd(b,typeof c=="number"?[c,c,c,c]:c)}function c(b){return bd(b,a)}if(!arguments.length)return d;var f;return e=(d=a)==null?bc:(f=typeof a)==="function"?b:f==="number"?(a=[a,a,a,a],c):c,n},n.round=function(a){return arguments.length?(b=a?Math.round:Number,n):b!=Number},n.sticky=function(a){return arguments.length?(f=a,g=null,n):f},n.ratio=function(a){return arguments.length?(h=a,n):h},z(n,a)}})();
  (function() {
  function d3_class(ctor, properties) {
  try {
  for (var key in properties) {
  Object.defineProperty(ctor.prototype, key, {
  value: properties[key],
  enumerable: false
  });
  }
  } catch (e) {
  ctor.prototype = properties;
  }
  }
  function d3_arrayCopy(pseudoarray) {
  var i = -1, n = pseudoarray.length, array = [];
  while (++i < n) array.push(pseudoarray[i]);
  return array;
  }
  function d3_arraySlice(pseudoarray) {
  return Array.prototype.slice.call(pseudoarray);
  }
  function d3_Map() {}
  function d3_identity(d) {
  return d;
  }
  function d3_this() {
  return this;
  }
  function d3_true() {
  return true;
  }
  function d3_functor(v) {
  return typeof v === "function" ? v : function() {
  return v;
  };
  }
  function d3_rebind(target, source, method) {
  return function() {
  var value = method.apply(source, arguments);
  return arguments.length ? target : value;
  };
  }
  function d3_number(x) {
  return x != null && !isNaN(x);
  }
  function d3_zipLength(d) {
  return d.length;
  }
  function d3_splitter(d) {
  return d == null;
  }
  function d3_collapse(s) {
  return s.trim().replace(/\s+/g, " ");
  }
  function d3_range_integerScale(x) {
  var k = 1;
  while (x * k % 1) k *= 10;
  return k;
  }
  function d3_dispatch() {}
  function d3_dispatch_event(dispatch) {
  function event() {
  var z = listeners, i = -1, n = z.length, l;
  while (++i < n) if (l = z[i].on) l.apply(this, arguments);
  return dispatch;
  }
  var listeners = [], listenerByName = new d3_Map;
  event.on = function(name, listener) {
  var l = listenerByName.get(name), i;
  if (arguments.length < 2) return l && l.on;
  if (l) {
  l.on = null;
  listeners = listeners.slice(0, i = listeners.indexOf(l)).concat(listeners.slice(i + 1));
  listenerByName.remove(name);
  }
  if (listener) listeners.push(listenerByName.set(name, {
  on: listener
  }));
  return dispatch;
  };
  return event;
  }
  function d3_format_precision(x, p) {
  return p - (x ? 1 + Math.floor(Math.log(x + Math.pow(10, 1 + Math.floor(Math.log(x) / Math.LN10) - p)) / Math.LN10) : 1);
  }
  function d3_format_typeDefault(x) {
  return x + "";
  }
  function d3_format_group(value) {
  var i = value.lastIndexOf("."), f = i >= 0 ? value.substring(i) : (i = value.length, ""), t = [];
  while (i > 0) t.push(value.substring(i -= 3, i + 3));
  return t.reverse().join(",") + f;
  }
  function d3_formatPrefix(d, i) {
  var k = Math.pow(10, Math.abs(8 - i) * 3);
  return {
  scale: i > 8 ? function(d) {
  return d / k;
  } : function(d) {
  return d * k;
  },
  symbol: d
  };
  }
  function d3_ease_clamp(f) {
  return function(t) {
  return t <= 0 ? 0 : t >= 1 ? 1 : f(t);
  };
  }
  function d3_ease_reverse(f) {
  return function(t) {
  return 1 - f(1 - t);
  };
  }
  function d3_ease_reflect(f) {
  return function(t) {
  return .5 * (t < .5 ? f(2 * t) : 2 - f(2 - 2 * t));
  };
  }
  function d3_ease_identity(t) {
  return t;
  }
  function d3_ease_poly(e) {
  return function(t) {
  return Math.pow(t, e);
  };
  }
  function d3_ease_sin(t) {
  return 1 - Math.cos(t * Math.PI / 2);
  }
  function d3_ease_exp(t) {
  return Math.pow(2, 10 * (t - 1));
  }
  function d3_ease_circle(t) {
  return 1 - Math.sqrt(1 - t * t);
  }
  function d3_ease_elastic(a, p) {
  var s;
  if (arguments.length < 2) p = .45;
  if (arguments.length < 1) {
  a = 1;
  s = p / 4;
  } else s = p / (2 * Math.PI) * Math.asin(1 / a);
  return function(t) {
  return 1 + a * Math.pow(2, 10 * -t) * Math.sin((t - s) * 2 * Math.PI / p);
  };
  }
  function d3_ease_back(s) {
  if (!s) s = 1.70158;
  return function(t) {
  return t * t * ((s + 1) * t - s);
  };
  }
  function d3_ease_bounce(t) {
  return t < 1 / 2.75 ? 7.5625 * t * t : t < 2 / 2.75 ? 7.5625 * (t -= 1.5 / 2.75) * t + .75 : t < 2.5 / 2.75 ? 7.5625 * (t -= 2.25 / 2.75) * t + .9375 : 7.5625 * (t -= 2.625 / 2.75) * t + .984375;
  }
  function d3_eventCancel() {
  d3.event.stopPropagation();
  d3.event.preventDefault();
  }
  function d3_eventSource() {
  var e = d3.event, s;
  while (s = e.sourceEvent) e = s;
  return e;
  }
  function d3_eventDispatch(target) {
  var dispatch = new d3_dispatch, i = 0, n = arguments.length;
  while (++i < n) dispatch[arguments[i]] = d3_dispatch_event(dispatch);
  dispatch.of = function(thiz, argumentz) {
  return function(e1) {
  try {
  var e0 = e1.sourceEvent = d3.event;
  e1.target = target;
  d3.event = e1;
  dispatch[e1.type].apply(thiz, argumentz);
  } finally {
  d3.event = e0;
  }
  };
  };
  return dispatch;
  }
  function d3_transform(m) {
  var r0 = [ m.a, m.b ], r1 = [ m.c, m.d ], kx = d3_transformNormalize(r0), kz = d3_transformDot(r0, r1), ky = d3_transformNormalize(d3_transformCombine(r1, r0, -kz)) || 0;
  if (r0[0] * r1[1] < r1[0] * r0[1]) {
  r0[0] *= -1;
  r0[1] *= -1;
  kx *= -1;
  kz *= -1;
  }
  this.rotate = (kx ? Math.atan2(r0[1], r0[0]) : Math.atan2(-r1[0], r1[1])) * d3_transformDegrees;
  this.translate = [ m.e, m.f ];
  this.scale = [ kx, ky ];
  this.skew = ky ? Math.atan2(kz, ky) * d3_transformDegrees : 0;
  }
  function d3_transformDot(a, b) {
  return a[0] * b[0] + a[1] * b[1];
  }
  function d3_transformNormalize(a) {
  var k = Math.sqrt(d3_transformDot(a, a));
  if (k) {
  a[0] /= k;
  a[1] /= k;
  }
  return k;
  }
  function d3_transformCombine(a, b, k) {
  a[0] += k * b[0];
  a[1] += k * b[1];
  return a;
  }
  function d3_interpolateByName(name) {
  return name == "transform" ? d3.interpolateTransform : d3.interpolate;
  }
  function d3_uninterpolateNumber(a, b) {
  b = b - (a = +a) ? 1 / (b - a) : 0;
  return function(x) {
  return (x - a) * b;
  };
  }
  function d3_uninterpolateClamp(a, b) {
  b = b - (a = +a) ? 1 / (b - a) : 0;
  return function(x) {
  return Math.max(0, Math.min(1, (x - a) * b));
  };
  }
  function d3_Color() {}
  function d3_rgb(r, g, b) {
  return new d3_Rgb(r, g, b);
  }
  function d3_Rgb(r, g, b) {
  this.r = r;
  this.g = g;
  this.b = b;
  }
  function d3_rgb_hex(v) {
  return v < 16 ? "0" + Math.max(0, v).toString(16) : Math.min(255, v).toString(16);
  }
  function d3_rgb_parse(format, rgb, hsl) {
  var r = 0, g = 0, b = 0, m1, m2, name;
  m1 = /([a-z]+)\((.*)\)/i.exec(format);
  if (m1) {
  m2 = m1[2].split(",");
  switch (m1[1]) {
  case "hsl":
  {
  return hsl(parseFloat(m2[0]), parseFloat(m2[1]) / 100, parseFloat(m2[2]) / 100);
  }
  case "rgb":
  {
  return rgb(d3_rgb_parseNumber(m2[0]), d3_rgb_parseNumber(m2[1]), d3_rgb_parseNumber(m2[2]));
  }
  }
  }
  if (name = d3_rgb_names.get(format)) return rgb(name.r, name.g, name.b);
  if (format != null && format.charAt(0) === "#") {
  if (format.length === 4) {
  r = format.charAt(1);
  r += r;
  g = format.charAt(2);
  g += g;
  b = format.charAt(3);
  b += b;
  } else if (format.length === 7) {
  r = format.substring(1, 3);
  g = format.substring(3, 5);
  b = format.substring(5, 7);
  }
  r = parseInt(r, 16);
  g = parseInt(g, 16);
  b = parseInt(b, 16);
  }
  return rgb(r, g, b);
  }
  function d3_rgb_hsl(r, g, b) {
  var min = Math.min(r /= 255, g /= 255, b /= 255), max = Math.max(r, g, b), d = max - min, h, s, l = (max + min) / 2;
  if (d) {
  s = l < .5 ? d / (max + min) : d / (2 - max - min);
  if (r == max) h = (g - b) / d + (g < b ? 6 : 0); else if (g == max) h = (b - r) / d + 2; else h = (r - g) / d + 4;
  h *= 60;
  } else {
  s = h = 0;
  }
  return d3_hsl(h, s, l);
  }
  function d3_rgb_lab(r, g, b) {
  r = d3_rgb_xyz(r);
  g = d3_rgb_xyz(g);
  b = d3_rgb_xyz(b);
  var x = d3_xyz_lab((.4124564 * r + .3575761 * g + .1804375 * b) / d3_lab_X), y = d3_xyz_lab((.2126729 * r + .7151522 * g + .072175 * b) / d3_lab_Y), z = d3_xyz_lab((.0193339 * r + .119192 * g + .9503041 * b) / d3_lab_Z);
  return d3_lab(116 * y - 16, 500 * (x - y), 200 * (y - z));
  }
  function d3_rgb_xyz(r) {
  return (r /= 255) <= .04045 ? r / 12.92 : Math.pow((r + .055) / 1.055, 2.4);
  }
  function d3_rgb_parseNumber(c) {
  var f = parseFloat(c);
  return c.charAt(c.length - 1) === "%" ? Math.round(f * 2.55) : f;
  }
  function d3_hsl(h, s, l) {
  return new d3_Hsl(h, s, l);
  }
  function d3_Hsl(h, s, l) {
  this.h = h;
  this.s = s;
  this.l = l;
  }
  function d3_hsl_rgb(h, s, l) {
  function v(h) {
  if (h > 360) h -= 360; else if (h < 0) h += 360;
  if (h < 60) return m1 + (m2 - m1) * h / 60;
  if (h < 180) return m2;
  if (h < 240) return m1 + (m2 - m1) * (240 - h) / 60;
  return m1;
  }
  function vv(h) {
  return Math.round(v(h) * 255);
  }
  var m1, m2;
  h = h % 360;
  if (h < 0) h += 360;
  s = s < 0 ? 0 : s > 1 ? 1 : s;
  l = l < 0 ? 0 : l > 1 ? 1 : l;
  m2 = l <= .5 ? l * (1 + s) : l + s - l * s;
  m1 = 2 * l - m2;
  return d3_rgb(vv(h + 120), vv(h), vv(h - 120));
  }
  function d3_hcl(h, c, l) {
  return new d3_Hcl(h, c, l);
  }
  function d3_Hcl(h, c, l) {
  this.h = h;
  this.c = c;
  this.l = l;
  }
  function d3_hcl_lab(h, c, l) {
  return d3_lab(l, Math.cos(h *= Math.PI / 180) * c, Math.sin(h) * c);
  }
  function d3_lab(l, a, b) {
  return new d3_Lab(l, a, b);
  }
  function d3_Lab(l, a, b) {
  this.l = l;
  this.a = a;
  this.b = b;
  }
  function d3_lab_rgb(l, a, b) {
  var y = (l + 16) / 116, x = y + a / 500, z = y - b / 200;
  x = d3_lab_xyz(x) * d3_lab_X;
  y = d3_lab_xyz(y) * d3_lab_Y;
  z = d3_lab_xyz(z) * d3_lab_Z;
  return d3_rgb(d3_xyz_rgb(3.2404542 * x - 1.5371385 * y - .4985314 * z), d3_xyz_rgb(-.969266 * x + 1.8760108 * y + .041556 * z), d3_xyz_rgb(.0556434 * x - .2040259 * y + 1.0572252 * z));
  }
  function d3_lab_hcl(l, a, b) {
  return d3_hcl(Math.atan2(b, a) / Math.PI * 180, Math.sqrt(a * a + b * b), l);
  }
  function d3_lab_xyz(x) {
  return x > .206893034 ? x * x * x : (x - 4 / 29) / 7.787037;
  }
  function d3_xyz_lab(x) {
  return x > .008856 ? Math.pow(x, 1 / 3) : 7.787037 * x + 4 / 29;
  }
  function d3_xyz_rgb(r) {
  return Math.round(255 * (r <= .00304 ? 12.92 * r : 1.055 * Math.pow(r, 1 / 2.4) - .055));
  }
  function d3_selection(groups) {
  d3_arraySubclass(groups, d3_selectionPrototype);
  return groups;
  }
  function d3_selection_selector(selector) {
  return function() {
  return d3_select(selector, this);
  };
  }
  function d3_selection_selectorAll(selector) {
  return function() {
  return d3_selectAll(selector, this);
  };
  }
  function d3_selection_attr(name, value) {
  function attrNull() {
  this.removeAttribute(name);
  }
  function attrNullNS() {
  this.removeAttributeNS(name.space, name.local);
  }
  function attrConstant() {
  this.setAttribute(name, value);
  }
  function attrConstantNS() {
  this.setAttributeNS(name.space, name.local, value);
  }
  function attrFunction() {
  var x = value.apply(this, arguments);
  if (x == null) this.removeAttribute(name); else this.setAttribute(name, x);
  }
  function attrFunctionNS() {
  var x = value.apply(this, arguments);
  if (x == null) this.removeAttributeNS(name.space, name.local); else this.setAttributeNS(name.space, name.local, x);
  }
  name = d3.ns.qualify(name);
  return value == null ? name.local ? attrNullNS : attrNull : typeof value === "function" ? name.local ? attrFunctionNS : attrFunction : name.local ? attrConstantNS : attrConstant;
  }
  function d3_selection_classedRe(name) {
  return new RegExp("(?:^|\\s+)" + d3.requote(name) + "(?:\\s+|$)", "g");
  }
  function d3_selection_classed(name, value) {
  function classedConstant() {
  var i = -1;
  while (++i < n) name[i](this, value);
  }
  function classedFunction() {
  var i = -1, x = value.apply(this, arguments);
  while (++i < n) name[i](this, x);
  }
  name = name.trim().split(/\s+/).map(d3_selection_classedName);
  var n = name.length;
  return typeof value === "function" ? classedFunction : classedConstant;
  }
  function d3_selection_classedName(name) {
  var re = d3_selection_classedRe(name);
  return function(node, value) {
  if (c = node.classList) return value ? c.add(name) : c.remove(name);
  var c = node.className, cb = c.baseVal != null, cv = cb ? c.baseVal : c;
  if (value) {
  re.lastIndex = 0;
  if (!re.test(cv)) {
  cv = d3_collapse(cv + " " + name);
  if (cb) c.baseVal = cv; else node.className = cv;
  }
  } else if (cv) {
  cv = d3_collapse(cv.replace(re, " "));
  if (cb) c.baseVal = cv; else node.className = cv;
  }
  };
  }
  function d3_selection_style(name, value, priority) {
  function styleNull() {
  this.style.removeProperty(name);
  }
  function styleConstant() {
  this.style.setProperty(name, value, priority);
  }
  function styleFunction() {
  var x = value.apply(this, arguments);
  if (x == null) this.style.removeProperty(name); else this.style.setProperty(name, x, priority);
  }
  return value == null ? styleNull : typeof value === "function" ? styleFunction : styleConstant;
  }
  function d3_selection_property(name, value) {
  function propertyNull() {
  delete this[name];
  }
  function propertyConstant() {
  this[name] = value;
  }
  function propertyFunction() {
  var x = value.apply(this, arguments);
  if (x == null) delete this[name]; else this[name] = x;
  }
  return value == null ? propertyNull : typeof value === "function" ? propertyFunction : propertyConstant;
  }
  function d3_selection_dataNode(data) {
  return {
  __data__: data
  };
  }
  function d3_selection_filter(selector) {
  return function() {
  return d3_selectMatches(this, selector);
  };
  }
  function d3_selection_sortComparator(comparator) {
  if (!arguments.length) comparator = d3.ascending;
  return function(a, b) {
  return comparator(a && a.__data__, b && b.__data__);
  };
  }
  function d3_selection_on(type, listener, capture) {
  function onRemove() {
  var wrapper = this[name];
  if (wrapper) {
  this.removeEventListener(type, wrapper, wrapper.$);
  delete this[name];
  }
  }
  function onAdd() {
  function wrapper(e) {
  var o = d3.event;
  d3.event = e;
  args[0] = node.__data__;
  try {
  listener.apply(node, args);
  } finally {
  d3.event = o;
  }
  }
  var node = this, args = arguments;
  onRemove.call(this);
  this.addEventListener(type, this[name] = wrapper, wrapper.$ = capture);
  wrapper._ = listener;
  }
  var name = "__on" + type, i = type.indexOf(".");
  if (i > 0) type = type.substring(0, i);
  return listener ? onAdd : onRemove;
  }
  function d3_selection_each(groups, callback) {
  for (var j = 0, m = groups.length; j < m; j++) {
  for (var group = groups[j], i = 0, n = group.length, node; i < n; i++) {
  if (node = group[i]) callback(node, i, j);
  }
  }
  return groups;
  }
  function d3_selection_enter(selection) {
  d3_arraySubclass(selection, d3_selection_enterPrototype);
  return selection;
  }
  function d3_transition(groups, id, time) {
  d3_arraySubclass(groups, d3_transitionPrototype);
  var tweens = new d3_Map, event = d3.dispatch("start", "end"), ease = d3_transitionEase;
  groups.id = id;
  groups.time = time;
  groups.tween = function(name, tween) {
  if (arguments.length < 2) return tweens.get(name);
  if (tween == null) tweens.remove(name); else tweens.set(name, tween);
  return groups;
  };
  groups.ease = function(value) {
  if (!arguments.length) return ease;
  ease = typeof value === "function" ? value : d3.ease.apply(d3, arguments);
  return groups;
  };
  groups.each = function(type, listener) {
  if (arguments.length < 2) return d3_transition_each.call(groups, type);
  event.on(type, listener);
  return groups;
  };
  d3.timer(function(elapsed) {
  return d3_selection_each(groups, function(node, i, j) {
  function start(elapsed) {
  if (lock.active > id) return stop();
  lock.active = id;
  tweens.forEach(function(key, value) {
  if (value = value.call(node, d, i)) {
  tweened.push(value);
  }
  });
  event.start.call(node, d, i);
  if (!tick(elapsed)) d3.timer(tick, 0, time);
  return 1;
  }
  function tick(elapsed) {
  if (lock.active !== id) return stop();
  var t = (elapsed - delay) / duration, e = ease(t), n = tweened.length;
  while (n > 0) {
  tweened[--n].call(node, e);
  }
  if (t >= 1) {
  stop();
  d3_transitionId = id;
  event.end.call(node, d, i);
  d3_transitionId = 0;
  return 1;
  }
  }
  function stop() {
  if (!--lock.count) delete node.__transition__;
  return 1;
  }
  var tweened = [], delay = node.delay, duration = node.duration, lock = (node = node.node).__transition__ || (node.__transition__ = {
  active: 0,
  count: 0
  }), d = node.__data__;
  ++lock.count;
  delay <= elapsed ? start(elapsed) : d3.timer(start, delay, time);
  });
  }, 0, time);
  return groups;
  }
  function d3_transition_each(callback) {
  var id = d3_transitionId, ease = d3_transitionEase, delay = d3_transitionDelay, duration = d3_transitionDuration;
  d3_transitionId = this.id;
  d3_transitionEase = this.ease();
  d3_selection_each(this, function(node, i, j) {
  d3_transitionDelay = node.delay;
  d3_transitionDuration = node.duration;
  callback.call(node = node.node, node.__data__, i, j);
  });
  d3_transitionId = id;
  d3_transitionEase = ease;
  d3_transitionDelay = delay;
  d3_transitionDuration = duration;
  return this;
  }
  function d3_tweenNull(d, i, a) {
  return a != "" && d3_tweenRemove;
  }
  function d3_tweenByName(b, name) {
  return d3.tween(b, d3_interpolateByName(name));
  }
  function d3_timer_step() {
  var elapsed, now = Date.now(), t1 = d3_timer_queue;
  while (t1) {
  elapsed = now - t1.then;
  if (elapsed >= t1.delay) t1.flush = t1.callback(elapsed);
  t1 = t1.next;
  }
  var delay = d3_timer_flush() - now;
  if (delay > 24) {
  if (isFinite(delay)) {
  clearTimeout(d3_timer_timeout);
  d3_timer_timeout = setTimeout(d3_timer_step, delay);
  }
  d3_timer_interval = 0;
  } else {
  d3_timer_interval = 1;
  d3_timer_frame(d3_timer_step);
  }
  }
  function d3_timer_flush() {
  var t0 = null, t1 = d3_timer_queue, then = Infinity;
  while (t1) {
  if (t1.flush) {
  delete d3_timer_byId[t1.callback.id];
  t1 = t0 ? t0.next = t1.next : d3_timer_queue = t1.next;
  } else {
  then = Math.min(then, t1.then + t1.delay);
  t1 = (t0 = t1).next;
  }
  }
  return then;
  }
  function d3_mousePoint(container, e) {
  var svg = container.ownerSVGElement || container;
  if (svg.createSVGPoint) {
  var point = svg.createSVGPoint();
  if (d3_mouse_bug44083 < 0 && (window.scrollX || window.scrollY)) {
  svg = d3.select(document.body).append("svg").style("position", "absolute").style("top", 0).style("left", 0);
  var ctm = svg[0][0].getScreenCTM();
  d3_mouse_bug44083 = !(ctm.f || ctm.e);
  svg.remove();
  }
  if (d3_mouse_bug44083) {
  point.x = e.pageX;
  point.y = e.pageY;
  } else {
  point.x = e.clientX;
  point.y = e.clientY;
  }
  point = point.matrixTransform(container.getScreenCTM().inverse());
  return [ point.x, point.y ];
  }
  var rect = container.getBoundingClientRect();
  return [ e.clientX - rect.left - container.clientLeft, e.clientY - rect.top - container.clientTop ];
  }
  function d3_noop() {}
  function d3_scaleExtent(domain) {
  var start = domain[0], stop = domain[domain.length - 1];
  return start < stop ? [ start, stop ] : [ stop, start ];
  }
  function d3_scaleRange(scale) {
  return scale.rangeExtent ? scale.rangeExtent() : d3_scaleExtent(scale.range());
  }
  function d3_scale_nice(domain, nice) {
  var i0 = 0, i1 = domain.length - 1, x0 = domain[i0], x1 = domain[i1], dx;
  if (x1 < x0) {
  dx = i0, i0 = i1, i1 = dx;
  dx = x0, x0 = x1, x1 = dx;
  }
  if (nice = nice(x1 - x0)) {
  domain[i0] = nice.floor(x0);
  domain[i1] = nice.ceil(x1);
  }
  return domain;
  }
  function d3_scale_niceDefault() {
  return Math;
  }
  function d3_scale_linear(domain, range, interpolate, clamp) {
  function rescale() {
  var linear = Math.min(domain.length, range.length) > 2 ? d3_scale_polylinear : d3_scale_bilinear, uninterpolate = clamp ? d3_uninterpolateClamp : d3_uninterpolateNumber;
  output = linear(domain, range, uninterpolate, interpolate);
  input = linear(range, domain, uninterpolate, d3.interpolate);
  return scale;
  }
  function scale(x) {
  return output(x);
  }
  var output, input;
  scale.invert = function(y) {
  return input(y);
  };
  scale.domain = function(x) {
  if (!arguments.length) return domain;
  domain = x.map(Number);
  return rescale();
  };
  scale.range = function(x) {
  if (!arguments.length) return range;
  range = x;
  return rescale();
  };
  scale.rangeRound = function(x) {
  return scale.range(x).interpolate(d3.interpolateRound);
  };
  scale.clamp = function(x) {
  if (!arguments.length) return clamp;
  clamp = x;
  return rescale();
  };
  scale.interpolate = function(x) {
  if (!arguments.length) return interpolate;
  interpolate = x;
  return rescale();
  };
  scale.ticks = function(m) {
  return d3_scale_linearTicks(domain, m);
  };
  scale.tickFormat = function(m) {
  return d3_scale_linearTickFormat(domain, m);
  };
  scale.nice = function() {
  d3_scale_nice(domain, d3_scale_linearNice);
  return rescale();
  };
  scale.copy = function() {
  return d3_scale_linear(domain, range, interpolate, clamp);
  };
  return rescale();
  }
  function d3_scale_linearRebind(scale, linear) {
  return d3.rebind(scale, linear, "range", "rangeRound", "interpolate", "clamp");
  }
  function d3_scale_linearNice(dx) {
  dx = Math.pow(10, Math.round(Math.log(dx) / Math.LN10) - 1);
  return dx && {
  floor: function(x) {
  return Math.floor(x / dx) * dx;
  },
  ceil: function(x) {
  return Math.ceil(x / dx) * dx;
  }
  };
  }
  function d3_scale_linearTickRange(domain, m) {
  var extent = d3_scaleExtent(domain), span = extent[1] - extent[0], step = Math.pow(10, Math.floor(Math.log(span / m) / Math.LN10)), err = m / span * step;
  if (err <= .15) step *= 10; else if (err <= .35) step *= 5; else if (err <= .75) step *= 2;
  extent[0] = Math.ceil(extent[0] / step) * step;
  extent[1] = Math.floor(extent[1] / step) * step + step * .5;
  extent[2] = step;
  return extent;
  }
  function d3_scale_linearTicks(domain, m) {
  return d3.range.apply(d3, d3_scale_linearTickRange(domain, m));
  }
  function d3_scale_linearTickFormat(domain, m) {
  return d3.format(",." + Math.max(0, -Math.floor(Math.log(d3_scale_linearTickRange(domain, m)[2]) / Math.LN10 + .01)) + "f");
  }
  function d3_scale_bilinear(domain, range, uninterpolate, interpolate) {
  var u = uninterpolate(domain[0], domain[1]), i = interpolate(range[0], range[1]);
  return function(x) {
  return i(u(x));
  };
  }
  function d3_scale_polylinear(domain, range, uninterpolate, interpolate) {
  var u = [], i = [], j = 0, k = Math.min(domain.length, range.length) - 1;
  if (domain[k] < domain[0]) {
  domain = domain.slice().reverse();
  range = range.slice().reverse();
  }
  while (++j <= k) {
  u.push(uninterpolate(domain[j - 1], domain[j]));
  i.push(interpolate(range[j - 1], range[j]));
  }
  return function(x) {
  var j = d3.bisect(domain, x, 1, k) - 1;
  return i[j](u[j](x));
  };
  }
  function d3_scale_log(linear, log) {
  function scale(x) {
  return linear(log(x));
  }
  var pow = log.pow;
  scale.invert = function(x) {
  return pow(linear.invert(x));
  };
  scale.domain = function(x) {
  if (!arguments.length) return linear.domain().map(pow);
  log = x[0] < 0 ? d3_scale_logn : d3_scale_logp;
  pow = log.pow;
  linear.domain(x.map(log));
  return scale;
  };
  scale.nice = function() {
  linear.domain(d3_scale_nice(linear.domain(), d3_scale_niceDefault));
  return scale;
  };
  scale.ticks = function() {
  var extent = d3_scaleExtent(linear.domain()), ticks = [];
  if (extent.every(isFinite)) {
  var i = Math.floor(extent[0]), j = Math.ceil(extent[1]), u = pow(extent[0]), v = pow(extent[1]);
  if (log === d3_scale_logn) {
  ticks.push(pow(i));
  for (; i++ < j; ) for (var k = 9; k > 0; k--) ticks.push(pow(i) * k);
  } else {
  for (; i < j; i++) for (var k = 1; k < 10; k++) ticks.push(pow(i) * k);
  ticks.push(pow(i));
  }
  for (i = 0; ticks[i] < u; i++) {}
  for (j = ticks.length; ticks[j - 1] > v; j--) {}
  ticks = ticks.slice(i, j);
  }
  return ticks;
  };
  scale.tickFormat = function(n, format) {
  if (arguments.length < 2) format = d3_scale_logFormat;
  if (arguments.length < 1) return format;
  var k = Math.max(.1, n / scale.ticks().length), f = log === d3_scale_logn ? (e = -1e-12, Math.floor) : (e = 1e-12, Math.ceil), e;
  return function(d) {
  return d / pow(f(log(d) + e)) <= k ? format(d) : "";
  };
  };
  scale.copy = function() {
  return d3_scale_log(linear.copy(), log);
  };
  return d3_scale_linearRebind(scale, linear);
  }
  function d3_scale_logp(x) {
  return Math.log(x < 0 ? 0 : x) / Math.LN10;
  }
  function d3_scale_logn(x) {
  return -Math.log(x > 0 ? 0 : -x) / Math.LN10;
  }
  function d3_scale_pow(linear, exponent) {
  function scale(x) {
  return linear(powp(x));
  }
  var powp = d3_scale_powPow(exponent), powb = d3_scale_powPow(1 / exponent);
  scale.invert = function(x) {
  return powb(linear.invert(x));
  };
  scale.domain = function(x) {
  if (!arguments.length) return linear.domain().map(powb);
  linear.domain(x.map(powp));
  return scale;
  };
  scale.ticks = function(m) {
  return d3_scale_linearTicks(scale.domain(), m);
  };
  scale.tickFormat = function(m) {
  return d3_scale_linearTickFormat(scale.domain(), m);
  };
  scale.nice = function() {
  return scale.domain(d3_scale_nice(scale.domain(), d3_scale_linearNice));
  };
  scale.exponent = function(x) {
  if (!arguments.length) return exponent;
  var domain = scale.domain();
  powp = d3_scale_powPow(exponent = x);
  powb = d3_scale_powPow(1 / exponent);
  return scale.domain(domain);
  };
  scale.copy = function() {
  return d3_scale_pow(linear.copy(), exponent);
  };
  return d3_scale_linearRebind(scale, linear);
  }
  function d3_scale_powPow(e) {
  return function(x) {
  return x < 0 ? -Math.pow(-x, e) : Math.pow(x, e);
  };
  }
  function d3_scale_ordinal(domain, ranger) {
  function scale(x) {
  return range[((index.get(x) || index.set(x, domain.push(x))) - 1) % range.length];
  }
  function steps(start, step) {
  return d3.range(domain.length).map(function(i) {
  return start + step * i;
  });
  }
  var index, range, rangeBand;
  scale.domain = function(x) {
  if (!arguments.length) return domain;
  domain = [];
  index = new d3_Map;
  var i = -1, n = x.length, xi;
  while (++i < n) if (!index.has(xi = x[i])) index.set(xi, domain.push(xi));
  return scale[ranger.t].apply(scale, ranger.a);
  };
  scale.range = function(x) {
  if (!arguments.length) return range;
  range = x;
  rangeBand = 0;
  ranger = {
  t: "range",
  a: arguments
  };
  return scale;
  };
  scale.rangePoints = function(x, padding) {
  if (arguments.length < 2) padding = 0;
  var start = x[0], stop = x[1], step = (stop - start) / (Math.max(1, domain.length - 1) + padding);
  range = steps(domain.length < 2 ? (start + stop) / 2 : start + step * padding / 2, step);
  rangeBand = 0;
  ranger = {
  t: "rangePoints",
  a: arguments
  };
  return scale;
  };
  scale.rangeBands = function(x, padding, outerPadding) {
  if (arguments.length < 2) padding = 0;
  if (arguments.length < 3) outerPadding = padding;
  var reverse = x[1] < x[0], start = x[reverse - 0], stop = x[1 - reverse], step = (stop - start) / (domain.length - padding + 2 * outerPadding);
  range = steps(start + step * outerPadding, step);
  if (reverse) range.reverse();
  rangeBand = step * (1 - padding);
  ranger = {
  t: "rangeBands",
  a: arguments
  };
  return scale;
  };
  scale.rangeRoundBands = function(x, padding, outerPadding) {
  if (arguments.length < 2) padding = 0;
  if (arguments.length < 3) outerPadding = padding;
  var reverse = x[1] < x[0], start = x[reverse - 0], stop = x[1 - reverse], step = Math.floor((stop - start) / (domain.length - padding + 2 * outerPadding)), error = stop - start - (domain.length - padding) * step;
  range = steps(start + Math.round(error / 2), step);
  if (reverse) range.reverse();
  rangeBand = Math.round(step * (1 - padding));
  ranger = {
  t: "rangeRoundBands",
  a: arguments
  };
  return scale;
  };
  scale.rangeBand = function() {
  return rangeBand;
  };
  scale.rangeExtent = function() {
  return d3_scaleExtent(ranger.a[0]);
  };
  scale.copy = function() {
  return d3_scale_ordinal(domain, ranger);
  };
  return scale.domain(domain);
  }
  function d3_scale_quantile(domain, range) {
  function rescale() {
  var k = 0, n = domain.length, q = range.length;
  thresholds = [];
  while (++k < q) thresholds[k - 1] = d3.quantile(domain, k / q);
  return scale;
  }
  function scale(x) {
  if (isNaN(x = +x)) return NaN;
  return range[d3.bisect(thresholds, x)];
  }
  var thresholds;
  scale.domain = function(x) {
  if (!arguments.length) return domain;
  domain = x.filter(function(d) {
  return !isNaN(d);
  }).sort(d3.ascending);
  return rescale();
  };
  scale.range = function(x) {
  if (!arguments.length) return range;
  range = x;
  return rescale();
  };
  scale.quantiles = function() {
  return thresholds;
  };
  scale.copy = function() {
  return d3_scale_quantile(domain, range);
  };
  return rescale();
  }
  function d3_scale_quantize(x0, x1, range) {
  function scale(x) {
  return range[Math.max(0, Math.min(i, Math.floor(kx * (x - x0))))];
  }
  function rescale() {
  kx = range.length / (x1 - x0);
  i = range.length - 1;
  return scale;
  }
  var kx, i;
  scale.domain = function(x) {
  if (!arguments.length) return [ x0, x1 ];
  x0 = +x[0];
  x1 = +x[x.length - 1];
  return rescale();
  };
  scale.range = function(x) {
  if (!arguments.length) return range;
  range = x;
  return rescale();
  };
  scale.copy = function() {
  return d3_scale_quantize(x0, x1, range);
  };
  return rescale();
  }
  function d3_scale_threshold(domain, range) {
  function scale(x) {
  return range[d3.bisect(domain, x)];
  }
  scale.domain = function(_) {
  if (!arguments.length) return domain;
  domain = _;
  return scale;
  };
  scale.range = function(_) {
  if (!arguments.length) return range;
  range = _;
  return scale;
  };
  scale.copy = function() {
  return d3_scale_threshold(domain, range);
  };
  return scale;
  }
  function d3_scale_identity(domain) {
  function identity(x) {
  return +x;
  }
  identity.invert = identity;
  identity.domain = identity.range = function(x) {
  if (!arguments.length) return domain;
  domain = x.map(identity);
  return identity;
  };
  identity.ticks = function(m) {
  return d3_scale_linearTicks(domain, m);
  };
  identity.tickFormat = function(m) {
  return d3_scale_linearTickFormat(domain, m);
  };
  identity.copy = function() {
  return d3_scale_identity(domain);
  };
  return identity;
  }
  function d3_svg_arcInnerRadius(d) {
  return d.innerRadius;
  }
  function d3_svg_arcOuterRadius(d) {
  return d.outerRadius;
  }
  function d3_svg_arcStartAngle(d) {
  return d.startAngle;
  }
  function d3_svg_arcEndAngle(d) {
  return d.endAngle;
  }
  function d3_svg_line(projection) {
  function line(data) {
  function segment() {
  segments.push("M", interpolate(projection(points), tension));
  }
  var segments = [], points = [], i = -1, n = data.length, d, fx = d3_functor(x), fy = d3_functor(y);
  while (++i < n) {
  if (defined.call(this, d = data[i], i)) {
  points.push([ +fx.call(this, d, i), +fy.call(this, d, i) ]);
  } else if (points.length) {
  segment();
  points = [];
  }
  }
  if (points.length) segment();
  return segments.length ? segments.join("") : null;
  }
  var x = d3_svg_lineX, y = d3_svg_lineY, defined = d3_true, interpolate = d3_svg_lineLinear, interpolateKey = interpolate.key, tension = .7;
  line.x = function(_) {
  if (!arguments.length) return x;
  x = _;
  return line;
  };
  line.y = function(_) {
  if (!arguments.length) return y;
  y = _;
  return line;
  };
  line.defined = function(_) {
  if (!arguments.length) return defined;
  defined = _;
  return line;
  };
  line.interpolate = function(_) {
  if (!arguments.length) return interpolateKey;
  if (typeof _ === "function") interpolateKey = interpolate = _; else interpolateKey = (interpolate = d3_svg_lineInterpolators.get(_) || d3_svg_lineLinear).key;
  return line;
  };
  line.tension = function(_) {
  if (!arguments.length) return tension;
  tension = _;
  return line;
  };
  return line;
  }
  function d3_svg_lineX(d) {
  return d[0];
  }
  function d3_svg_lineY(d) {
  return d[1];
  }
  function d3_svg_lineLinear(points) {
  return points.join("L");
  }
  function d3_svg_lineLinearClosed(points) {
  return d3_svg_lineLinear(points) + "Z";
  }
  function d3_svg_lineStepBefore(points) {
  var i = 0, n = points.length, p = points[0], path = [ p[0], ",", p[1] ];
  while (++i < n) path.push("V", (p = points[i])[1], "H", p[0]);
  return path.join("");
  }
  function d3_svg_lineStepAfter(points) {
  var i = 0, n = points.length, p = points[0], path = [ p[0], ",", p[1] ];
  while (++i < n) path.push("H", (p = points[i])[0], "V", p[1]);
  return path.join("");
  }
  function d3_svg_lineCardinalOpen(points, tension) {
  return points.length < 4 ? d3_svg_lineLinear(points) : points[1] + d3_svg_lineHermite(points.slice(1, points.length - 1), d3_svg_lineCardinalTangents(points, tension));
  }
  function d3_svg_lineCardinalClosed(points, tension) {
  return points.length < 3 ? d3_svg_lineLinear(points) : points[0] + d3_svg_lineHermite((points.push(points[0]), points), d3_svg_lineCardinalTangents([ points[points.length - 2] ].concat(points, [ points[1] ]), tension));
  }
  function d3_svg_lineCardinal(points, tension, closed) {
  return points.length < 3 ? d3_svg_lineLinear(points) : points[0] + d3_svg_lineHermite(points, d3_svg_lineCardinalTangents(points, tension));
  }
  function d3_svg_lineHermite(points, tangents) {
  if (tangents.length < 1 || points.length != tangents.length && points.length != tangents.length + 2) {
  return d3_svg_lineLinear(points);
  }
  var quad = points.length != tangents.length, path = "", p0 = points[0], p = points[1], t0 = tangents[0], t = t0, pi = 1;
  if (quad) {
  path += "Q" + (p[0] - t0[0] * 2 / 3) + "," + (p[1] - t0[1] * 2 / 3) + "," + p[0] + "," + p[1];
  p0 = points[1];
  pi = 2;
  }
  if (tangents.length > 1) {
  t = tangents[1];
  p = points[pi];
  pi++;
  path += "C" + (p0[0] + t0[0]) + "," + (p0[1] + t0[1]) + "," + (p[0] - t[0]) + "," + (p[1] - t[1]) + "," + p[0] + "," + p[1];
  for (var i = 2; i < tangents.length; i++, pi++) {
  p = points[pi];
  t = tangents[i];
  path += "S" + (p[0] - t[0]) + "," + (p[1] - t[1]) + "," + p[0] + "," + p[1];
  }
  }
  if (quad) {
  var lp = points[pi];
  path += "Q" + (p[0] + t[0] * 2 / 3) + "," + (p[1] + t[1] * 2 / 3) + "," + lp[0] + "," + lp[1];
  }
  return path;
  }
  function d3_svg_lineCardinalTangents(points, tension) {
  var tangents = [], a = (1 - tension) / 2, p0, p1 = points[0], p2 = points[1], i = 1, n = points.length;
  while (++i < n) {
  p0 = p1;
  p1 = p2;
  p2 = points[i];
  tangents.push([ a * (p2[0] - p0[0]), a * (p2[1] - p0[1]) ]);
  }
  return tangents;
  }
  function d3_svg_lineBasis(points) {
  if (points.length < 3) return d3_svg_lineLinear(points);
  var i = 1, n = points.length, pi = points[0], x0 = pi[0], y0 = pi[1], px = [ x0, x0, x0, (pi = points[1])[0] ], py = [ y0, y0, y0, pi[1] ], path = [ x0, ",", y0 ];
  d3_svg_lineBasisBezier(path, px, py);
  while (++i < n) {
  pi = points[i];
  px.shift();
  px.push(pi[0]);
  py.shift();
  py.push(pi[1]);
  d3_svg_lineBasisBezier(path, px, py);
  }
  i = -1;
  while (++i < 2) {
  px.shift();
  px.push(pi[0]);
  py.shift();
  py.push(pi[1]);
  d3_svg_lineBasisBezier(path, px, py);
  }
  return path.join("");
  }
  function d3_svg_lineBasisOpen(points) {
  if (points.length < 4) return d3_svg_lineLinear(points);
  var path = [], i = -1, n = points.length, pi, px = [ 0 ], py = [ 0 ];
  while (++i < 3) {
  pi = points[i];
  px.push(pi[0]);
  py.push(pi[1]);
  }
  path.push(d3_svg_lineDot4(d3_svg_lineBasisBezier3, px) + "," + d3_svg_lineDot4(d3_svg_lineBasisBezier3, py));
  --i;
  while (++i < n) {
  pi = points[i];
  px.shift();
  px.push(pi[0]);
  py.shift();
  py.push(pi[1]);
  d3_svg_lineBasisBezier(path, px, py);
  }
  return path.join("");
  }
  function d3_svg_lineBasisClosed(points) {
  var path, i = -1, n = points.length, m = n + 4, pi, px = [], py = [];
  while (++i < 4) {
  pi = points[i % n];
  px.push(pi[0]);
  py.push(pi[1]);
  }
  path = [ d3_svg_lineDot4(d3_svg_lineBasisBezier3, px), ",", d3_svg_lineDot4(d3_svg_lineBasisBezier3, py) ];
  --i;
  while (++i < m) {
  pi = points[i % n];
  px.shift();
  px.push(pi[0]);
  py.shift();
  py.push(pi[1]);
  d3_svg_lineBasisBezier(path, px, py);
  }
  return path.join("");
  }
  function d3_svg_lineBundle(points, tension) {
  var n = points.length - 1;
  if (n) {
  var x0 = points[0][0], y0 = points[0][1], dx = points[n][0] - x0, dy = points[n][1] - y0, i = -1, p, t;
  while (++i <= n) {
  p = points[i];
  t = i / n;
  p[0] = tension * p[0] + (1 - tension) * (x0 + t * dx);
  p[1] = tension * p[1] + (1 - tension) * (y0 + t * dy);
  }
  }
  return d3_svg_lineBasis(points);
  }
  function d3_svg_lineDot4(a, b) {
  return a[0] * b[0] + a[1] * b[1] + a[2] * b[2] + a[3] * b[3];
  }
  function d3_svg_lineBasisBezier(path, x, y) {
  path.push("C", d3_svg_lineDot4(d3_svg_lineBasisBezier1, x), ",", d3_svg_lineDot4(d3_svg_lineBasisBezier1, y), ",", d3_svg_lineDot4(d3_svg_lineBasisBezier2, x), ",", d3_svg_lineDot4(d3_svg_lineBasisBezier2, y), ",", d3_svg_lineDot4(d3_svg_lineBasisBezier3, x), ",", d3_svg_lineDot4(d3_svg_lineBasisBezier3, y));
  }
  function d3_svg_lineSlope(p0, p1) {
  return (p1[1] - p0[1]) / (p1[0] - p0[0]);
  }
  function d3_svg_lineFiniteDifferences(points) {
  var i = 0, j = points.length - 1, m = [], p0 = points[0], p1 = points[1], d = m[0] = d3_svg_lineSlope(p0, p1);
  while (++i < j) {
  m[i] = (d + (d = d3_svg_lineSlope(p0 = p1, p1 = points[i + 1]))) / 2;
  }
  m[i] = d;
  return m;
  }
  function d3_svg_lineMonotoneTangents(points) {
  var tangents = [], d, a, b, s, m = d3_svg_lineFiniteDifferences(points), i = -1, j = points.length - 1;
  while (++i < j) {
  d = d3_svg_lineSlope(points[i], points[i + 1]);
  if (Math.abs(d) < 1e-6) {
  m[i] = m[i + 1] = 0;
  } else {
  a = m[i] / d;
  b = m[i + 1] / d;
  s = a * a + b * b;
  if (s > 9) {
  s = d * 3 / Math.sqrt(s);
  m[i] = s * a;
  m[i + 1] = s * b;
  }
  }
  }
  i = -1;
  while (++i <= j) {
  s = (points[Math.min(j, i + 1)][0] - points[Math.max(0, i - 1)][0]) / (6 * (1 + m[i] * m[i]));
  tangents.push([ s || 0, m[i] * s || 0 ]);
  }
  return tangents;
  }
  function d3_svg_lineMonotone(points) {
  return points.length < 3 ? d3_svg_lineLinear(points) : points[0] + d3_svg_lineHermite(points, d3_svg_lineMonotoneTangents(points));
  }
  function d3_svg_lineRadial(points) {
  var point, i = -1, n = points.length, r, a;
  while (++i < n) {
  point = points[i];
  r = point[0];
  a = point[1] + d3_svg_arcOffset;
  point[0] = r * Math.cos(a);
  point[1] = r * Math.sin(a);
  }
  return points;
  }
  function d3_svg_area(projection) {
  function area(data) {
  function segment() {
  segments.push("M", interpolate(projection(points1), tension), L, interpolateReverse(projection(points0.reverse()), tension), "Z");
  }
  var segments = [], points0 = [], points1 = [], i = -1, n = data.length, d, fx0 = d3_functor(x0), fy0 = d3_functor(y0), fx1 = x0 === x1 ? function() {
  return x;
  } : d3_functor(x1), fy1 = y0 === y1 ? function() {
  return y;
  } : d3_functor(y1), x, y;
  while (++i < n) {
  if (defined.call(this, d = data[i], i)) {
  points0.push([ x = +fx0.call(this, d, i), y = +fy0.call(this, d, i) ]);
  points1.push([ +fx1.call(this, d, i), +fy1.call(this, d, i) ]);
  } else if (points0.length) {
  segment();
  points0 = [];
  points1 = [];
  }
  }
  if (points0.length) segment();
  return segments.length ? segments.join("") : null;
  }
  var x0 = d3_svg_lineX, x1 = d3_svg_lineX, y0 = 0, y1 = d3_svg_lineY, defined = d3_true, interpolate = d3_svg_lineLinear, interpolateKey = interpolate.key, interpolateReverse = interpolate, L = "L", tension = .7;
  area.x = function(_) {
  if (!arguments.length) return x1;
  x0 = x1 = _;
  return area;
  };
  area.x0 = function(_) {
  if (!arguments.length) return x0;
  x0 = _;
  return area;
  };
  area.x1 = function(_) {
  if (!arguments.length) return x1;
  x1 = _;
  return area;
  };
  area.y = function(_) {
  if (!arguments.length) return y1;
  y0 = y1 = _;
  return area;
  };
  area.y0 = function(_) {
  if (!arguments.length) return y0;
  y0 = _;
  return area;
  };
  area.y1 = function(_) {
  if (!arguments.length) return y1;
  y1 = _;
  return area;
  };
  area.defined = function(_) {
  if (!arguments.length) return defined;
  defined = _;
  return area;
  };
  area.interpolate = function(_) {
  if (!arguments.length) return interpolateKey;
  if (typeof _ === "function") interpolateKey = interpolate = _; else interpolateKey = (interpolate = d3_svg_lineInterpolators.get(_) || d3_svg_lineLinear).key;
  interpolateReverse = interpolate.reverse || interpolate;
  L = interpolate.closed ? "M" : "L";
  return area;
  };
  area.tension = function(_) {
  if (!arguments.length) return tension;
  tension = _;
  return area;
  };
  return area;
  }
  function d3_svg_chordSource(d) {
  return d.source;
  }
  function d3_svg_chordTarget(d) {
  return d.target;
  }
  function d3_svg_chordRadius(d) {
  return d.radius;
  }
  function d3_svg_chordStartAngle(d) {
  return d.startAngle;
  }
  function d3_svg_chordEndAngle(d) {
  return d.endAngle;
  }
  function d3_svg_diagonalProjection(d) {
  return [ d.x, d.y ];
  }
  function d3_svg_diagonalRadialProjection(projection) {
  return function() {
  var d = projection.apply(this, arguments), r = d[0], a = d[1] + d3_svg_arcOffset;
  return [ r * Math.cos(a), r * Math.sin(a) ];
  };
  }
  function d3_svg_symbolSize() {
  return 64;
  }
  function d3_svg_symbolType() {
  return "circle";
  }
  function d3_svg_symbolCircle(size) {
  var r = Math.sqrt(size / Math.PI);
  return "M0," + r + "A" + r + "," + r + " 0 1,1 0," + -r + "A" + r + "," + r + " 0 1,1 0," + r + "Z";
  }
  function d3_svg_axisX(selection, x) {
  selection.attr("transform", function(d) {
  return "translate(" + x(d) + ",0)";
  });
  }
  function d3_svg_axisY(selection, y) {
  selection.attr("transform", function(d) {
  return "translate(0," + y(d) + ")";
  });
  }
  function d3_svg_axisSubdivide(scale, ticks, m) {
  subticks = [];
  if (m && ticks.length > 1) {
  var extent = d3_scaleExtent(scale.domain()), subticks, i = -1, n = ticks.length, d = (ticks[1] - ticks[0]) / ++m, j, v;
  while (++i < n) {
  for (j = m; --j > 0; ) {
  if ((v = +ticks[i] - j * d) >= extent[0]) {
  subticks.push(v);
  }
  }
  }
  for (--i, j = 0; ++j < m && (v = +ticks[i] + j * d) < extent[1]; ) {
  subticks.push(v);
  }
  }
  return subticks;
  }
  function d3_behavior_zoomDelta() {
  if (!d3_behavior_zoomDiv) {
  d3_behavior_zoomDiv = d3.select("body").append("div").style("visibility", "hidden").style("top", 0).style("height", 0).style("width", 0).style("overflow-y", "scroll").append("div").style("height", "2000px").node().parentNode;
  }
  var e = d3.event, delta;
  try {
  d3_behavior_zoomDiv.scrollTop = 1e3;
  d3_behavior_zoomDiv.dispatchEvent(e);
  delta = 1e3 - d3_behavior_zoomDiv.scrollTop;
  } catch (error) {
  delta = e.wheelDelta || -e.detail * 5;
  }
  return delta;
  }
  function d3_layout_bundlePath(link) {
  var start = link.source, end = link.target, lca = d3_layout_bundleLeastCommonAncestor(start, end), points = [ start ];
  while (start !== lca) {
  start = start.parent;
  points.push(start);
  }
  var k = points.length;
  while (end !== lca) {
  points.splice(k, 0, end);
  end = end.parent;
  }
  return points;
  }
  function d3_layout_bundleAncestors(node) {
  var ancestors = [], parent = node.parent;
  while (parent != null) {
  ancestors.push(node);
  node = parent;
  parent = parent.parent;
  }
  ancestors.push(node);
  return ancestors;
  }
  function d3_layout_bundleLeastCommonAncestor(a, b) {
  if (a === b) return a;
  var aNodes = d3_layout_bundleAncestors(a), bNodes = d3_layout_bundleAncestors(b), aNode = aNodes.pop(), bNode = bNodes.pop(), sharedNode = null;
  while (aNode === bNode) {
  sharedNode = aNode;
  aNode = aNodes.pop();
  bNode = bNodes.pop();
  }
  return sharedNode;
  }
  function d3_layout_forceDragstart(d) {
  d.fixed |= 2;
  }
  function d3_layout_forceDragend(d) {
  d.fixed &= 1;
  }
  function d3_layout_forceMouseover(d) {
  d.fixed |= 4;
  }
  function d3_layout_forceMouseout(d) {
  d.fixed &= 3;
  }
  function d3_layout_forceAccumulate(quad, alpha, charges) {
  var cx = 0, cy = 0;
  quad.charge = 0;
  if (!quad.leaf) {
  var nodes = quad.nodes, n = nodes.length, i = -1, c;
  while (++i < n) {
  c = nodes[i];
  if (c == null) continue;
  d3_layout_forceAccumulate(c, alpha, charges);
  quad.charge += c.charge;
  cx += c.charge * c.cx;
  cy += c.charge * c.cy;
  }
  }
  if (quad.point) {
  if (!quad.leaf) {
  quad.point.x += Math.random() - .5;
  quad.point.y += Math.random() - .5;
  }
  var k = alpha * charges[quad.point.index];
  quad.charge += quad.pointCharge = k;
  cx += k * quad.point.x;
  cy += k * quad.point.y;
  }
  quad.cx = cx / quad.charge;
  quad.cy = cy / quad.charge;
  }
  function d3_layout_forceLinkDistance(link) {
  return 20;
  }
  function d3_layout_forceLinkStrength(link) {
  return 1;
  }
  function d3_layout_stackX(d) {
  return d.x;
  }
  function d3_layout_stackY(d) {
  return d.y;
  }
  function d3_layout_stackOut(d, y0, y) {
  d.y0 = y0;
  d.y = y;
  }
  function d3_layout_stackOrderDefault(data) {
  return d3.range(data.length);
  }
  function d3_layout_stackOffsetZero(data) {
  var j = -1, m = data[0].length, y0 = [];
  while (++j < m) y0[j] = 0;
  return y0;
  }
  function d3_layout_stackMaxIndex(array) {
  var i = 1, j = 0, v = array[0][1], k, n = array.length;
  for (; i < n; ++i) {
  if ((k = array[i][1]) > v) {
  j = i;
  v = k;
  }
  }
  return j;
  }
  function d3_layout_stackReduceSum(d) {
  return d.reduce(d3_layout_stackSum, 0);
  }
  function d3_layout_stackSum(p, d) {
  return p + d[1];
  }
  function d3_layout_histogramBinSturges(range, values) {
  return d3_layout_histogramBinFixed(range, Math.ceil(Math.log(values.length) / Math.LN2 + 1));
  }
  function d3_layout_histogramBinFixed(range, n) {
  var x = -1, b = +range[0], m = (range[1] - b) / n, f = [];
  while (++x <= n) f[x] = m * x + b;
  return f;
  }
  function d3_layout_histogramRange(values) {
  return [ d3.min(values), d3.max(values) ];
  }
  function d3_layout_hierarchyRebind(object, hierarchy) {
  d3.rebind(object, hierarchy, "sort", "children", "value");
  object.links = d3_layout_hierarchyLinks;
  object.nodes = function(d) {
  d3_layout_hierarchyInline = true;
  return (object.nodes = object)(d);
  };
  return object;
  }
  function d3_layout_hierarchyChildren(d) {
  return d.children;
  }
  function d3_layout_hierarchyValue(d) {
  return d.value;
  }
  function d3_layout_hierarchySort(a, b) {
  return b.value - a.value;
  }
  function d3_layout_hierarchyLinks(nodes) {
  return d3.merge(nodes.map(function(parent) {
  return (parent.children || []).map(function(child) {
  return {
  source: parent,
  target: child
  };
  });
  }));
  }
  function d3_layout_packSort(a, b) {
  return a.value - b.value;
  }
  function d3_layout_packInsert(a, b) {
  var c = a._pack_next;
  a._pack_next = b;
  b._pack_prev = a;
  b._pack_next = c;
  c._pack_prev = b;
  }
  function d3_layout_packSplice(a, b) {
  a._pack_next = b;
  b._pack_prev = a;
  }
  function d3_layout_packIntersects(a, b) {
  var dx = b.x - a.x, dy = b.y - a.y, dr = a.r + b.r;
  return dr * dr - dx * dx - dy * dy > .001;
  }
  function d3_layout_packSiblings(node) {
  function bound(node) {
  xMin = Math.min(node.x - node.r, xMin);
  xMax = Math.max(node.x + node.r, xMax);
  yMin = Math.min(node.y - node.r, yMin);
  yMax = Math.max(node.y + node.r, yMax);
  }
  if (!(nodes = node.children) || !(n = nodes.length)) return;
  var nodes, xMin = Infinity, xMax = -Infinity, yMin = Infinity, yMax = -Infinity, a, b, c, i, j, k, n;
  nodes.forEach(d3_layout_packLink);
  a = nodes[0];
  a.x = -a.r;
  a.y = 0;
  bound(a);
  if (n > 1) {
  b = nodes[1];
  b.x = b.r;
  b.y = 0;
  bound(b);
  if (n > 2) {
  c = nodes[2];
  d3_layout_packPlace(a, b, c);
  bound(c);
  d3_layout_packInsert(a, c);
  a._pack_prev = c;
  d3_layout_packInsert(c, b);
  b = a._pack_next;
  for (i = 3; i < n; i++) {
  d3_layout_packPlace(a, b, c = nodes[i]);
  var isect = 0, s1 = 1, s2 = 1;
  for (j = b._pack_next; j !== b; j = j._pack_next, s1++) {
  if (d3_layout_packIntersects(j, c)) {
  isect = 1;
  break;
  }
  }
  if (isect == 1) {
  for (k = a._pack_prev; k !== j._pack_prev; k = k._pack_prev, s2++) {
  if (d3_layout_packIntersects(k, c)) {
  break;
  }
  }
  }
  if (isect) {
  if (s1 < s2 || s1 == s2 && b.r < a.r) d3_layout_packSplice(a, b = j); else d3_layout_packSplice(a = k, b);
  i--;
  } else {
  d3_layout_packInsert(a, c);
  b = c;
  bound(c);
  }
  }
  }
  }
  var cx = (xMin + xMax) / 2, cy = (yMin + yMax) / 2, cr = 0;
  for (i = 0; i < n; i++) {
  c = nodes[i];
  c.x -= cx;
  c.y -= cy;
  cr = Math.max(cr, c.r + Math.sqrt(c.x * c.x + c.y * c.y));
  }
  node.r = cr;
  nodes.forEach(d3_layout_packUnlink);
  }
  function d3_layout_packLink(node) {
  node._pack_next = node._pack_prev = node;
  }
  function d3_layout_packUnlink(node) {
  delete node._pack_next;
  delete node._pack_prev;
  }
  function d3_layout_packTransform(node, x, y, k) {
  var children = node.children;
  node.x = x += k * node.x;
  node.y = y += k * node.y;
  node.r *= k;
  if (children) {
  var i = -1, n = children.length;
  while (++i < n) d3_layout_packTransform(children[i], x, y, k);
  }
  }
  function d3_layout_packPlace(a, b, c) {
  var db = a.r + c.r, dx = b.x - a.x, dy = b.y - a.y;
  if (db && (dx || dy)) {
  var da = b.r + c.r, dc = dx * dx + dy * dy;
  da *= da;
  db *= db;
  var x = .5 + (db - da) / (2 * dc), y = Math.sqrt(Math.max(0, 2 * da * (db + dc) - (db -= dc) * db - da * da)) / (2 * dc);
  c.x = a.x + x * dx + y * dy;
  c.y = a.y + x * dy - y * dx;
  } else {
  c.x = a.x + db;
  c.y = a.y;
  }
  }
  function d3_layout_clusterY(children) {
  return 1 + d3.max(children, function(child) {
  return child.y;
  });
  }
  function d3_layout_clusterX(children) {
  return children.reduce(function(x, child) {
  return x + child.x;
  }, 0) / children.length;
  }
  function d3_layout_clusterLeft(node) {
  var children = node.children;
  return children && children.length ? d3_layout_clusterLeft(children[0]) : node;
  }
  function d3_layout_clusterRight(node) {
  var children = node.children, n;
  return children && (n = children.length) ? d3_layout_clusterRight(children[n - 1]) : node;
  }
  function d3_layout_treeSeparation(a, b) {
  return a.parent == b.parent ? 1 : 2;
  }
  function d3_layout_treeLeft(node) {
  var children = node.children;
  return children && children.length ? children[0] : node._tree.thread;
  }
  function d3_layout_treeRight(node) {
  var children = node.children, n;
  return children && (n = children.length) ? children[n - 1] : node._tree.thread;
  }
  function d3_layout_treeSearch(node, compare) {
  var children = node.children;
  if (children && (n = children.length)) {
  var child, n, i = -1;
  while (++i < n) {
  if (compare(child = d3_layout_treeSearch(children[i], compare), node) > 0) {
  node = child;
  }
  }
  }
  return node;
  }
  function d3_layout_treeRightmost(a, b) {
  return a.x - b.x;
  }
  function d3_layout_treeLeftmost(a, b) {
  return b.x - a.x;
  }
  function d3_layout_treeDeepest(a, b) {
  return a.depth - b.depth;
  }
  function d3_layout_treeVisitAfter(node, callback) {
  function visit(node, previousSibling) {
  var children = node.children;
  if (children && (n = children.length)) {
  var child, previousChild = null, i = -1, n;
  while (++i < n) {
  child = children[i];
  visit(child, previousChild);
  previousChild = child;
  }
  }
  callback(node, previousSibling);
  }
  visit(node, null);
  }
  function d3_layout_treeShift(node) {
  var shift = 0, change = 0, children = node.children, i = children.length, child;
  while (--i >= 0) {
  child = children[i]._tree;
  child.prelim += shift;
  child.mod += shift;
  shift += child.shift + (change += child.change);
  }
  }
  function d3_layout_treeMove(ancestor, node, shift) {
  ancestor = ancestor._tree;
  node = node._tree;
  var change = shift / (node.number - ancestor.number);
  ancestor.change += change;
  node.change -= change;
  node.shift += shift;
  node.prelim += shift;
  node.mod += shift;
  }
  function d3_layout_treeAncestor(vim, node, ancestor) {
  return vim._tree.ancestor.parent == node.parent ? vim._tree.ancestor : ancestor;
  }
  function d3_layout_treemapPadNull(node) {
  return {
  x: node.x,
  y: node.y,
  dx: node.dx,
  dy: node.dy
  };
  }
  function d3_layout_treemapPad(node, padding) {
  var x = node.x + padding[3], y = node.y + padding[0], dx = node.dx - padding[1] - padding[3], dy = node.dy - padding[0] - padding[2];
  if (dx < 0) {
  x += dx / 2;
  dx = 0;
  }
  if (dy < 0) {
  y += dy / 2;
  dy = 0;
  }
  return {
  x: x,
  y: y,
  dx: dx,
  dy: dy
  };
  }
  function d3_dsv(delimiter, mimeType) {
  function dsv(url, callback) {
  d3.text(url, mimeType, function(text) {
  callback(text && dsv.parse(text));
  });
  }
  function formatRow(row) {
  return row.map(formatValue).join(delimiter);
  }
  function formatValue(text) {
  return reFormat.test(text) ? '"' + text.replace(/\"/g, '""') + '"' : text;
  }
  var reParse = new RegExp("\r\n|[" + delimiter + "\r\n]", "g"), reFormat = new RegExp('["' + delimiter + "\n]"), delimiterCode = delimiter.charCodeAt(0);
  dsv.parse = function(text) {
  var header;
  return dsv.parseRows(text, function(row, i) {
  if (i) {
  var o = {}, j = -1, m = header.length;
  while (++j < m) o[header[j]] = row[j];
  return o;
  } else {
  header = row;
  return null;
  }
  });
  };
  dsv.parseRows = function(text, f) {
  function token() {
  if (reParse.lastIndex >= text.length) return EOF;
  if (eol) {
  eol = false;
  return EOL;
  }
  var j = reParse.lastIndex;
  if (text.charCodeAt(j) === 34) {
  var i = j;
  while (i++ < text.length) {
  if (text.charCodeAt(i) === 34) {
  if (text.charCodeAt(i + 1) !== 34) break;
  i++;
  }
  }
  reParse.lastIndex = i + 2;
  var c = text.charCodeAt(i + 1);
  if (c === 13) {
  eol = true;
  if (text.charCodeAt(i + 2) === 10) reParse.lastIndex++;
  } else if (c === 10) {
  eol = true;
  }
  return text.substring(j + 1, i).replace(/""/g, '"');
  }
  var m = reParse.exec(text);
  if (m) {
  eol = m[0].charCodeAt(0) !== delimiterCode;
  return text.substring(j, m.index);
  }
  reParse.lastIndex = text.length;
  return text.substring(j);
  }
  var EOL = {}, EOF = {}, rows = [], n = 0, t, eol;
  reParse.lastIndex = 0;
  while ((t = token()) !== EOF) {
  var a = [];
  while (t !== EOL && t !== EOF) {
  a.push(t);
  t = token();
  }
  if (f && !(a = f(a, n++))) continue;
  rows.push(a);
  }
  return rows;
  };
  dsv.format = function(rows) {
  return rows.map(formatRow).join("\n");
  };
  return dsv;
  }
  function d3_geo_type(types, defaultValue) {
  return function(object) {
  return object && types.hasOwnProperty(object.type) ? types[object.type](object) : defaultValue;
  };
  }
  function d3_path_circle(radius) {
  return "m0," + radius + "a" + radius + "," + radius + " 0 1,1 0," + -2 * radius + "a" + radius + "," + radius + " 0 1,1 0," + +2 * radius + "z";
  }
  function d3_geo_bounds(o, f) {
  if (d3_geo_boundsTypes.hasOwnProperty(o.type)) d3_geo_boundsTypes[o.type](o, f);
  }
  function d3_geo_boundsFeature(o, f) {
  d3_geo_bounds(o.geometry, f);
  }
  function d3_geo_boundsFeatureCollection(o, f) {
  for (var a = o.features, i = 0, n = a.length; i < n; i++) {
  d3_geo_bounds(a[i].geometry, f);
  }
  }
  function d3_geo_boundsGeometryCollection(o, f) {
  for (var a = o.geometries, i = 0, n = a.length; i < n; i++) {
  d3_geo_bounds(a[i], f);
  }
  }
  function d3_geo_boundsLineString(o, f) {
  for (var a = o.coordinates, i = 0, n = a.length; i < n; i++) {
  f.apply(null, a[i]);
  }
  }
  function d3_geo_boundsMultiLineString(o, f) {
  for (var a = o.coordinates, i = 0, n = a.length; i < n; i++) {
  for (var b = a[i], j = 0, m = b.length; j < m; j++) {
  f.apply(null, b[j]);
  }
  }
  }
  function d3_geo_boundsMultiPolygon(o, f) {
  for (var a = o.coordinates, i = 0, n = a.length; i < n; i++) {
  for (var b = a[i][0], j = 0, m = b.length; j < m; j++) {
  f.apply(null, b[j]);
  }
  }
  }
  function d3_geo_boundsPoint(o, f) {
  f.apply(null, o.coordinates);
  }
  function d3_geo_boundsPolygon(o, f) {
  for (var a = o.coordinates[0], i = 0, n = a.length; i < n; i++) {
  f.apply(null, a[i]);
  }
  }
  function d3_geo_greatArcSource(d) {
  return d.source;
  }
  function d3_geo_greatArcTarget(d) {
  return d.target;
  }
  function d3_geo_greatArcInterpolator() {
  function interpolate(t) {
  var B = Math.sin(t *= d) * k, A = Math.sin(d - t) * k, x = A * kx0 + B * kx1, y = A * ky0 + B * ky1, z = A * sy0 + B * sy1;
  return [ Math.atan2(y, x) / d3_geo_radians, Math.atan2(z, Math.sqrt(x * x + y * y)) / d3_geo_radians ];
  }
  var x0, y0, cy0, sy0, kx0, ky0, x1, y1, cy1, sy1, kx1, ky1, d, k;
  interpolate.distance = function() {
  if (d == null) k = 1 / Math.sin(d = Math.acos(Math.max(-1, Math.min(1, sy0 * sy1 + cy0 * cy1 * Math.cos(x1 - x0)))));
  return d;
  };
  interpolate.source = function(_) {
  var cx0 = Math.cos(x0 = _[0] * d3_geo_radians), sx0 = Math.sin(x0);
  cy0 = Math.cos(y0 = _[1] * d3_geo_radians);
  sy0 = Math.sin(y0);
  kx0 = cy0 * cx0;
  ky0 = cy0 * sx0;
  d = null;
  return interpolate;
  };
  interpolate.target = function(_) {
  var cx1 = Math.cos(x1 = _[0] * d3_geo_radians), sx1 = Math.sin(x1);
  cy1 = Math.cos(y1 = _[1] * d3_geo_radians);
  sy1 = Math.sin(y1);
  kx1 = cy1 * cx1;
  ky1 = cy1 * sx1;
  d = null;
  return interpolate;
  };
  return interpolate;
  }
  function d3_geo_greatArcInterpolate(a, b) {
  var i = d3_geo_greatArcInterpolator().source(a).target(b);
  i.distance();
  return i;
  }
  function d3_geom_contourStart(grid) {
  var x = 0, y = 0;
  while (true) {
  if (grid(x, y)) {
  return [ x, y ];
  }
  if (x === 0) {
  x = y + 1;
  y = 0;
  } else {
  x = x - 1;
  y = y + 1;
  }
  }
  }
  function d3_geom_hullCCW(i1, i2, i3, v) {
  var t, a, b, c, d, e, f;
  t = v[i1];
  a = t[0];
  b = t[1];
  t = v[i2];
  c = t[0];
  d = t[1];
  t = v[i3];
  e = t[0];
  f = t[1];
  return (f - b) * (c - a) - (d - b) * (e - a) > 0;
  }
  function d3_geom_polygonInside(p, a, b) {
  return (b[0] - a[0]) * (p[1] - a[1]) < (b[1] - a[1]) * (p[0] - a[0]);
  }
  function d3_geom_polygonIntersect(c, d, a, b) {
  var x1 = c[0], x2 = d[0], x3 = a[0], x4 = b[0], y1 = c[1], y2 = d[1], y3 = a[1], y4 = b[1], x13 = x1 - x3, x21 = x2 - x1, x43 = x4 - x3, y13 = y1 - y3, y21 = y2 - y1, y43 = y4 - y3, ua = (x43 * y13 - y43 * x13) / (y43 * x21 - x43 * y21);
  return [ x1 + ua * x21, y1 + ua * y21 ];
  }
  function d3_voronoi_tessellate(vertices, callback) {
  var Sites = {
  list: vertices.map(function(v, i) {
  return {
  index: i,
  x: v[0],
  y: v[1]
  };
  }).sort(function(a, b) {
  return a.y < b.y ? -1 : a.y > b.y ? 1 : a.x < b.x ? -1 : a.x > b.x ? 1 : 0;
  }),
  bottomSite: null
  };
  var EdgeList = {
  list: [],
  leftEnd: null,
  rightEnd: null,
  init: function() {
  EdgeList.leftEnd = EdgeList.createHalfEdge(null, "l");
  EdgeList.rightEnd = EdgeList.createHalfEdge(null, "l");
  EdgeList.leftEnd.r = EdgeList.rightEnd;
  EdgeList.rightEnd.l = EdgeList.leftEnd;
  EdgeList.list.unshift(EdgeList.leftEnd, EdgeList.rightEnd);
  },
  createHalfEdge: function(edge, side) {
  return {
  edge: edge,
  side: side,
  vertex: null,
  l: null,
  r: null
  };
  },
  insert: function(lb, he) {
  he.l = lb;
  he.r = lb.r;
  lb.r.l = he;
  lb.r = he;
  },
  leftBound: function(p) {
  var he = EdgeList.leftEnd;
  do {
  he = he.r;
  } while (he != EdgeList.rightEnd && Geom.rightOf(he, p));
  he = he.l;
  return he;
  },
  del: function(he) {
  he.l.r = he.r;
  he.r.l = he.l;
  he.edge = null;
  },
  right: function(he) {
  return he.r;
  },
  left: function(he) {
  return he.l;
  },
  leftRegion: function(he) {
  return he.edge == null ? Sites.bottomSite : he.edge.region[he.side];
  },
  rightRegion: function(he) {
  return he.edge == null ? Sites.bottomSite : he.edge.region[d3_voronoi_opposite[he.side]];
  }
  };
  var Geom = {
  bisect: function(s1, s2) {
  var newEdge = {
  region: {
  l: s1,
  r: s2
  },
  ep: {
  l: null,
  r: null
  }
  };
  var dx = s2.x - s1.x, dy = s2.y - s1.y, adx = dx > 0 ? dx : -dx, ady = dy > 0 ? dy : -dy;
  newEdge.c = s1.x * dx + s1.y * dy + (dx * dx + dy * dy) * .5;
  if (adx > ady) {
  newEdge.a = 1;
  newEdge.b = dy / dx;
  newEdge.c /= dx;
  } else {
  newEdge.b = 1;
  newEdge.a = dx / dy;
  newEdge.c /= dy;
  }
  return newEdge;
  },
  intersect: function(el1, el2) {
  var e1 = el1.edge, e2 = el2.edge;
  if (!e1 || !e2 || e1.region.r == e2.region.r) {
  return null;
  }
  var d = e1.a * e2.b - e1.b * e2.a;
  if (Math.abs(d) < 1e-10) {
  return null;
  }
  var xint = (e1.c * e2.b - e2.c * e1.b) / d, yint = (e2.c * e1.a - e1.c * e2.a) / d, e1r = e1.region.r, e2r = e2.region.r, el, e;
  if (e1r.y < e2r.y || e1r.y == e2r.y && e1r.x < e2r.x) {
  el = el1;
  e = e1;
  } else {
  el = el2;
  e = e2;
  }
  var rightOfSite = xint >= e.region.r.x;
  if (rightOfSite && el.side === "l" || !rightOfSite && el.side === "r") {
  return null;
  }
  return {
  x: xint,
  y: yint
  };
  },
  rightOf: function(he, p) {
  var e = he.edge, topsite = e.region.r, rightOfSite = p.x > topsite.x;
  if (rightOfSite && he.side === "l") {
  return 1;
  }
  if (!rightOfSite && he.side === "r") {
  return 0;
  }
  if (e.a === 1) {
  var dyp = p.y - topsite.y, dxp = p.x - topsite.x, fast = 0, above = 0;
  if (!rightOfSite && e.b < 0 || rightOfSite && e.b >= 0) {
  above = fast = dyp >= e.b * dxp;
  } else {
  above = p.x + p.y * e.b > e.c;
  if (e.b < 0) {
  above = !above;
  }
  if (!above) {
  fast = 1;
  }
  }
  if (!fast) {
  var dxs = topsite.x - e.region.l.x;
  above = e.b * (dxp * dxp - dyp * dyp) < dxs * dyp * (1 + 2 * dxp / dxs + e.b * e.b);
  if (e.b < 0) {
  above = !above;
  }
  }
  } else {
  var yl = e.c - e.a * p.x, t1 = p.y - yl, t2 = p.x - topsite.x, t3 = yl - topsite.y;
  above = t1 * t1 > t2 * t2 + t3 * t3;
  }
  return he.side === "l" ? above : !above;
  },
  endPoint: function(edge, side, site) {
  edge.ep[side] = site;
  if (!edge.ep[d3_voronoi_opposite[side]]) return;
  callback(edge);
  },
  distance: function(s, t) {
  var dx = s.x - t.x, dy = s.y - t.y;
  return Math.sqrt(dx * dx + dy * dy);
  }
  };
  var EventQueue = {
  list: [],
  insert: function(he, site, offset) {
  he.vertex = site;
  he.ystar = site.y + offset;
  for (var i = 0, list = EventQueue.list, l = list.length; i < l; i++) {
  var next = list[i];
  if (he.ystar > next.ystar || he.ystar == next.ystar && site.x > next.vertex.x) {
  continue;
  } else {
  break;
  }
  }
  list.splice(i, 0, he);
  },
  del: function(he) {
  for (var i = 0, ls = EventQueue.list, l = ls.length; i < l && ls[i] != he; ++i) {}
  ls.splice(i, 1);
  },
  empty: function() {
  return EventQueue.list.length === 0;
  },
  nextEvent: function(he) {
  for (var i = 0, ls = EventQueue.list, l = ls.length; i < l; ++i) {
  if (ls[i] == he) return ls[i + 1];
  }
  return null;
  },
  min: function() {
  var elem = EventQueue.list[0];
  return {
  x: elem.vertex.x,
  y: elem.ystar
  };
  },
  extractMin: function() {
  return EventQueue.list.shift();
  }
  };
  EdgeList.init();
  Sites.bottomSite = Sites.list.shift();
  var newSite = Sites.list.shift(), newIntStar;
  var lbnd, rbnd, llbnd, rrbnd, bisector;
  var bot, top, temp, p, v;
  var e, pm;
  while (true) {
  if (!EventQueue.empty()) {
  newIntStar = EventQueue.min();
  }
  if (newSite && (EventQueue.empty() || newSite.y < newIntStar.y || newSite.y == newIntStar.y && newSite.x < newIntStar.x)) {
  lbnd = EdgeList.leftBound(newSite);
  rbnd = EdgeList.right(lbnd);
  bot = EdgeList.rightRegion(lbnd);
  e = Geom.bisect(bot, newSite);
  bisector = EdgeList.createHalfEdge(e, "l");
  EdgeList.insert(lbnd, bisector);
  p = Geom.intersect(lbnd, bisector);
  if (p) {
  EventQueue.del(lbnd);
  EventQueue.insert(lbnd, p, Geom.distance(p, newSite));
  }
  lbnd = bisector;
  bisector = EdgeList.createHalfEdge(e, "r");
  EdgeList.insert(lbnd, bisector);
  p = Geom.intersect(bisector, rbnd);
  if (p) {
  EventQueue.insert(bisector, p, Geom.distance(p, newSite));
  }
  newSite = Sites.list.shift();
  } else if (!EventQueue.empty()) {
  lbnd = EventQueue.extractMin();
  llbnd = EdgeList.left(lbnd);
  rbnd = EdgeList.right(lbnd);
  rrbnd = EdgeList.right(rbnd);
  bot = EdgeList.leftRegion(lbnd);
  top = EdgeList.rightRegion(rbnd);
  v = lbnd.vertex;
  Geom.endPoint(lbnd.edge, lbnd.side, v);
  Geom.endPoint(rbnd.edge, rbnd.side, v);
  EdgeList.del(lbnd);
  EventQueue.del(rbnd);
  EdgeList.del(rbnd);
  pm = "l";
  if (bot.y > top.y) {
  temp = bot;
  bot = top;
  top = temp;
  pm = "r";
  }
  e = Geom.bisect(bot, top);
  bisector = EdgeList.createHalfEdge(e, pm);
  EdgeList.insert(llbnd, bisector);
  Geom.endPoint(e, d3_voronoi_opposite[pm], v);
  p = Geom.intersect(llbnd, bisector);
  if (p) {
  EventQueue.del(llbnd);
  EventQueue.insert(llbnd, p, Geom.distance(p, bot));
  }
  p = Geom.intersect(bisector, rrbnd);
  if (p) {
  EventQueue.insert(bisector, p, Geom.distance(p, bot));
  }
  } else {
  break;
  }
  }
  for (lbnd = EdgeList.right(EdgeList.leftEnd); lbnd != EdgeList.rightEnd; lbnd = EdgeList.right(lbnd)) {
  callback(lbnd.edge);
  }
  }
  function d3_geom_quadtreeNode() {
  return {
  leaf: true,
  nodes: [],
  point: null
  };
  }
  function d3_geom_quadtreeVisit(f, node, x1, y1, x2, y2) {
  if (!f(node, x1, y1, x2, y2)) {
  var sx = (x1 + x2) * .5, sy = (y1 + y2) * .5, children = node.nodes;
  if (children[0]) d3_geom_quadtreeVisit(f, children[0], x1, y1, sx, sy);
  if (children[1]) d3_geom_quadtreeVisit(f, children[1], sx, y1, x2, sy);
  if (children[2]) d3_geom_quadtreeVisit(f, children[2], x1, sy, sx, y2);
  if (children[3]) d3_geom_quadtreeVisit(f, children[3], sx, sy, x2, y2);
  }
  }
  function d3_geom_quadtreePoint(p) {
  return {
  x: p[0],
  y: p[1]
  };
  }
  function d3_time_utc() {
  this._ = new Date(arguments.length > 1 ? Date.UTC.apply(this, arguments) : arguments[0]);
  }
  function d3_time_formatAbbreviate(name) {
  return name.substring(0, 3);
  }
  function d3_time_parse(date, template, string, j) {
  var c, p, i = 0, n = template.length, m = string.length;
  while (i < n) {
  if (j >= m) return -1;
  c = template.charCodeAt(i++);
  if (c == 37) {
  p = d3_time_parsers[template.charAt(i++)];
  if (!p || (j = p(date, string, j)) < 0) return -1;
  } else if (c != string.charCodeAt(j++)) {
  return -1;
  }
  }
  return j;
  }
  function d3_time_formatRe(names) {
  return new RegExp("^(?:" + names.map(d3.requote).join("|") + ")", "i");
  }
  function d3_time_formatLookup(names) {
  var map = new d3_Map, i = -1, n = names.length;
  while (++i < n) map.set(names[i].toLowerCase(), i);
  return map;
  }
  function d3_time_parseWeekdayAbbrev(date, string, i) {
  d3_time_dayAbbrevRe.lastIndex = 0;
  var n = d3_time_dayAbbrevRe.exec(string.substring(i));
  return n ? i += n[0].length : -1;
  }
  function d3_time_parseWeekday(date, string, i) {
  d3_time_dayRe.lastIndex = 0;
  var n = d3_time_dayRe.exec(string.substring(i));
  return n ? i += n[0].length : -1;
  }
  function d3_time_parseMonthAbbrev(date, string, i) {
  d3_time_monthAbbrevRe.lastIndex = 0;
  var n = d3_time_monthAbbrevRe.exec(string.substring(i));
  return n ? (date.m = d3_time_monthAbbrevLookup.get(n[0].toLowerCase()), i += n[0].length) : -1;
  }
  function d3_time_parseMonth(date, string, i) {
  d3_time_monthRe.lastIndex = 0;
  var n = d3_time_monthRe.exec(string.substring(i));
  return n ? (date.m = d3_time_monthLookup.get(n[0].toLowerCase()), i += n[0].length) : -1;
  }
  function d3_time_parseLocaleFull(date, string, i) {
  return d3_time_parse(date, d3_time_formats.c.toString(), string, i);
  }
  function d3_time_parseLocaleDate(date, string, i) {
  return d3_time_parse(date, d3_time_formats.x.toString(), string, i);
  }
  function d3_time_parseLocaleTime(date, string, i) {
  return d3_time_parse(date, d3_time_formats.X.toString(), string, i);
  }
  function d3_time_parseFullYear(date, string, i) {
  d3_time_numberRe.lastIndex = 0;
  var n = d3_time_numberRe.exec(string.substring(i, i + 4));
  return n ? (date.y = +n[0], i += n[0].length) : -1;
  }
  function d3_time_parseYear(date, string, i) {
  d3_time_numberRe.lastIndex = 0;
  var n = d3_time_numberRe.exec(string.substring(i, i + 2));
  return n ? (date.y = d3_time_expandYear(+n[0]), i += n[0].length) : -1;
  }
  function d3_time_expandYear(d) {
  return d + (d > 68 ? 1900 : 2e3);
  }
  function d3_time_parseMonthNumber(date, string, i) {
  d3_time_numberRe.lastIndex = 0;
  var n = d3_time_numberRe.exec(string.substring(i, i + 2));
  return n ? (date.m = n[0] - 1, i += n[0].length) : -1;
  }
  function d3_time_parseDay(date, string, i) {
  d3_time_numberRe.lastIndex = 0;
  var n = d3_time_numberRe.exec(string.substring(i, i + 2));
  return n ? (date.d = +n[0], i += n[0].length) : -1;
  }
  function d3_time_parseHour24(date, string, i) {
  d3_time_numberRe.lastIndex = 0;
  var n = d3_time_numberRe.exec(string.substring(i, i + 2));
  return n ? (date.H = +n[0], i += n[0].length) : -1;
  }
  function d3_time_parseMinutes(date, string, i) {
  d3_time_numberRe.lastIndex = 0;
  var n = d3_time_numberRe.exec(string.substring(i, i + 2));
  return n ? (date.M = +n[0], i += n[0].length) : -1;
  }
  function d3_time_parseSeconds(date, string, i) {
  d3_time_numberRe.lastIndex = 0;
  var n = d3_time_numberRe.exec(string.substring(i, i + 2));
  return n ? (date.S = +n[0], i += n[0].length) : -1;
  }
  function d3_time_parseMilliseconds(date, string, i) {
  d3_time_numberRe.lastIndex = 0;
  var n = d3_time_numberRe.exec(string.substring(i, i + 3));
  return n ? (date.L = +n[0], i += n[0].length) : -1;
  }
  function d3_time_parseAmPm(date, string, i) {
  var n = d3_time_amPmLookup.get(string.substring(i, i += 2).toLowerCase());
  return n == null ? -1 : (date.p = n, i);
  }
  function d3_time_zone(d) {
  var z = d.getTimezoneOffset(), zs = z > 0 ? "-" : "+", zh = ~~(Math.abs(z) / 60), zm = Math.abs(z) % 60;
  return zs + d3_time_zfill2(zh) + d3_time_zfill2(zm);
  }
  function d3_time_formatIsoNative(date) {
  return date.toISOString();
  }
  function d3_time_interval(local, step, number) {
  function round(date) {
  var d0 = local(date), d1 = offset(d0, 1);
  return date - d0 < d1 - date ? d0 : d1;
  }
  function ceil(date) {
  step(date = local(new d3_time(date - 1)), 1);
  return date;
  }
  function offset(date, k) {
  step(date = new d3_time(+date), k);
  return date;
  }
  function range(t0, t1, dt) {
  var time = ceil(t0), times = [];
  if (dt > 1) {
  while (time < t1) {
  if (!(number(time) % dt)) times.push(new Date(+time));
  step(time, 1);
  }
  } else {
  while (time < t1) times.push(new Date(+time)), step(time, 1);
  }
  return times;
  }
  function range_utc(t0, t1, dt) {
  try {
  d3_time = d3_time_utc;
  var utc = new d3_time_utc;
  utc._ = t0;
  return range(utc, t1, dt);
  } finally {
  d3_time = Date;
  }
  }
  local.floor = local;
  local.round = round;
  local.ceil = ceil;
  local.offset = offset;
  local.range = range;
  var utc = local.utc = d3_time_interval_utc(local);
  utc.floor = utc;
  utc.round = d3_time_interval_utc(round);
  utc.ceil = d3_time_interval_utc(ceil);
  utc.offset = d3_time_interval_utc(offset);
  utc.range = range_utc;
  return local;
  }
  function d3_time_interval_utc(method) {
  return function(date, k) {
  try {
  d3_time = d3_time_utc;
  var utc = new d3_time_utc;
  utc._ = date;
  return method(utc, k)._;
  } finally {
  d3_time = Date;
  }
  };
  }
  function d3_time_scale(linear, methods, format) {
  function scale(x) {
  return linear(x);
  }
  scale.invert = function(x) {
  return d3_time_scaleDate(linear.invert(x));
  };
  scale.domain = function(x) {
  if (!arguments.length) return linear.domain().map(d3_time_scaleDate);
  linear.domain(x);
  return scale;
  };
  scale.nice = function(m) {
  return scale.domain(d3_scale_nice(scale.domain(), function() {
  return m;
  }));
  };
  scale.ticks = function(m, k) {
  var extent = d3_time_scaleExtent(scale.domain());
  if (typeof m !== "function") {
  var span = extent[1] - extent[0], target = span / m, i = d3.bisect(d3_time_scaleSteps, target);
  if (i == d3_time_scaleSteps.length) return methods.year(extent, m);
  if (!i) return linear.ticks(m).map(d3_time_scaleDate);
  if (Math.log(target / d3_time_scaleSteps[i - 1]) < Math.log(d3_time_scaleSteps[i] / target)) --i;
  m = methods[i];
  k = m[1];
  m = m[0].range;
  }
  return m(extent[0], new Date(+extent[1] + 1), k);
  };
  scale.tickFormat = function() {
  return format;
  };
  scale.copy = function() {
  return d3_time_scale(linear.copy(), methods, format);
  };
  return d3.rebind(scale, linear, "range", "rangeRound", "interpolate", "clamp");
  }
  function d3_time_scaleExtent(domain) {
  var start = domain[0], stop = domain[domain.length - 1];
  return start < stop ? [ start, stop ] : [ stop, start ];
  }
  function d3_time_scaleDate(t) {
  return new Date(t);
  }
  function d3_time_scaleFormat(formats) {
  return function(date) {
  var i = formats.length - 1, f = formats[i];
  while (!f[1](date)) f = formats[--i];
  return f[0](date);
  };
  }
  function d3_time_scaleSetYear(y) {
  var d = new Date(y, 0, 1);
  d.setFullYear(y);
  return d;
  }
  function d3_time_scaleGetYear(d) {
  var y = d.getFullYear(), d0 = d3_time_scaleSetYear(y), d1 = d3_time_scaleSetYear(y + 1);
  return y + (d - d0) / (d1 - d0);
  }
  function d3_time_scaleUTCSetYear(y) {
  var d = new Date(Date.UTC(y, 0, 1));
  d.setUTCFullYear(y);
  return d;
  }
  function d3_time_scaleUTCGetYear(d) {
  var y = d.getUTCFullYear(), d0 = d3_time_scaleUTCSetYear(y), d1 = d3_time_scaleUTCSetYear(y + 1);
  return y + (d - d0) / (d1 - d0);
  }
  if (!Date.now) Date.now = function() {
  return +(new Date);
  };
  try {
  document.createElement("div").style.setProperty("opacity", 0, "");
  } catch (error) {
  var d3_style_prototype = CSSStyleDeclaration.prototype, d3_style_setProperty = d3_style_prototype.setProperty;
  d3_style_prototype.setProperty = function(name, value, priority) {
  d3_style_setProperty.call(this, name, value + "", priority);
  };
  }
  d3 = {
  version: "2.10.3"
  };
  var d3_array = d3_arraySlice;
  try {
  d3_array(document.documentElement.childNodes)[0].nodeType;
  } catch (e) {
  d3_array = d3_arrayCopy;
  }
  var d3_arraySubclass = [].__proto__ ? function(array, prototype) {
  array.__proto__ = prototype;
  } : function(array, prototype) {
  for (var property in prototype) array[property] = prototype[property];
  };
  d3.map = function(object) {
  var map = new d3_Map;
  for (var key in object) map.set(key, object[key]);
  return map;
  };
  d3_class(d3_Map, {
  has: function(key) {
  return d3_map_prefix + key in this;
  },
  get: function(key) {
  return this[d3_map_prefix + key];
  },
  set: function(key, value) {
  return this[d3_map_prefix + key] = value;
  },
  remove: function(key) {
  key = d3_map_prefix + key;
  return key in this && delete this[key];
  },
  keys: function() {
  var keys = [];
  this.forEach(function(key) {
  keys.push(key);
  });
  return keys;
  },
  values: function() {
  var values = [];
  this.forEach(function(key, value) {
  values.push(value);
  });
  return values;
  },
  entries: function() {
  var entries = [];
  this.forEach(function(key, value) {
  entries.push({
  key: key,
  value: value
  });
  });
  return entries;
  },
  forEach: function(f) {
  for (var key in this) {
  if (key.charCodeAt(0) === d3_map_prefixCode) {
  f.call(this, key.substring(1), this[key]);
  }
  }
  }
  });
  var d3_map_prefix = "\0", d3_map_prefixCode = d3_map_prefix.charCodeAt(0);
  d3.functor = d3_functor;
  d3.rebind = function(target, source) {
  var i = 1, n = arguments.length, method;
  while (++i < n) target[method = arguments[i]] = d3_rebind(target, source, source[method]);
  return target;
  };
  d3.ascending = function(a, b) {
  return a < b ? -1 : a > b ? 1 : a >= b ? 0 : NaN;
  };
  d3.descending = function(a, b) {
  return b < a ? -1 : b > a ? 1 : b >= a ? 0 : NaN;
  };
  d3.mean = function(array, f) {
  var n = array.length, a, m = 0, i = -1, j = 0;
  if (arguments.length === 1) {
  while (++i < n) if (d3_number(a = array[i])) m += (a - m) / ++j;
  } else {
  while (++i < n) if (d3_number(a = f.call(array, array[i], i))) m += (a - m) / ++j;
  }
  return j ? m : undefined;
  };
  d3.median = function(array, f) {
  if (arguments.length > 1) array = array.map(f);
  array = array.filter(d3_number);
  return array.length ? d3.quantile(array.sort(d3.ascending), .5) : undefined;
  };
  d3.min = function(array, f) {
  var i = -1, n = array.length, a, b;
  if (arguments.length === 1) {
  while (++i < n && ((a = array[i]) == null || a != a)) a = undefined;
  while (++i < n) if ((b = array[i]) != null && a > b) a = b;
  } else {
  while (++i < n && ((a = f.call(array, array[i], i)) == null || a != a)) a = undefined;
  while (++i < n) if ((b = f.call(array, array[i], i)) != null && a > b) a = b;
  }
  return a;
  };
  d3.max = function(array, f) {
  var i = -1, n = array.length, a, b;
  if (arguments.length === 1) {
  while (++i < n && ((a = array[i]) == null || a != a)) a = undefined;
  while (++i < n) if ((b = array[i]) != null && b > a) a = b;
  } else {
  while (++i < n && ((a = f.call(array, array[i], i)) == null || a != a)) a = undefined;
  while (++i < n) if ((b = f.call(array, array[i], i)) != null && b > a) a = b;
  }
  return a;
  };
  d3.extent = function(array, f) {
  var i = -1, n = array.length, a, b, c;
  if (arguments.length === 1) {
  while (++i < n && ((a = c = array[i]) == null || a != a)) a = c = undefined;
  while (++i < n) if ((b = array[i]) != null) {
  if (a > b) a = b;
  if (c < b) c = b;
  }
  } else {
  while (++i < n && ((a = c = f.call(array, array[i], i)) == null || a != a)) a = undefined;
  while (++i < n) if ((b = f.call(array, array[i], i)) != null) {
  if (a > b) a = b;
  if (c < b) c = b;
  }
  }
  return [ a, c ];
  };
  d3.random = {
  normal: function(µ, σ) {
  var n = arguments.length;
  if (n < 2) σ = 1;
  if (n < 1) µ = 0;
  return function() {
  var x, y, r;
  do {
  x = Math.random() * 2 - 1;
  y = Math.random() * 2 - 1;
  r = x * x + y * y;
  } while (!r || r > 1);
  return µ + σ * x * Math.sqrt(-2 * Math.log(r) / r);
  };
  },
  logNormal: function(µ, σ) {
  var n = arguments.length;
  if (n < 2) σ = 1;
  if (n < 1) µ = 0;
  var random = d3.random.normal();
  return function() {
  return Math.exp(µ + σ * random());
  };
  },
  irwinHall: function(m) {
  return function() {
  for (var s = 0, j = 0; j < m; j++) s += Math.random();
  return s / m;
  };
  }
  };
  d3.sum = function(array, f) {
  var s = 0, n = array.length, a, i = -1;
  if (arguments.length === 1) {
  while (++i < n) if (!isNaN(a = +array[i])) s += a;
  } else {
  while (++i < n) if (!isNaN(a = +f.call(array, array[i], i))) s += a;
  }
  return s;
  };
  d3.quantile = function(values, p) {
  var H = (values.length - 1) * p + 1, h = Math.floor(H), v = values[h - 1], e = H - h;
  return e ? v + e * (values[h] - v) : v;
  };
  d3.transpose = function(matrix) {
  return d3.zip.apply(d3, matrix);
  };
  d3.zip = function() {
  if (!(n = arguments.length)) return [];
  for (var i = -1, m = d3.min(arguments, d3_zipLength), zips = new Array(m); ++i < m; ) {
  for (var j = -1, n, zip = zips[i] = new Array(n); ++j < n; ) {
  zip[j] = arguments[j][i];
  }
  }
  return zips;
  };
  d3.bisector = function(f) {
  return {
  left: function(a, x, lo, hi) {
  if (arguments.length < 3) lo = 0;
  if (arguments.length < 4) hi = a.length;
  while (lo < hi) {
  var mid = lo + hi >>> 1;
  if (f.call(a, a[mid], mid) < x) lo = mid + 1; else hi = mid;
  }
  return lo;
  },
  right: function(a, x, lo, hi) {
  if (arguments.length < 3) lo = 0;
  if (arguments.length < 4) hi = a.length;
  while (lo < hi) {
  var mid = lo + hi >>> 1;
  if (x < f.call(a, a[mid], mid)) hi = mid; else lo = mid + 1;
  }
  return lo;
  }
  };
  };
  var d3_bisector = d3.bisector(function(d) {
  return d;
  });
  d3.bisectLeft = d3_bisector.left;
  d3.bisect = d3.bisectRight = d3_bisector.right;
  d3.first = function(array, f) {
  var i = 0, n = array.length, a = array[0], b;
  if (arguments.length === 1) f = d3.ascending;
  while (++i < n) {
  if (f.call(array, a, b = array[i]) > 0) {
  a = b;
  }
  }
  return a;
  };
  d3.last = function(array, f) {
  var i = 0, n = array.length, a = array[0], b;
  if (arguments.length === 1) f = d3.ascending;
  while (++i < n) {
  if (f.call(array, a, b = array[i]) <= 0) {
  a = b;
  }
  }
  return a;
  };
  d3.nest = function() {
  function map(array, depth) {
  if (depth >= keys.length) return rollup ? rollup.call(nest, array) : sortValues ? array.sort(sortValues) : array;
  var i = -1, n = array.length, key = keys[depth++], keyValue, object, valuesByKey = new d3_Map, values, o = {};
  while (++i < n) {
  if (values = valuesByKey.get(keyValue = key(object = array[i]))) {
  values.push(object);
  } else {
  valuesByKey.set(keyValue, [ object ]);
  }
  }
  valuesByKey.forEach(function(keyValue, values) {
  o[keyValue] = map(values, depth);
  });
  return o;
  }
  function entries(map, depth) {
  if (depth >= keys.length) return map;
  var a = [], sortKey = sortKeys[depth++], key;
  for (key in map) {
  a.push({
  key: key,
  values: entries(map[key], depth)
  });
  }
  if (sortKey) a.sort(function(a, b) {
  return sortKey(a.key, b.key);
  });
  return a;
  }
  var nest = {}, keys = [], sortKeys = [], sortValues, rollup;
  nest.map = function(array) {
  return map(array, 0);
  };
  nest.entries = function(array) {
  return entries(map(array, 0), 0);
  };
  nest.key = function(d) {
  keys.push(d);
  return nest;
  };
  nest.sortKeys = function(order) {
  sortKeys[keys.length - 1] = order;
  return nest;
  };
  nest.sortValues = function(order) {
  sortValues = order;
  return nest;
  };
  nest.rollup = function(f) {
  rollup = f;
  return nest;
  };
  return nest;
  };
  d3.keys = function(map) {
  var keys = [];
  for (var key in map) keys.push(key);
  return keys;
  };
  d3.values = function(map) {
  var values = [];
  for (var key in map) values.push(map[key]);
  return values;
  };
  d3.entries = function(map) {
  var entries = [];
  for (var key in map) entries.push({
  key: key,
  value: map[key]
  });
  return entries;
  };
  d3.permute = function(array, indexes) {
  var permutes = [], i = -1, n = indexes.length;
  while (++i < n) permutes[i] = array[indexes[i]];
  return permutes;
  };
  d3.merge = function(arrays) {
  return Array.prototype.concat.apply([], arrays);
  };
  d3.split = function(array, f) {
  var arrays = [], values = [], value, i = -1, n = array.length;
  if (arguments.length < 2) f = d3_splitter;
  while (++i < n) {
  if (f.call(values, value = array[i], i)) {
  values = [];
  } else {
  if (!values.length) arrays.push(values);
  values.push(value);
  }
  }
  return arrays;
  };
  d3.range = function(start, stop, step) {
  if (arguments.length < 3) {
  step = 1;
  if (arguments.length < 2) {
  stop = start;
  start = 0;
  }
  }
  if ((stop - start) / step === Infinity) throw new Error("infinite range");
  var range = [], k = d3_range_integerScale(Math.abs(step)), i = -1, j;
  start *= k, stop *= k, step *= k;
  if (step < 0) while ((j = start + step * ++i) > stop) range.push(j / k); else while ((j = start + step * ++i) < stop) range.push(j / k);
  return range;
  };
  d3.requote = function(s) {
  return s.replace(d3_requote_re, "\\$&");
  };
  var d3_requote_re = /[\\\^\$\*\+\?\|\[\]\(\)\.\{\}]/g;
  d3.round = function(x, n) {
  return n ? Math.round(x * (n = Math.pow(10, n))) / n : Math.round(x);
  };
  d3.xhr = function(url, mime, callback) {
  var req = new XMLHttpRequest;
  if (arguments.length < 3) callback = mime, mime = null; else if (mime && req.overrideMimeType) req.overrideMimeType(mime);
  req.open("GET", url, true);
  if (mime) req.setRequestHeader("Accept", mime);
  req.onreadystatechange = function() {
  if (req.readyState === 4) {
  var s = req.status;
  callback(!s && req.response || s >= 200 && s < 300 || s === 304 ? req : null);
  }
  };
  req.send(null);
  };
  d3.text = function(url, mime, callback) {
  function ready(req) {
  callback(req && req.responseText);
  }
  if (arguments.length < 3) {
  callback = mime;
  mime = null;
  }
  d3.xhr(url, mime, ready);
  };
  d3.json = function(url, callback) {
  d3.text(url, "application/json", function(text) {
  callback(text ? JSON.parse(text) : null);
  });
  };
  d3.html = function(url, callback) {
  d3.text(url, "text/html", function(text) {
  if (text != null) {
  var range = document.createRange();
  range.selectNode(document.body);
  text = range.createContextualFragment(text);
  }
  callback(text);
  });
  };
  d3.xml = function(url, mime, callback) {
  function ready(req) {
  callback(req && req.responseXML);
  }
  if (arguments.length < 3) {
  callback = mime;
  mime = null;
  }
  d3.xhr(url, mime, ready);
  };
  var d3_nsPrefix = {
  svg: "http://www.w3.org/2000/svg",
  xhtml: "http://www.w3.org/1999/xhtml",
  xlink: "http://www.w3.org/1999/xlink",
  xml: "http://www.w3.org/XML/1998/namespace",
  xmlns: "http://www.w3.org/2000/xmlns/"
  };
  d3.ns = {
  prefix: d3_nsPrefix,
  qualify: function(name) {
  var i = name.indexOf(":"), prefix = name;
  if (i >= 0) {
  prefix = name.substring(0, i);
  name = name.substring(i + 1);
  }
  return d3_nsPrefix.hasOwnProperty(prefix) ? {
  space: d3_nsPrefix[prefix],
  local: name
  } : name;
  }
  };
  d3.dispatch = function() {
  var dispatch = new d3_dispatch, i = -1, n = arguments.length;
  while (++i < n) dispatch[arguments[i]] = d3_dispatch_event(dispatch);
  return dispatch;
  };
  d3_dispatch.prototype.on = function(type, listener) {
  var i = type.indexOf("."), name = "";
  if (i > 0) {
  name = type.substring(i + 1);
  type = type.substring(0, i);
  }
  return arguments.length < 2 ? this[type].on(name) : this[type].on(name, listener);
  };
  d3.format = function(specifier) {
  var match = d3_format_re.exec(specifier), fill = match[1] || " ", sign = match[3] || "", zfill = match[5], width = +match[6], comma = match[7], precision = match[8], type = match[9], scale = 1, suffix = "", integer = false;
  if (precision) precision = +precision.substring(1);
  if (zfill) {
  fill = "0";
  if (comma) width -= Math.floor((width - 1) / 4);
  }
  switch (type) {
  case "n":
  comma = true;
  type = "g";
  break;
  case "%":
  scale = 100;
  suffix = "%";
  type = "f";
  break;
  case "p":
  scale = 100;
  suffix = "%";
  type = "r";
  break;
  case "d":
  integer = true;
  precision = 0;
  break;
  case "s":
  scale = -1;
  type = "r";
  break;
  }
  if (type == "r" && !precision) type = "g";
  type = d3_format_types.get(type) || d3_format_typeDefault;
  return function(value) {
  if (integer && value % 1) return "";
  var negative = value < 0 && (value = -value) ? "-" : sign;
  if (scale < 0) {
  var prefix = d3.formatPrefix(value, precision);
  value = prefix.scale(value);
  suffix = prefix.symbol;
  } else {
  value *= scale;
  }
  value = type(value, precision);
  if (zfill) {
  var length = value.length + negative.length;
  if (length < width) value = (new Array(width - length + 1)).join(fill) + value;
  if (comma) value = d3_format_group(value);
  value = negative + value;
  } else {
  if (comma) value = d3_format_group(value);
  value = negative + value;
  var length = value.length;
  if (length < width) value = (new Array(width - length + 1)).join(fill) + value;
  }
  return value + suffix;
  };
  };
  var d3_format_re = /(?:([^{])?([<>=^]))?([+\- ])?(#)?(0)?([0-9]+)?(,)?(\.[0-9]+)?([a-zA-Z%])?/;
  var d3_format_types = d3.map({
  g: function(x, p) {
  return x.toPrecision(p);
  },
  e: function(x, p) {
  return x.toExponential(p);
  },
  f: function(x, p) {
  return x.toFixed(p);
  },
  r: function(x, p) {
  return d3.round(x, p = d3_format_precision(x, p)).toFixed(Math.max(0, Math.min(20, p)));
  }
  });
  var d3_formatPrefixes = [ "y", "z", "a", "f", "p", "n", "μ", "m", "", "k", "M", "G", "T", "P", "E", "Z", "Y" ].map(d3_formatPrefix);
  d3.formatPrefix = function(value, precision) {
  var i = 0;
  if (value) {
  if (value < 0) value *= -1;
  if (precision) value = d3.round(value, d3_format_precision(value, precision));
  i = 1 + Math.floor(1e-12 + Math.log(value) / Math.LN10);
  i = Math.max(-24, Math.min(24, Math.floor((i <= 0 ? i + 1 : i - 1) / 3) * 3));
  }
  return d3_formatPrefixes[8 + i / 3];
  };
  var d3_ease_quad = d3_ease_poly(2), d3_ease_cubic = d3_ease_poly(3), d3_ease_default = function() {
  return d3_ease_identity;
  };
  var d3_ease = d3.map({
  linear: d3_ease_default,
  poly: d3_ease_poly,
  quad: function() {
  return d3_ease_quad;
  },
  cubic: function() {
  return d3_ease_cubic;
  },
  sin: function() {
  return d3_ease_sin;
  },
  exp: function() {
  return d3_ease_exp;
  },
  circle: function() {
  return d3_ease_circle;
  },
  elastic: d3_ease_elastic,
  back: d3_ease_back,
  bounce: function() {
  return d3_ease_bounce;
  }
  });
  var d3_ease_mode = d3.map({
  "in": d3_ease_identity,
  out: d3_ease_reverse,
  "in-out": d3_ease_reflect,
  "out-in": function(f) {
  return d3_ease_reflect(d3_ease_reverse(f));
  }
  });
  d3.ease = function(name) {
  var i = name.indexOf("-"), t = i >= 0 ? name.substring(0, i) : name, m = i >= 0 ? name.substring(i + 1) : "in";
  t = d3_ease.get(t) || d3_ease_default;
  m = d3_ease_mode.get(m) || d3_ease_identity;
  return d3_ease_clamp(m(t.apply(null, Array.prototype.slice.call(arguments, 1))));
  };
  d3.event = null;
  d3.transform = function(string) {
  var g = document.createElementNS(d3.ns.prefix.svg, "g");
  return (d3.transform = function(string) {
  g.setAttribute("transform", string);
  var t = g.transform.baseVal.consolidate();
  return new d3_transform(t ? t.matrix : d3_transformIdentity);
  })(string);
  };
  d3_transform.prototype.toString = function() {
  return "translate(" + this.translate + ")rotate(" + this.rotate + ")skewX(" + this.skew + ")scale(" + this.scale + ")";
  };
  var d3_transformDegrees = 180 / Math.PI, d3_transformIdentity = {
  a: 1,
  b: 0,
  c: 0,
  d: 1,
  e: 0,
  f: 0
  };
  d3.interpolate = function(a, b) {
  var i = d3.interpolators.length, f;
  while (--i >= 0 && !(f = d3.interpolators[i](a, b))) ;
  return f;
  };
  d3.interpolateNumber = function(a, b) {
  b -= a;
  return function(t) {
  return a + b * t;
  };
  };
  d3.interpolateRound = function(a, b) {
  b -= a;
  return function(t) {
  return Math.round(a + b * t);
  };
  };
  d3.interpolateString = function(a, b) {
  var m, i, j, s0 = 0, s1 = 0, s = [], q = [], n, o;
  d3_interpolate_number.lastIndex = 0;
  for (i = 0; m = d3_interpolate_number.exec(b); ++i) {
  if (m.index) s.push(b.substring(s0, s1 = m.index));
  q.push({
  i: s.length,
  x: m[0]
  });
  s.push(null);
  s0 = d3_interpolate_number.lastIndex;
  }
  if (s0 < b.length) s.push(b.substring(s0));
  for (i = 0, n = q.length; (m = d3_interpolate_number.exec(a)) && i < n; ++i) {
  o = q[i];
  if (o.x == m[0]) {
  if (o.i) {
  if (s[o.i + 1] == null) {
  s[o.i - 1] += o.x;
  s.splice(o.i, 1);
  for (j = i + 1; j < n; ++j) q[j].i--;
  } else {
  s[o.i - 1] += o.x + s[o.i + 1];
  s.splice(o.i, 2);
  for (j = i + 1; j < n; ++j) q[j].i -= 2;
  }
  } else {
  if (s[o.i + 1] == null) {
  s[o.i] = o.x;
  } else {
  s[o.i] = o.x + s[o.i + 1];
  s.splice(o.i + 1, 1);
  for (j = i + 1; j < n; ++j) q[j].i--;
  }
  }
  q.splice(i, 1);
  n--;
  i--;
  } else {
  o.x = d3.interpolateNumber(parseFloat(m[0]), parseFloat(o.x));
  }
  }
  while (i < n) {
  o = q.pop();
  if (s[o.i + 1] == null) {
  s[o.i] = o.x;
  } else {
  s[o.i] = o.x + s[o.i + 1];
  s.splice(o.i + 1, 1);
  }
  n--;
  }
  if (s.length === 1) {
  return s[0] == null ? q[0].x : function() {
  return b;
  };
  }
  return function(t) {
  for (i = 0; i < n; ++i) s[(o = q[i]).i] = o.x(t);
  return s.join("");
  };
  };
  d3.interpolateTransform = function(a, b) {
  var s = [], q = [], n, A = d3.transform(a), B = d3.transform(b), ta = A.translate, tb = B.translate, ra = A.rotate, rb = B.rotate, wa = A.skew, wb = B.skew, ka = A.scale, kb = B.scale;
  if (ta[0] != tb[0] || ta[1] != tb[1]) {
  s.push("translate(", null, ",", null, ")");
  q.push({
  i: 1,
  x: d3.interpolateNumber(ta[0], tb[0])
  }, {
  i: 3,
  x: d3.interpolateNumber(ta[1], tb[1])
  });
  } else if (tb[0] || tb[1]) {
  s.push("translate(" + tb + ")");
  } else {
  s.push("");
  }
  if (ra != rb) {
  if (ra - rb > 180) rb += 360; else if (rb - ra > 180) ra += 360;
  q.push({
  i: s.push(s.pop() + "rotate(", null, ")") - 2,
  x: d3.interpolateNumber(ra, rb)
  });
  } else if (rb) {
  s.push(s.pop() + "rotate(" + rb + ")");
  }
  if (wa != wb) {
  q.push({
  i: s.push(s.pop() + "skewX(", null, ")") - 2,
  x: d3.interpolateNumber(wa, wb)
  });
  } else if (wb) {
  s.push(s.pop() + "skewX(" + wb + ")");
  }
  if (ka[0] != kb[0] || ka[1] != kb[1]) {
  n = s.push(s.pop() + "scale(", null, ",", null, ")");
  q.push({
  i: n - 4,
  x: d3.interpolateNumber(ka[0], kb[0])
  }, {
  i: n - 2,
  x: d3.interpolateNumber(ka[1], kb[1])
  });
  } else if (kb[0] != 1 || kb[1] != 1) {
  s.push(s.pop() + "scale(" + kb + ")");
  }
  n = q.length;
  return function(t) {
  var i = -1, o;
  while (++i < n) s[(o = q[i]).i] = o.x(t);
  return s.join("");
  };
  };
  d3.interpolateRgb = function(a, b) {
  a = d3.rgb(a);
  b = d3.rgb(b);
  var ar = a.r, ag = a.g, ab = a.b, br = b.r - ar, bg = b.g - ag, bb = b.b - ab;
  return function(t) {
  return "#" + d3_rgb_hex(Math.round(ar + br * t)) + d3_rgb_hex(Math.round(ag + bg * t)) + d3_rgb_hex(Math.round(ab + bb * t));
  };
  };
  d3.interpolateHsl = function(a, b) {
  a = d3.hsl(a);
  b = d3.hsl(b);
  var h0 = a.h, s0 = a.s, l0 = a.l, h1 = b.h - h0, s1 = b.s - s0, l1 = b.l - l0;
  if (h1 > 180) h1 -= 360; else if (h1 < -180) h1 += 360;
  return function(t) {
  return d3_hsl_rgb(h0 + h1 * t, s0 + s1 * t, l0 + l1 * t) + "";
  };
  };
  d3.interpolateLab = function(a, b) {
  a = d3.lab(a);
  b = d3.lab(b);
  var al = a.l, aa = a.a, ab = a.b, bl = b.l - al, ba = b.a - aa, bb = b.b - ab;
  return function(t) {
  return d3_lab_rgb(al + bl * t, aa + ba * t, ab + bb * t) + "";
  };
  };
  d3.interpolateHcl = function(a, b) {
  a = d3.hcl(a);
  b = d3.hcl(b);
  var ah = a.h, ac = a.c, al = a.l, bh = b.h - ah, bc = b.c - ac, bl = b.l - al;
  if (bh > 180) bh -= 360; else if (bh < -180) bh += 360;
  return function(t) {
  return d3_hcl_lab(ah + bh * t, ac + bc * t, al + bl * t) + "";
  };
  };
  d3.interpolateArray = function(a, b) {
  var x = [], c = [], na = a.length, nb = b.length, n0 = Math.min(a.length, b.length), i;
  for (i = 0; i < n0; ++i) x.push(d3.interpolate(a[i], b[i]));
  for (; i < na; ++i) c[i] = a[i];
  for (; i < nb; ++i) c[i] = b[i];
  return function(t) {
  for (i = 0; i < n0; ++i) c[i] = x[i](t);
  return c;
  };
  };
  d3.interpolateObject = function(a, b) {
  var i = {}, c = {}, k;
  for (k in a) {
  if (k in b) {
  i[k] = d3_interpolateByName(k)(a[k], b[k]);
  } else {
  c[k] = a[k];
  }
  }
  for (k in b) {
  if (!(k in a)) {
  c[k] = b[k];
  }
  }
  return function(t) {
  for (k in i) c[k] = i[k](t);
  return c;
  };
  };
  var d3_interpolate_number = /[-+]?(?:\d+\.?\d*|\.?\d+)(?:[eE][-+]?\d+)?/g;
  d3.interpolators = [ d3.interpolateObject, function(a, b) {
  return b instanceof Array && d3.interpolateArray(a, b);
  }, function(a, b) {
  return (typeof a === "string" || typeof b === "string") && d3.interpolateString(a + "", b + "");
  }, function(a, b) {
  return (typeof b === "string" ? d3_rgb_names.has(b) || /^(#|rgb\(|hsl\()/.test(b) : b instanceof d3_Color) && d3.interpolateRgb(a, b);
  }, function(a, b) {
  return !isNaN(a = +a) && !isNaN(b = +b) && d3.interpolateNumber(a, b);
  } ];
  d3_Color.prototype.toString = function() {
  return this.rgb() + "";
  };
  d3.rgb = function(r, g, b) {
  return arguments.length === 1 ? r instanceof d3_Rgb ? d3_rgb(r.r, r.g, r.b) : d3_rgb_parse("" + r, d3_rgb, d3_hsl_rgb) : d3_rgb(~~r, ~~g, ~~b);
  };
  var d3_rgbPrototype = d3_Rgb.prototype = new d3_Color;
  d3_rgbPrototype.brighter = function(k) {
  k = Math.pow(.7, arguments.length ? k : 1);
  var r = this.r, g = this.g, b = this.b, i = 30;
  if (!r && !g && !b) return d3_rgb(i, i, i);
  if (r && r < i) r = i;
  if (g && g < i) g = i;
  if (b && b < i) b = i;
  return d3_rgb(Math.min(255, Math.floor(r / k)), Math.min(255, Math.floor(g / k)), Math.min(255, Math.floor(b / k)));
  };
  d3_rgbPrototype.darker = function(k) {
  k = Math.pow(.7, arguments.length ? k : 1);
  return d3_rgb(Math.floor(k * this.r), Math.floor(k * this.g), Math.floor(k * this.b));
  };
  d3_rgbPrototype.hsl = function() {
  return d3_rgb_hsl(this.r, this.g, this.b);
  };
  d3_rgbPrototype.toString = function() {
  return "#" + d3_rgb_hex(this.r) + d3_rgb_hex(this.g) + d3_rgb_hex(this.b);
  };
  var d3_rgb_names = d3.map({
  aliceblue: "#f0f8ff",
  antiquewhite: "#faebd7",
  aqua: "#00ffff",
  aquamarine: "#7fffd4",
  azure: "#f0ffff",
  beige: "#f5f5dc",
  bisque: "#ffe4c4",
  black: "#000000",
  blanchedalmond: "#ffebcd",
  blue: "#0000ff",
  blueviolet: "#8a2be2",
  brown: "#a52a2a",
  burlywood: "#deb887",
  cadetblue: "#5f9ea0",
  chartreuse: "#7fff00",
  chocolate: "#d2691e",
  coral: "#ff7f50",
  cornflowerblue: "#6495ed",
  cornsilk: "#fff8dc",
  crimson: "#dc143c",
  cyan: "#00ffff",
  darkblue: "#00008b",
  darkcyan: "#008b8b",
  darkgoldenrod: "#b8860b",
  darkgray: "#a9a9a9",
  darkgreen: "#006400",
  darkgrey: "#a9a9a9",
  darkkhaki: "#bdb76b",
  darkmagenta: "#8b008b",
  darkolivegreen: "#556b2f",
  darkorange: "#ff8c00",
  darkorchid: "#9932cc",
  darkred: "#8b0000",
  darksalmon: "#e9967a",
  darkseagreen: "#8fbc8f",
  darkslateblue: "#483d8b",
  darkslategray: "#2f4f4f",
  darkslategrey: "#2f4f4f",
  darkturquoise: "#00ced1",
  darkviolet: "#9400d3",
  deeppink: "#ff1493",
  deepskyblue: "#00bfff",
  dimgray: "#696969",
  dimgrey: "#696969",
  dodgerblue: "#1e90ff",
  firebrick: "#b22222",
  floralwhite: "#fffaf0",
  forestgreen: "#228b22",
  fuchsia: "#ff00ff",
  gainsboro: "#dcdcdc",
  ghostwhite: "#f8f8ff",
  gold: "#ffd700",
  goldenrod: "#daa520",
  gray: "#808080",
  green: "#008000",
  greenyellow: "#adff2f",
  grey: "#808080",
  honeydew: "#f0fff0",
  hotpink: "#ff69b4",
  indianred: "#cd5c5c",
  indigo: "#4b0082",
  ivory: "#fffff0",
  khaki: "#f0e68c",
  lavender: "#e6e6fa",
  lavenderblush: "#fff0f5",
  lawngreen: "#7cfc00",
  lemonchiffon: "#fffacd",
  lightblue: "#add8e6",
  lightcoral: "#f08080",
  lightcyan: "#e0ffff",
  lightgoldenrodyellow: "#fafad2",
  lightgray: "#d3d3d3",
  lightgreen: "#90ee90",
  lightgrey: "#d3d3d3",
  lightpink: "#ffb6c1",
  lightsalmon: "#ffa07a",
  lightseagreen: "#20b2aa",
  lightskyblue: "#87cefa",
  lightslategray: "#778899",
  lightslategrey: "#778899",
  lightsteelblue: "#b0c4de",
  lightyellow: "#ffffe0",
  lime: "#00ff00",
  limegreen: "#32cd32",
  linen: "#faf0e6",
  magenta: "#ff00ff",
  maroon: "#800000",
  mediumaquamarine: "#66cdaa",
  mediumblue: "#0000cd",
  mediumorchid: "#ba55d3",
  mediumpurple: "#9370db",
  mediumseagreen: "#3cb371",
  mediumslateblue: "#7b68ee",
  mediumspringgreen: "#00fa9a",
  mediumturquoise: "#48d1cc",
  mediumvioletred: "#c71585",
  midnightblue: "#191970",
  mintcream: "#f5fffa",
  mistyrose: "#ffe4e1",
  moccasin: "#ffe4b5",
  navajowhite: "#ffdead",
  navy: "#000080",
  oldlace: "#fdf5e6",
  olive: "#808000",
  olivedrab: "#6b8e23",
  orange: "#ffa500",
  orangered: "#ff4500",
  orchid: "#da70d6",
  palegoldenrod: "#eee8aa",
  palegreen: "#98fb98",
  paleturquoise: "#afeeee",
  palevioletred: "#db7093",
  papayawhip: "#ffefd5",
  peachpuff: "#ffdab9",
  peru: "#cd853f",
  pink: "#ffc0cb",
  plum: "#dda0dd",
  powderblue: "#b0e0e6",
  purple: "#800080",
  red: "#ff0000",
  rosybrown: "#bc8f8f",
  royalblue: "#4169e1",
  saddlebrown: "#8b4513",
  salmon: "#fa8072",
  sandybrown: "#f4a460",
  seagreen: "#2e8b57",
  seashell: "#fff5ee",
  sienna: "#a0522d",
  silver: "#c0c0c0",
  skyblue: "#87ceeb",
  slateblue: "#6a5acd",
  slategray: "#708090",
  slategrey: "#708090",
  snow: "#fffafa",
  springgreen: "#00ff7f",
  steelblue: "#4682b4",
  tan: "#d2b48c",
  teal: "#008080",
  thistle: "#d8bfd8",
  tomato: "#ff6347",
  turquoise: "#40e0d0",
  violet: "#ee82ee",
  wheat: "#f5deb3",
  white: "#ffffff",
  whitesmoke: "#f5f5f5",
  yellow: "#ffff00",
  yellowgreen: "#9acd32"
  });
  d3_rgb_names.forEach(function(key, value) {
  d3_rgb_names.set(key, d3_rgb_parse(value, d3_rgb, d3_hsl_rgb));
  });
  d3.hsl = function(h, s, l) {
  return arguments.length === 1 ? h instanceof d3_Hsl ? d3_hsl(h.h, h.s, h.l) : d3_rgb_parse("" + h, d3_rgb_hsl, d3_hsl) : d3_hsl(+h, +s, +l);
  };
  var d3_hslPrototype = d3_Hsl.prototype = new d3_Color;
  d3_hslPrototype.brighter = function(k) {
  k = Math.pow(.7, arguments.length ? k : 1);
  return d3_hsl(this.h, this.s, this.l / k);
  };
  d3_hslPrototype.darker = function(k) {
  k = Math.pow(.7, arguments.length ? k : 1);
  return d3_hsl(this.h, this.s, k * this.l);
  };
  d3_hslPrototype.rgb = function() {
  return d3_hsl_rgb(this.h, this.s, this.l);
  };
  d3.hcl = function(h, c, l) {
  return arguments.length === 1 ? h instanceof d3_Hcl ? d3_hcl(h.h, h.c, h.l) : h instanceof d3_Lab ? d3_lab_hcl(h.l, h.a, h.b) : d3_lab_hcl((h = d3_rgb_lab((h = d3.rgb(h)).r, h.g, h.b)).l, h.a, h.b) : d3_hcl(+h, +c, +l);
  };
  var d3_hclPrototype = d3_Hcl.prototype = new d3_Color;
  d3_hclPrototype.brighter = function(k) {
  return d3_hcl(this.h, this.c, Math.min(100, this.l + d3_lab_K * (arguments.length ? k : 1)));
  };
  d3_hclPrototype.darker = function(k) {
  return d3_hcl(this.h, this.c, Math.max(0, this.l - d3_lab_K * (arguments.length ? k : 1)));
  };
  d3_hclPrototype.rgb = function() {
  return d3_hcl_lab(this.h, this.c, this.l).rgb();
  };
  d3.lab = function(l, a, b) {
  return arguments.length === 1 ? l instanceof d3_Lab ? d3_lab(l.l, l.a, l.b) : l instanceof d3_Hcl ? d3_hcl_lab(l.l, l.c, l.h) : d3_rgb_lab((l = d3.rgb(l)).r, l.g, l.b) : d3_lab(+l, +a, +b);
  };
  var d3_lab_K = 18;
  var d3_lab_X = .95047, d3_lab_Y = 1, d3_lab_Z = 1.08883;
  var d3_labPrototype = d3_Lab.prototype = new d3_Color;
  d3_labPrototype.brighter = function(k) {
  return d3_lab(Math.min(100, this.l + d3_lab_K * (arguments.length ? k : 1)), this.a, this.b);
  };
  d3_labPrototype.darker = function(k) {
  return d3_lab(Math.max(0, this.l - d3_lab_K * (arguments.length ? k : 1)), this.a, this.b);
  };
  d3_labPrototype.rgb = function() {
  return d3_lab_rgb(this.l, this.a, this.b);
  };
  var d3_select = function(s, n) {
  return n.querySelector(s);
  }, d3_selectAll = function(s, n) {
  return n.querySelectorAll(s);
  }, d3_selectRoot = document.documentElement, d3_selectMatcher = d3_selectRoot.matchesSelector || d3_selectRoot.webkitMatchesSelector || d3_selectRoot.mozMatchesSelector || d3_selectRoot.msMatchesSelector || d3_selectRoot.oMatchesSelector, d3_selectMatches = function(n, s) {
  return d3_selectMatcher.call(n, s);
  };
  if (typeof Sizzle === "function") {
  d3_select = function(s, n) {
  return Sizzle(s, n)[0] || null;
  };
  d3_selectAll = function(s, n) {
  return Sizzle.uniqueSort(Sizzle(s, n));
  };
  d3_selectMatches = Sizzle.matchesSelector;
  }
  var d3_selectionPrototype = [];
  d3.selection = function() {
  return d3_selectionRoot;
  };
  d3.selection.prototype = d3_selectionPrototype;
  d3_selectionPrototype.select = function(selector) {
  var subgroups = [], subgroup, subnode, group, node;
  if (typeof selector !== "function") selector = d3_selection_selector(selector);
  for (var j = -1, m = this.length; ++j < m; ) {
  subgroups.push(subgroup = []);
  subgroup.parentNode = (group = this[j]).parentNode;
  for (var i = -1, n = group.length; ++i < n; ) {
  if (node = group[i]) {
  subgroup.push(subnode = selector.call(node, node.__data__, i));
  if (subnode && "__data__" in node) subnode.__data__ = node.__data__;
  } else {
  subgroup.push(null);
  }
  }
  }
  return d3_selection(subgroups);
  };
  d3_selectionPrototype.selectAll = function(selector) {
  var subgroups = [], subgroup, node;
  if (typeof selector !== "function") selector = d3_selection_selectorAll(selector);
  for (var j = -1, m = this.length; ++j < m; ) {
  for (var group = this[j], i = -1, n = group.length; ++i < n; ) {
  if (node = group[i]) {
  subgroups.push(subgroup = d3_array(selector.call(node, node.__data__, i)));
  subgroup.parentNode = node;
  }
  }
  }
  return d3_selection(subgroups);
  };
  d3_selectionPrototype.attr = function(name, value) {
  if (arguments.length < 2) {
  if (typeof name === "string") {
  var node = this.node();
  name = d3.ns.qualify(name);
  return name.local ? node.getAttributeNS(name.space, name.local) : node.getAttribute(name);
  }
  for (value in name) this.each(d3_selection_attr(value, name[value]));
  return this;
  }
  return this.each(d3_selection_attr(name, value));
  };
  d3_selectionPrototype.classed = function(name, value) {
  if (arguments.length < 2) {
  if (typeof name === "string") {
  var node = this.node(), n = (name = name.trim().split(/^|\s+/g)).length, i = -1;
  if (value = node.classList) {
  while (++i < n) if (!value.contains(name[i])) return false;
  } else {
  value = node.className;
  if (value.baseVal != null) value = value.baseVal;
  while (++i < n) if (!d3_selection_classedRe(name[i]).test(value)) return false;
  }
  return true;
  }
  for (value in name) this.each(d3_selection_classed(value, name[value]));
  return this;
  }
  return this.each(d3_selection_classed(name, value));
  };
  d3_selectionPrototype.style = function(name, value, priority) {
  var n = arguments.length;
  if (n < 3) {
  if (typeof name !== "string") {
  if (n < 2) value = "";
  for (priority in name) this.each(d3_selection_style(priority, name[priority], value));
  return this;
  }
  if (n < 2) return window.getComputedStyle(this.node(), null).getPropertyValue(name);
  priority = "";
  }
  return this.each(d3_selection_style(name, value, priority));
  };
  d3_selectionPrototype.property = function(name, value) {
  if (arguments.length < 2) {
  if (typeof name === "string") return this.node()[name];
  for (value in name) this.each(d3_selection_property(value, name[value]));
  return this;
  }
  return this.each(d3_selection_property(name, value));
  };
  d3_selectionPrototype.text = function(value) {
  return arguments.length < 1 ? this.node().textContent : this.each(typeof value === "function" ? function() {
  var v = value.apply(this, arguments);
  this.textContent = v == null ? "" : v;
  } : value == null ? function() {
  this.textContent = "";
  } : function() {
  this.textContent = value;
  });
  };
  d3_selectionPrototype.html = function(value) {
  return arguments.length < 1 ? this.node().innerHTML : this.each(typeof value === "function" ? function() {
  var v = value.apply(this, arguments);
  this.innerHTML = v == null ? "" : v;
  } : value == null ? function() {
  this.innerHTML = "";
  } : function() {
  this.innerHTML = value;
  });
  };
  d3_selectionPrototype.append = function(name) {
  function append() {
  return this.appendChild(document.createElementNS(this.namespaceURI, name));
  }
  function appendNS() {
  return this.appendChild(document.createElementNS(name.space, name.local));
  }
  name = d3.ns.qualify(name);
  return this.select(name.local ? appendNS : append);
  };
  d3_selectionPrototype.insert = function(name, before) {
  function insert() {
  return this.insertBefore(document.createElementNS(this.namespaceURI, name), d3_select(before, this));
  }
  function insertNS() {
  return this.insertBefore(document.createElementNS(name.space, name.local), d3_select(before, this));
  }
  name = d3.ns.qualify(name);
  return this.select(name.local ? insertNS : insert);
  };
  d3_selectionPrototype.remove = function() {
  return this.each(function() {
  var parent = this.parentNode;
  if (parent) parent.removeChild(this);
  });
  };
  d3_selectionPrototype.data = function(value, key) {
  function bind(group, groupData) {
  var i, n = group.length, m = groupData.length, n0 = Math.min(n, m), n1 = Math.max(n, m), updateNodes = [], enterNodes = [], exitNodes = [], node, nodeData;
  if (key) {
  var nodeByKeyValue = new d3_Map, keyValues = [], keyValue, j = groupData.length;
  for (i = -1; ++i < n; ) {
  keyValue = key.call(node = group[i], node.__data__, i);
  if (nodeByKeyValue.has(keyValue)) {
  exitNodes[j++] = node;
  } else {
  nodeByKeyValue.set(keyValue, node);
  }
  keyValues.push(keyValue);
  }
  for (i = -1; ++i < m; ) {
  keyValue = key.call(groupData, nodeData = groupData[i], i);
  if (nodeByKeyValue.has(keyValue)) {
  updateNodes[i] = node = nodeByKeyValue.get(keyValue);
  node.__data__ = nodeData;
  enterNodes[i] = exitNodes[i] = null;
  } else {
  enterNodes[i] = d3_selection_dataNode(nodeData);
  updateNodes[i] = exitNodes[i] = null;
  }
  nodeByKeyValue.remove(keyValue);
  }
  for (i = -1; ++i < n; ) {
  if (nodeByKeyValue.has(keyValues[i])) {
  exitNodes[i] = group[i];
  }
  }
  } else {
  for (i = -1; ++i < n0; ) {
  node = group[i];
  nodeData = groupData[i];
  if (node) {
  node.__data__ = nodeData;
  updateNodes[i] = node;
  enterNodes[i] = exitNodes[i] = null;
  } else {
  enterNodes[i] = d3_selection_dataNode(nodeData);
  updateNodes[i] = exitNodes[i] = null;
  }
  }
  for (; i < m; ++i) {
  enterNodes[i] = d3_selection_dataNode(groupData[i]);
  updateNodes[i] = exitNodes[i] = null;
  }
  for (; i < n1; ++i) {
  exitNodes[i] = group[i];
  enterNodes[i] = updateNodes[i] = null;
  }
  }
  enterNodes.update = updateNodes;
  enterNodes.parentNode = updateNodes.parentNode = exitNodes.parentNode = group.parentNode;
  enter.push(enterNodes);
  update.push(updateNodes);
  exit.push(exitNodes);
  }
  var i = -1, n = this.length, group, node;
  if (!arguments.length) {
  value = new Array(n = (group = this[0]).length);
  while (++i < n) {
  if (node = group[i]) {
  value[i] = node.__data__;
  }
  }
  return value;
  }
  var enter = d3_selection_enter([]), update = d3_selection([]), exit = d3_selection([]);
  if (typeof value === "function") {
  while (++i < n) {
  bind(group = this[i], value.call(group, group.parentNode.__data__, i));
  }
  } else {
  while (++i < n) {
  bind(group = this[i], value);
  }
  }
  update.enter = function() {
  return enter;
  };
  update.exit = function() {
  return exit;
  };
  return update;
  };
  d3_selectionPrototype.datum = d3_selectionPrototype.map = function(value) {
  return arguments.length < 1 ? this.property("__data__") : this.property("__data__", value);
  };
  d3_selectionPrototype.filter = function(filter) {
  var subgroups = [], subgroup, group, node;
  if (typeof filter !== "function") filter = d3_selection_filter(filter);
  for (var j = 0, m = this.length; j < m; j++) {
  subgroups.push(subgroup = []);
  subgroup.parentNode = (group = this[j]).parentNode;
  for (var i = 0, n = group.length; i < n; i++) {
  if ((node = group[i]) && filter.call(node, node.__data__, i)) {
  subgroup.push(node);
  }
  }
  }
  return d3_selection(subgroups);
  };
  d3_selectionPrototype.order = function() {
  for (var j = -1, m = this.length; ++j < m; ) {
  for (var group = this[j], i = group.length - 1, next = group[i], node; --i >= 0; ) {
  if (node = group[i]) {
  if (next && next !== node.nextSibling) next.parentNode.insertBefore(node, next);
  next = node;
  }
  }
  }
  return this;
  };
  d3_selectionPrototype.sort = function(comparator) {
  comparator = d3_selection_sortComparator.apply(this, arguments);
  for (var j = -1, m = this.length; ++j < m; ) this[j].sort(comparator);
  return this.order();
  };
  d3_selectionPrototype.on = function(type, listener, capture) {
  var n = arguments.length;
  if (n < 3) {
  if (typeof type !== "string") {
  if (n < 2) listener = false;
  for (capture in type) this.each(d3_selection_on(capture, type[capture], listener));
  return this;
  }
  if (n < 2) return (n = this.node()["__on" + type]) && n._;
  capture = false;
  }
  return this.each(d3_selection_on(type, listener, capture));
  };
  d3_selectionPrototype.each = function(callback) {
  return d3_selection_each(this, function(node, i, j) {
  callback.call(node, node.__data__, i, j);
  });
  };
  d3_selectionPrototype.call = function(callback) {
  callback.apply(this, (arguments[0] = this, arguments));
  return this;
  };
  d3_selectionPrototype.empty = function() {
  return !this.node();
  };
  d3_selectionPrototype.node = function(callback) {
  for (var j = 0, m = this.length; j < m; j++) {
  for (var group = this[j], i = 0, n = group.length; i < n; i++) {
  var node = group[i];
  if (node) return node;
  }
  }
  return null;
  };
  d3_selectionPrototype.transition = function() {
  var subgroups = [], subgroup, node;
  for (var j = -1, m = this.length; ++j < m; ) {
  subgroups.push(subgroup = []);
  for (var group = this[j], i = -1, n = group.length; ++i < n; ) {
  subgroup.push((node = group[i]) ? {
  node: node,
  delay: d3_transitionDelay,
  duration: d3_transitionDuration
  } : null);
  }
  }
  return d3_transition(subgroups, d3_transitionId || ++d3_transitionNextId, Date.now());
  };
  var d3_selectionRoot = d3_selection([ [ document ] ]);
  d3_selectionRoot[0].parentNode = d3_selectRoot;
  d3.select = function(selector) {
  return typeof selector === "string" ? d3_selectionRoot.select(selector) : d3_selection([ [ selector ] ]);
  };
  d3.selectAll = function(selector) {
  return typeof selector === "string" ? d3_selectionRoot.selectAll(selector) : d3_selection([ d3_array(selector) ]);
  };
  var d3_selection_enterPrototype = [];
  d3.selection.enter = d3_selection_enter;
  d3.selection.enter.prototype = d3_selection_enterPrototype;
  d3_selection_enterPrototype.append = d3_selectionPrototype.append;
  d3_selection_enterPrototype.insert = d3_selectionPrototype.insert;
  d3_selection_enterPrototype.empty = d3_selectionPrototype.empty;
  d3_selection_enterPrototype.node = d3_selectionPrototype.node;
  d3_selection_enterPrototype.select = function(selector) {
  var subgroups = [], subgroup, subnode, upgroup, group, node;
  for (var j = -1, m = this.length; ++j < m; ) {
  upgroup = (group = this[j]).update;
  subgroups.push(subgroup = []);
  subgroup.parentNode = group.parentNode;
  for (var i = -1, n = group.length; ++i < n; ) {
  if (node = group[i]) {
  subgroup.push(upgroup[i] = subnode = selector.call(group.parentNode, node.__data__, i));
  subnode.__data__ = node.__data__;
  } else {
  subgroup.push(null);
  }
  }
  }
  return d3_selection(subgroups);
  };
  var d3_transitionPrototype = [], d3_transitionNextId = 0, d3_transitionId = 0, d3_transitionDefaultDelay = 0, d3_transitionDefaultDuration = 250, d3_transitionDefaultEase = d3.ease("cubic-in-out"), d3_transitionDelay = d3_transitionDefaultDelay, d3_transitionDuration = d3_transitionDefaultDuration, d3_transitionEase = d3_transitionDefaultEase;
  d3_transitionPrototype.call = d3_selectionPrototype.call;
  d3.transition = function(selection) {
  return arguments.length ? d3_transitionId ? selection.transition() : selection : d3_selectionRoot.transition();
  };
  d3.transition.prototype = d3_transitionPrototype;
  d3_transitionPrototype.select = function(selector) {
  var subgroups = [], subgroup, subnode, node;
  if (typeof selector !== "function") selector = d3_selection_selector(selector);
  for (var j = -1, m = this.length; ++j < m; ) {
  subgroups.push(subgroup = []);
  for (var group = this[j], i = -1, n = group.length; ++i < n; ) {
  if ((node = group[i]) && (subnode = selector.call(node.node, node.node.__data__, i))) {
  if ("__data__" in node.node) subnode.__data__ = node.node.__data__;
  subgroup.push({
  node: subnode,
  delay: node.delay,
  duration: node.duration
  });
  } else {
  subgroup.push(null);
  }
  }
  }
  return d3_transition(subgroups, this.id, this.time).ease(this.ease());
  };
  d3_transitionPrototype.selectAll = function(selector) {
  var subgroups = [], subgroup, subnodes, node;
  if (typeof selector !== "function") selector = d3_selection_selectorAll(selector);
  for (var j = -1, m = this.length; ++j < m; ) {
  for (var group = this[j], i = -1, n = group.length; ++i < n; ) {
  if (node = group[i]) {
  subnodes = selector.call(node.node, node.node.__data__, i);
  subgroups.push(subgroup = []);
  for (var k = -1, o = subnodes.length; ++k < o; ) {
  subgroup.push({
  node: subnodes[k],
  delay: node.delay,
  duration: node.duration
  });
  }
  }
  }
  }
  return d3_transition(subgroups, this.id, this.time).ease(this.ease());
  };
  d3_transitionPrototype.filter = function(filter) {
  var subgroups = [], subgroup, group, node;
  if (typeof filter !== "function") filter = d3_selection_filter(filter);
  for (var j = 0, m = this.length; j < m; j++) {
  subgroups.push(subgroup = []);
  for (var group = this[j], i = 0, n = group.length; i < n; i++) {
  if ((node = group[i]) && filter.call(node.node, node.node.__data__, i)) {
  subgroup.push(node);
  }
  }
  }
  return d3_transition(subgroups, this.id, this.time).ease(this.ease());
  };
  d3_transitionPrototype.attr = function(name, value) {
  if (arguments.length < 2) {
  for (value in name) this.attrTween(value, d3_tweenByName(name[value], value));
  return this;
  }
  return this.attrTween(name, d3_tweenByName(value, name));
  };
  d3_transitionPrototype.attrTween = function(nameNS, tween) {
  function attrTween(d, i) {
  var f = tween.call(this, d, i, this.getAttribute(name));
  return f === d3_tweenRemove ? (this.removeAttribute(name), null) : f && function(t) {
  this.setAttribute(name, f(t));
  };
  }
  function attrTweenNS(d, i) {
  var f = tween.call(this, d, i, this.getAttributeNS(name.space, name.local));
  return f === d3_tweenRemove ? (this.removeAttributeNS(name.space, name.local), null) : f && function(t) {
  this.setAttributeNS(name.space, name.local, f(t));
  };
  }
  var name = d3.ns.qualify(nameNS);
  return this.tween("attr." + nameNS, name.local ? attrTweenNS : attrTween);
  };
  d3_transitionPrototype.style = function(name, value, priority) {
  var n = arguments.length;
  if (n < 3) {
  if (typeof name !== "string") {
  if (n < 2) value = "";
  for (priority in name) this.styleTween(priority, d3_tweenByName(name[priority], priority), value);
  return this;
  }
  priority = "";
  }
  return this.styleTween(name, d3_tweenByName(value, name), priority);
  };
  d3_transitionPrototype.styleTween = function(name, tween, priority) {
  if (arguments.length < 3) priority = "";
  return this.tween("style." + name, function(d, i) {
  var f = tween.call(this, d, i, window.getComputedStyle(this, null).getPropertyValue(name));
  return f === d3_tweenRemove ? (this.style.removeProperty(name), null) : f && function(t) {
  this.style.setProperty(name, f(t), priority);
  };
  });
  };
  d3_transitionPrototype.text = function(value) {
  return this.tween("text", function(d, i) {
  this.textContent = typeof value === "function" ? value.call(this, d, i) : value;
  });
  };
  d3_transitionPrototype.remove = function() {
  return this.each("end.transition", function() {
  var p;
  if (!this.__transition__ && (p = this.parentNode)) p.removeChild(this);
  });
  };
  d3_transitionPrototype.delay = function(value) {
  return d3_selection_each(this, typeof value === "function" ? function(node, i, j) {
  node.delay = value.call(node = node.node, node.__data__, i, j) | 0;
  } : (value = value | 0, function(node) {
  node.delay = value;
  }));
  };
  d3_transitionPrototype.duration = function(value) {
  return d3_selection_each(this, typeof value === "function" ? function(node, i, j) {
  node.duration = Math.max(1, value.call(node = node.node, node.__data__, i, j) | 0);
  } : (value = Math.max(1, value | 0), function(node) {
  node.duration = value;
  }));
  };
  d3_transitionPrototype.transition = function() {
  return this.select(d3_this);
  };
  d3.tween = function(b, interpolate) {
  function tweenFunction(d, i, a) {
  var v = b.call(this, d, i);
  return v == null ? a != "" && d3_tweenRemove : a != v && interpolate(a, v + "");
  }
  function tweenString(d, i, a) {
  return a != b && interpolate(a, b);
  }
  return typeof b === "function" ? tweenFunction : b == null ? d3_tweenNull : (b += "", tweenString);
  };
  var d3_tweenRemove = {};
  var d3_timer_id = 0, d3_timer_byId = {}, d3_timer_queue = null, d3_timer_interval, d3_timer_timeout;
  d3.timer = function(callback, delay, then) {
  if (arguments.length < 3) {
  if (arguments.length < 2) delay = 0; else if (!isFinite(delay)) return;
  then = Date.now();
  }
  var timer = d3_timer_byId[callback.id];
  if (timer && timer.callback === callback) {
  timer.then = then;
  timer.delay = delay;
  } else d3_timer_byId[callback.id = ++d3_timer_id] = d3_timer_queue = {
  callback: callback,
  then: then,
  delay: delay,
  next: d3_timer_queue
  };
  if (!d3_timer_interval) {
  d3_timer_timeout = clearTimeout(d3_timer_timeout);
  d3_timer_interval = 1;
  d3_timer_frame(d3_timer_step);
  }
  };
  d3.timer.flush = function() {
  var elapsed, now = Date.now(), t1 = d3_timer_queue;
  while (t1) {
  elapsed = now - t1.then;
  if (!t1.delay) t1.flush = t1.callback(elapsed);
  t1 = t1.next;
  }
  d3_timer_flush();
  };
  var d3_timer_frame = window.requestAnimationFrame || window.webkitRequestAnimationFrame || window.mozRequestAnimationFrame || window.oRequestAnimationFrame || window.msRequestAnimationFrame || function(callback) {
  setTimeout(callback, 17);
  };
  d3.mouse = function(container) {
  return d3_mousePoint(container, d3_eventSource());
  };
  var d3_mouse_bug44083 = /WebKit/.test(navigator.userAgent) ? -1 : 0;
  d3.touches = function(container, touches) {
  if (arguments.length < 2) touches = d3_eventSource().touches;
  return touches ? d3_array(touches).map(function(touch) {
  var point = d3_mousePoint(container, touch);
  point.identifier = touch.identifier;
  return point;
  }) : [];
  };
  d3.scale = {};
  d3.scale.linear = function() {
  return d3_scale_linear([ 0, 1 ], [ 0, 1 ], d3.interpolate, false);
  };
  d3.scale.log = function() {
  return d3_scale_log(d3.scale.linear(), d3_scale_logp);
  };
  var d3_scale_logFormat = d3.format(".0e");
  d3_scale_logp.pow = function(x) {
  return Math.pow(10, x);
  };
  d3_scale_logn.pow = function(x) {
  return -Math.pow(10, -x);
  };
  d3.scale.pow = function() {
  return d3_scale_pow(d3.scale.linear(), 1);
  };
  d3.scale.sqrt = function() {
  return d3.scale.pow().exponent(.5);
  };
  d3.scale.ordinal = function() {
  return d3_scale_ordinal([], {
  t: "range",
  a: [ [] ]
  });
  };
  d3.scale.category10 = function() {
  return d3.scale.ordinal().range(d3_category10);
  };
  d3.scale.category20 = function() {
  return d3.scale.ordinal().range(d3_category20);
  };
  d3.scale.category20b = function() {
  return d3.scale.ordinal().range(d3_category20b);
  };
  d3.scale.category20c = function() {
  return d3.scale.ordinal().range(d3_category20c);
  };
  var d3_category10 = [ "#1f77b4", "#ff7f0e", "#2ca02c", "#d62728", "#9467bd", "#8c564b", "#e377c2", "#7f7f7f", "#bcbd22", "#17becf" ];
  var d3_category20 = [ "#1f77b4", "#aec7e8", "#ff7f0e", "#ffbb78", "#2ca02c", "#98df8a", "#d62728", "#ff9896", "#9467bd", "#c5b0d5", "#8c564b", "#c49c94", "#e377c2", "#f7b6d2", "#7f7f7f", "#c7c7c7", "#bcbd22", "#dbdb8d", "#17becf", "#9edae5" ];
  var d3_category20b = [ "#393b79", "#5254a3", "#6b6ecf", "#9c9ede", "#637939", "#8ca252", "#b5cf6b", "#cedb9c", "#8c6d31", "#bd9e39", "#e7ba52", "#e7cb94", "#843c39", "#ad494a", "#d6616b", "#e7969c", "#7b4173", "#a55194", "#ce6dbd", "#de9ed6" ];
  var d3_category20c = [ "#3182bd", "#6baed6", "#9ecae1", "#c6dbef", "#e6550d", "#fd8d3c", "#fdae6b", "#fdd0a2", "#31a354", "#74c476", "#a1d99b", "#c7e9c0", "#756bb1", "#9e9ac8", "#bcbddc", "#dadaeb", "#636363", "#969696", "#bdbdbd", "#d9d9d9" ];
  d3.scale.quantile = function() {
  return d3_scale_quantile([], []);
  };
  d3.scale.quantize = function() {
  return d3_scale_quantize(0, 1, [ 0, 1 ]);
  };
  d3.scale.threshold = function() {
  return d3_scale_threshold([ .5 ], [ 0, 1 ]);
  };
  d3.scale.identity = function() {
  return d3_scale_identity([ 0, 1 ]);
  };
  d3.svg = {};
  d3.svg.arc = function() {
  function arc() {
  var r0 = innerRadius.apply(this, arguments), r1 = outerRadius.apply(this, arguments), a0 = startAngle.apply(this, arguments) + d3_svg_arcOffset, a1 = endAngle.apply(this, arguments) + d3_svg_arcOffset, da = (a1 < a0 && (da = a0, a0 = a1, a1 = da), a1 - a0), df = da < Math.PI ? "0" : "1", c0 = Math.cos(a0), s0 = Math.sin(a0), c1 = Math.cos(a1), s1 = Math.sin(a1);
  return da >= d3_svg_arcMax ? r0 ? "M0," + r1 + "A" + r1 + "," + r1 + " 0 1,1 0," + -r1 + "A" + r1 + "," + r1 + " 0 1,1 0," + r1 + "M0," + r0 + "A" + r0 + "," + r0 + " 0 1,0 0," + -r0 + "A" + r0 + "," + r0 + " 0 1,0 0," + r0 + "Z" : "M0," + r1 + "A" + r1 + "," + r1 + " 0 1,1 0," + -r1 + "A" + r1 + "," + r1 + " 0 1,1 0," + r1 + "Z" : r0 ? "M" + r1 * c0 + "," + r1 * s0 + "A" + r1 + "," + r1 + " 0 " + df + ",1 " + r1 * c1 + "," + r1 * s1 + "L" + r0 * c1 + "," + r0 * s1 + "A" + r0 + "," + r0 + " 0 " + df + ",0 " + r0 * c0 + "," + r0 * s0 + "Z" : "M" + r1 * c0 + "," + r1 * s0 + "A" + r1 + "," + r1 + " 0 " + df + ",1 " + r1 * c1 + "," + r1 * s1 + "L0,0" + "Z";
  }
  var innerRadius = d3_svg_arcInnerRadius, outerRadius = d3_svg_arcOuterRadius, startAngle = d3_svg_arcStartAngle, endAngle = d3_svg_arcEndAngle;
  arc.innerRadius = function(v) {
  if (!arguments.length) return innerRadius;
  innerRadius = d3_functor(v);
  return arc;
  };
  arc.outerRadius = function(v) {
  if (!arguments.length) return outerRadius;
  outerRadius = d3_functor(v);
  return arc;
  };
  arc.startAngle = function(v) {
  if (!arguments.length) return startAngle;
  startAngle = d3_functor(v);
  return arc;
  };
  arc.endAngle = function(v) {
  if (!arguments.length) return endAngle;
  endAngle = d3_functor(v);
  return arc;
  };
  arc.centroid = function() {
  var r = (innerRadius.apply(this, arguments) + outerRadius.apply(this, arguments)) / 2, a = (startAngle.apply(this, arguments) + endAngle.apply(this, arguments)) / 2 + d3_svg_arcOffset;
  return [ Math.cos(a) * r, Math.sin(a) * r ];
  };
  return arc;
  };
  var d3_svg_arcOffset = -Math.PI / 2, d3_svg_arcMax = 2 * Math.PI - 1e-6;
  d3.svg.line = function() {
  return d3_svg_line(d3_identity);
  };
  var d3_svg_lineInterpolators = d3.map({
  linear: d3_svg_lineLinear,
  "linear-closed": d3_svg_lineLinearClosed,
  "step-before": d3_svg_lineStepBefore,
  "step-after": d3_svg_lineStepAfter,
  basis: d3_svg_lineBasis,
  "basis-open": d3_svg_lineBasisOpen,
  "basis-closed": d3_svg_lineBasisClosed,
  bundle: d3_svg_lineBundle,
  cardinal: d3_svg_lineCardinal,
  "cardinal-open": d3_svg_lineCardinalOpen,
  "cardinal-closed": d3_svg_lineCardinalClosed,
  monotone: d3_svg_lineMonotone
  });
  d3_svg_lineInterpolators.forEach(function(key, value) {
  value.key = key;
  value.closed = /-closed$/.test(key);
  });
  var d3_svg_lineBasisBezier1 = [ 0, 2 / 3, 1 / 3, 0 ], d3_svg_lineBasisBezier2 = [ 0, 1 / 3, 2 / 3, 0 ], d3_svg_lineBasisBezier3 = [ 0, 1 / 6, 2 / 3, 1 / 6 ];
  d3.svg.line.radial = function() {
  var line = d3_svg_line(d3_svg_lineRadial);
  line.radius = line.x, delete line.x;
  line.angle = line.y, delete line.y;
  return line;
  };
  d3_svg_lineStepBefore.reverse = d3_svg_lineStepAfter;
  d3_svg_lineStepAfter.reverse = d3_svg_lineStepBefore;
  d3.svg.area = function() {
  return d3_svg_area(d3_identity);
  };
  d3.svg.area.radial = function() {
  var area = d3_svg_area(d3_svg_lineRadial);
  area.radius = area.x, delete area.x;
  area.innerRadius = area.x0, delete area.x0;
  area.outerRadius = area.x1, delete area.x1;
  area.angle = area.y, delete area.y;
  area.startAngle = area.y0, delete area.y0;
  area.endAngle = area.y1, delete area.y1;
  return area;
  };
  d3.svg.chord = function() {
  function chord(d, i) {
  var s = subgroup(this, source, d, i), t = subgroup(this, target, d, i);
  return "M" + s.p0 + arc(s.r, s.p1, s.a1 - s.a0) + (equals(s, t) ? curve(s.r, s.p1, s.r, s.p0) : curve(s.r, s.p1, t.r, t.p0) + arc(t.r, t.p1, t.a1 - t.a0) + curve(t.r, t.p1, s.r, s.p0)) + "Z";
  }
  function subgroup(self, f, d, i) {
  var subgroup = f.call(self, d, i), r = radius.call(self, subgroup, i), a0 = startAngle.call(self, subgroup, i) + d3_svg_arcOffset, a1 = endAngle.call(self, subgroup, i) + d3_svg_arcOffset;
  return {
  r: r,
  a0: a0,
  a1: a1,
  p0: [ r * Math.cos(a0), r * Math.sin(a0) ],
  p1: [ r * Math.cos(a1), r * Math.sin(a1) ]
  };
  }
  function equals(a, b) {
  return a.a0 == b.a0 && a.a1 == b.a1;
  }
  function arc(r, p, a) {
  return "A" + r + "," + r + " 0 " + +(a > Math.PI) + ",1 " + p;
  }
  function curve(r0, p0, r1, p1) {
  return "Q 0,0 " + p1;
  }
  var source = d3_svg_chordSource, target = d3_svg_chordTarget, radius = d3_svg_chordRadius, startAngle = d3_svg_arcStartAngle, endAngle = d3_svg_arcEndAngle;
  chord.radius = function(v) {
  if (!arguments.length) return radius;
  radius = d3_functor(v);
  return chord;
  };
  chord.source = function(v) {
  if (!arguments.length) return source;
  source = d3_functor(v);
  return chord;
  };
  chord.target = function(v) {
  if (!arguments.length) return target;
  target = d3_functor(v);
  return chord;
  };
  chord.startAngle = function(v) {
  if (!arguments.length) return startAngle;
  startAngle = d3_functor(v);
  return chord;
  };
  chord.endAngle = function(v) {
  if (!arguments.length) return endAngle;
  endAngle = d3_functor(v);
  return chord;
  };
  return chord;
  };
  d3.svg.diagonal = function() {
  function diagonal(d, i) {
  var p0 = source.call(this, d, i), p3 = target.call(this, d, i), m = (p0.y + p3.y) / 2, p = [ p0, {
  x: p0.x,
  y: m
  }, {
  x: p3.x,
  y: m
  }, p3 ];
  p = p.map(projection);
  return "M" + p[0] + "C" + p[1] + " " + p[2] + " " + p[3];
  }
  var source = d3_svg_chordSource, target = d3_svg_chordTarget, projection = d3_svg_diagonalProjection;
  diagonal.source = function(x) {
  if (!arguments.length) return source;
  source = d3_functor(x);
  return diagonal;
  };
  diagonal.target = function(x) {
  if (!arguments.length) return target;
  target = d3_functor(x);
  return diagonal;
  };
  diagonal.projection = function(x) {
  if (!arguments.length) return projection;
  projection = x;
  return diagonal;
  };
  return diagonal;
  };
  d3.svg.diagonal.radial = function() {
  var diagonal = d3.svg.diagonal(), projection = d3_svg_diagonalProjection, projection_ = diagonal.projection;
  diagonal.projection = function(x) {
  return arguments.length ? projection_(d3_svg_diagonalRadialProjection(projection = x)) : projection;
  };
  return diagonal;
  };
  d3.svg.mouse = d3.mouse;
  d3.svg.touches = d3.touches;
  d3.svg.symbol = function() {
  function symbol(d, i) {
  return (d3_svg_symbols.get(type.call(this, d, i)) || d3_svg_symbolCircle)(size.call(this, d, i));
  }
  var type = d3_svg_symbolType, size = d3_svg_symbolSize;
  symbol.type = function(x) {
  if (!arguments.length) return type;
  type = d3_functor(x);
  return symbol;
  };
  symbol.size = function(x) {
  if (!arguments.length) return size;
  size = d3_functor(x);
  return symbol;
  };
  return symbol;
  };
  var d3_svg_symbols = d3.map({
  circle: d3_svg_symbolCircle,
  cross: function(size) {
  var r = Math.sqrt(size / 5) / 2;
  return "M" + -3 * r + "," + -r + "H" + -r + "V" + -3 * r + "H" + r + "V" + -r + "H" + 3 * r + "V" + r + "H" + r + "V" + 3 * r + "H" + -r + "V" + r + "H" + -3 * r + "Z";
  },
  diamond: function(size) {
  var ry = Math.sqrt(size / (2 * d3_svg_symbolTan30)), rx = ry * d3_svg_symbolTan30;
  return "M0," + -ry + "L" + rx + ",0" + " 0," + ry + " " + -rx + ",0" + "Z";
  },
  square: function(size) {
  var r = Math.sqrt(size) / 2;
  return "M" + -r + "," + -r + "L" + r + "," + -r + " " + r + "," + r + " " + -r + "," + r + "Z";
  },
  "triangle-down": function(size) {
  var rx = Math.sqrt(size / d3_svg_symbolSqrt3), ry = rx * d3_svg_symbolSqrt3 / 2;
  return "M0," + ry + "L" + rx + "," + -ry + " " + -rx + "," + -ry + "Z";
  },
  "triangle-up": function(size) {
  var rx = Math.sqrt(size / d3_svg_symbolSqrt3), ry = rx * d3_svg_symbolSqrt3 / 2;
  return "M0," + -ry + "L" + rx + "," + ry + " " + -rx + "," + ry + "Z";
  }
  });
  d3.svg.symbolTypes = d3_svg_symbols.keys();
  var d3_svg_symbolSqrt3 = Math.sqrt(3), d3_svg_symbolTan30 = Math.tan(30 * Math.PI / 180);
  d3.svg.axis = function() {
  function axis(g) {
  g.each(function() {
  var g = d3.select(this);
  var ticks = tickValues == null ? scale.ticks ? scale.ticks.apply(scale, tickArguments_) : scale.domain() : tickValues, tickFormat = tickFormat_ == null ? scale.tickFormat ? scale.tickFormat.apply(scale, tickArguments_) : String : tickFormat_;
  var subticks = d3_svg_axisSubdivide(scale, ticks, tickSubdivide), subtick = g.selectAll(".minor").data(subticks, String), subtickEnter = subtick.enter().insert("line", "g").attr("class", "tick minor").style("opacity", 1e-6), subtickExit = d3.transition(subtick.exit()).style("opacity", 1e-6).remove(), subtickUpdate = d3.transition(subtick).style("opacity", 1);
  var tick = g.selectAll("g").data(ticks, String), tickEnter = tick.enter().insert("g", "path").style("opacity", 1e-6), tickExit = d3.transition(tick.exit()).style("opacity", 1e-6).remove(), tickUpdate = d3.transition(tick).style("opacity", 1), tickTransform;
  var range = d3_scaleRange(scale), path = g.selectAll(".domain").data([ 0 ]), pathEnter = path.enter().append("path").attr("class", "domain"), pathUpdate = d3.transition(path);
  var scale1 = scale.copy(), scale0 = this.__chart__ || scale1;
  this.__chart__ = scale1;
  tickEnter.append("line").attr("class", "tick");
  tickEnter.append("text");
  var lineEnter = tickEnter.select("line"), lineUpdate = tickUpdate.select("line"), text = tick.select("text").text(tickFormat), textEnter = tickEnter.select("text"), textUpdate = tickUpdate.select("text");
  switch (orient) {
  case "bottom":
  {
  tickTransform = d3_svg_axisX;
  subtickEnter.attr("y2", tickMinorSize);
  subtickUpdate.attr("x2", 0).attr("y2", tickMinorSize);
  lineEnter.attr("y2", tickMajorSize);
  textEnter.attr("y", Math.max(tickMajorSize, 0) + tickPadding);
  lineUpdate.attr("x2", 0).attr("y2", tickMajorSize);
  textUpdate.attr("x", 0).attr("y", Math.max(tickMajorSize, 0) + tickPadding);
  text.attr("dy", ".71em").attr("text-anchor", "middle");
  pathUpdate.attr("d", "M" + range[0] + "," + tickEndSize + "V0H" + range[1] + "V" + tickEndSize);
  break;
  }
  case "top":
  {
  tickTransform = d3_svg_axisX;
  subtickEnter.attr("y2", -tickMinorSize);
  subtickUpdate.attr("x2", 0).attr("y2", -tickMinorSize);
  lineEnter.attr("y2", -tickMajorSize);
  textEnter.attr("y", -(Math.max(tickMajorSize, 0) + tickPadding));
  lineUpdate.attr("x2", 0).attr("y2", -tickMajorSize);
  textUpdate.attr("x", 0).attr("y", -(Math.max(tickMajorSize, 0) + tickPadding));
  text.attr("dy", "0em").attr("text-anchor", "middle");
  pathUpdate.attr("d", "M" + range[0] + "," + -tickEndSize + "V0H" + range[1] + "V" + -tickEndSize);
  break;
  }
  case "left":
  {
  tickTransform = d3_svg_axisY;
  subtickEnter.attr("x2", -tickMinorSize);
  subtickUpdate.attr("x2", -tickMinorSize).attr("y2", 0);
  lineEnter.attr("x2", -tickMajorSize);
  textEnter.attr("x", -(Math.max(tickMajorSize, 0) + tickPadding));
  lineUpdate.attr("x2", -tickMajorSize).attr("y2", 0);
  textUpdate.attr("x", -(Math.max(tickMajorSize, 0) + tickPadding)).attr("y", 0);
  text.attr("dy", ".32em").attr("text-anchor", "end");
  pathUpdate.attr("d", "M" + -tickEndSize + "," + range[0] + "H0V" + range[1] + "H" + -tickEndSize);
  break;
  }
  case "right":
  {
  tickTransform = d3_svg_axisY;
  subtickEnter.attr("x2", tickMinorSize);
  subtickUpdate.attr("x2", tickMinorSize).attr("y2", 0);
  lineEnter.attr("x2", tickMajorSize);
  textEnter.attr("x", Math.max(tickMajorSize, 0) + tickPadding);
  lineUpdate.attr("x2", tickMajorSize).attr("y2", 0);
  textUpdate.attr("x", Math.max(tickMajorSize, 0) + tickPadding).attr("y", 0);
  text.attr("dy", ".32em").attr("text-anchor", "start");
  pathUpdate.attr("d", "M" + tickEndSize + "," + range[0] + "H0V" + range[1] + "H" + tickEndSize);
  break;
  }
  }
  if (scale.ticks) {
  tickEnter.call(tickTransform, scale0);
  tickUpdate.call(tickTransform, scale1);
  tickExit.call(tickTransform, scale1);
  subtickEnter.call(tickTransform, scale0);
  subtickUpdate.call(tickTransform, scale1);
  subtickExit.call(tickTransform, scale1);
  } else {
  var dx = scale1.rangeBand() / 2, x = function(d) {
  return scale1(d) + dx;
  };
  tickEnter.call(tickTransform, x);
  tickUpdate.call(tickTransform, x);
  }
  });
  }
  var scale = d3.scale.linear(), orient = "bottom", tickMajorSize = 6, tickMinorSize = 6, tickEndSize = 6, tickPadding = 3, tickArguments_ = [ 10 ], tickValues = null, tickFormat_, tickSubdivide = 0;
  axis.scale = function(x) {
  if (!arguments.length) return scale;
  scale = x;
  return axis;
  };
  axis.orient = function(x) {
  if (!arguments.length) return orient;
  orient = x;
  return axis;
  };
  axis.ticks = function() {
  if (!arguments.length) return tickArguments_;
  tickArguments_ = arguments;
  return axis;
  };
  axis.tickValues = function(x) {
  if (!arguments.length) return tickValues;
  tickValues = x;
  return axis;
  };
  axis.tickFormat = function(x) {
  if (!arguments.length) return tickFormat_;
  tickFormat_ = x;
  return axis;
  };
  axis.tickSize = function(x, y, z) {
  if (!arguments.length) return tickMajorSize;
  var n = arguments.length - 1;
  tickMajorSize = +x;
  tickMinorSize = n > 1 ? +y : tickMajorSize;
  tickEndSize = n > 0 ? +arguments[n] : tickMajorSize;
  return axis;
  };
  axis.tickPadding = function(x) {
  if (!arguments.length) return tickPadding;
  tickPadding = +x;
  return axis;
  };
  axis.tickSubdivide = function(x) {
  if (!arguments.length) return tickSubdivide;
  tickSubdivide = +x;
  return axis;
  };
  return axis;
  };
  d3.svg.brush = function() {
  function brush(g) {
  g.each(function() {
  var g = d3.select(this), bg = g.selectAll(".background").data([ 0 ]), fg = g.selectAll(".extent").data([ 0 ]), tz = g.selectAll(".resize").data(resizes, String), e;
  g.style("pointer-events", "all").on("mousedown.brush", brushstart).on("touchstart.brush", brushstart);
  bg.enter().append("rect").attr("class", "background").style("visibility", "hidden").style("cursor", "crosshair");
  fg.enter().append("rect").attr("class", "extent").style("cursor", "move");
  tz.enter().append("g").attr("class", function(d) {
  return "resize " + d;
  }).style("cursor", function(d) {
  return d3_svg_brushCursor[d];
  }).append("rect").attr("x", function(d) {
  return /[ew]$/.test(d) ? -3 : null;
  }).attr("y", function(d) {
  return /^[ns]/.test(d) ? -3 : null;
  }).attr("width", 6).attr("height", 6).style("visibility", "hidden");
  tz.style("display", brush.empty() ? "none" : null);
  tz.exit().remove();
  if (x) {
  e = d3_scaleRange(x);
  bg.attr("x", e[0]).attr("width", e[1] - e[0]);
  redrawX(g);
  }
  if (y) {
  e = d3_scaleRange(y);
  bg.attr("y", e[0]).attr("height", e[1] - e[0]);
  redrawY(g);
  }
  redraw(g);
  });
  }
  function redraw(g) {
  g.selectAll(".resize").attr("transform", function(d) {
  return "translate(" + extent[+/e$/.test(d)][0] + "," + extent[+/^s/.test(d)][1] + ")";
  });
  }
  function redrawX(g) {
  g.select(".extent").attr("x", extent[0][0]);
  g.selectAll(".extent,.n>rect,.s>rect").attr("width", extent[1][0] - extent[0][0]);
  }
  function redrawY(g) {
  g.select(".extent").attr("y", extent[0][1]);
  g.selectAll(".extent,.e>rect,.w>rect").attr("height", extent[1][1] - extent[0][1]);
  }
  function brushstart() {
  function mouse() {
  var touches = d3.event.changedTouches;
  return touches ? d3.touches(target, touches)[0] : d3.mouse(target);
  }
  function keydown() {
  if (d3.event.keyCode == 32) {
  if (!dragging) {
  center = null;
  origin[0] -= extent[1][0];
  origin[1] -= extent[1][1];
  dragging = 2;
  }
  d3_eventCancel();
  }
  }
  function keyup() {
  if (d3.event.keyCode == 32 && dragging == 2) {
  origin[0] += extent[1][0];
  origin[1] += extent[1][1];
  dragging = 0;
  d3_eventCancel();
  }
  }
  function brushmove() {
  var point = mouse(), moved = false;
  if (offset) {
  point[0] += offset[0];
  point[1] += offset[1];
  }
  if (!dragging) {
  if (d3.event.altKey) {
  if (!center) center = [ (extent[0][0] + extent[1][0]) / 2, (extent[0][1] + extent[1][1]) / 2 ];
  origin[0] = extent[+(point[0] < center[0])][0];
  origin[1] = extent[+(point[1] < center[1])][1];
  } else center = null;
  }
  if (resizingX && move1(point, x, 0)) {
  redrawX(g);
  moved = true;
  }
  if (resizingY && move1(point, y, 1)) {
  redrawY(g);
  moved = true;
  }
  if (moved) {
  redraw(g);
  event_({
  type: "brush",
  mode: dragging ? "move" : "resize"
  });
  }
  }
  function move1(point, scale, i) {
  var range = d3_scaleRange(scale), r0 = range[0], r1 = range[1], position = origin[i], size = extent[1][i] - extent[0][i], min, max;
  if (dragging) {
  r0 -= position;
  r1 -= size + position;
  }
  min = Math.max(r0, Math.min(r1, point[i]));
  if (dragging) {
  max = (min += position) + size;
  } else {
  if (center) position = Math.max(r0, Math.min(r1, 2 * center[i] - min));
  if (position < min) {
  max = min;
  min = position;
  } else {
  max = position;
  }
  }
  if (extent[0][i] !== min || extent[1][i] !== max) {
  extentDomain = null;
  extent[0][i] = min;
  extent[1][i] = max;
  return true;
  }
  }
  function brushend() {
  brushmove();
  g.style("pointer-events", "all").selectAll(".resize").style("display", brush.empty() ? "none" : null);
  d3.select("body").style("cursor", null);
  w.on("mousemove.brush", null).on("mouseup.brush", null).on("touchmove.brush", null).on("touchend.brush", null).on("keydown.brush", null).on("keyup.brush", null);
  event_({
  type: "brushend"
  });
  d3_eventCancel();
  }
  var target = this, eventTarget = d3.select(d3.event.target), event_ = event.of(target, arguments), g = d3.select(target), resizing = eventTarget.datum(), resizingX = !/^(n|s)$/.test(resizing) && x, resizingY = !/^(e|w)$/.test(resizing) && y, dragging = eventTarget.classed("extent"), center, origin = mouse(), offset;
  var w = d3.select(window).on("mousemove.brush", brushmove).on("mouseup.brush", brushend).on("touchmove.brush", brushmove).on("touchend.brush", brushend).on("keydown.brush", keydown).on("keyup.brush", keyup);
  if (dragging) {
  origin[0] = extent[0][0] - origin[0];
  origin[1] = extent[0][1] - origin[1];
  } else if (resizing) {
  var ex = +/w$/.test(resizing), ey = +/^n/.test(resizing);
  offset = [ extent[1 - ex][0] - origin[0], extent[1 - ey][1] - origin[1] ];
  origin[0] = extent[ex][0];
  origin[1] = extent[ey][1];
  } else if (d3.event.altKey) center = origin.slice();
  g.style("pointer-events", "none").selectAll(".resize").style("display", null);
  d3.select("body").style("cursor", eventTarget.style("cursor"));
  event_({
  type: "brushstart"
  });
  brushmove();
  d3_eventCancel();
  }
  var event = d3_eventDispatch(brush, "brushstart", "brush", "brushend"), x = null, y = null, resizes = d3_svg_brushResizes[0], extent = [ [ 0, 0 ], [ 0, 0 ] ], extentDomain;
  brush.x = function(z) {
  if (!arguments.length) return x;
  x = z;
  resizes = d3_svg_brushResizes[!x << 1 | !y];
  return brush;
  };
  brush.y = function(z) {
  if (!arguments.length) return y;
  y = z;
  resizes = d3_svg_brushResizes[!x << 1 | !y];
  return brush;
  };
  brush.extent = function(z) {
  var x0, x1, y0, y1, t;
  if (!arguments.length) {
  z = extentDomain || extent;
  if (x) {
  x0 = z[0][0], x1 = z[1][0];
  if (!extentDomain) {
  x0 = extent[0][0], x1 = extent[1][0];
  if (x.invert) x0 = x.invert(x0), x1 = x.invert(x1);
  if (x1 < x0) t = x0, x0 = x1, x1 = t;
  }
  }
  if (y) {
  y0 = z[0][1], y1 = z[1][1];
  if (!extentDomain) {
  y0 = extent[0][1], y1 = extent[1][1];
  if (y.invert) y0 = y.invert(y0), y1 = y.invert(y1);
  if (y1 < y0) t = y0, y0 = y1, y1 = t;
  }
  }
  return x && y ? [ [ x0, y0 ], [ x1, y1 ] ] : x ? [ x0, x1 ] : y && [ y0, y1 ];
  }
  extentDomain = [ [ 0, 0 ], [ 0, 0 ] ];
  if (x) {
  x0 = z[0], x1 = z[1];
  if (y) x0 = x0[0], x1 = x1[0];
  extentDomain[0][0] = x0, extentDomain[1][0] = x1;
  if (x.invert) x0 = x(x0), x1 = x(x1);
  if (x1 < x0) t = x0, x0 = x1, x1 = t;
  extent[0][0] = x0 | 0, extent[1][0] = x1 | 0;
  }
  if (y) {
  y0 = z[0], y1 = z[1];
  if (x) y0 = y0[1], y1 = y1[1];
  extentDomain[0][1] = y0, extentDomain[1][1] = y1;
  if (y.invert) y0 = y(y0), y1 = y(y1);
  if (y1 < y0) t = y0, y0 = y1, y1 = t;
  extent[0][1] = y0 | 0, extent[1][1] = y1 | 0;
  }
  return brush;
  };
  brush.clear = function() {
  extentDomain = null;
  extent[0][0] = extent[0][1] = extent[1][0] = extent[1][1] = 0;
  return brush;
  };
  brush.empty = function() {
  return x && extent[0][0] === extent[1][0] || y && extent[0][1] === extent[1][1];
  };
  return d3.rebind(brush, event, "on");
  };
  var d3_svg_brushCursor = {
  n: "ns-resize",
  e: "ew-resize",
  s: "ns-resize",
  w: "ew-resize",
  nw: "nwse-resize",
  ne: "nesw-resize",
  se: "nwse-resize",
  sw: "nesw-resize"
  };
  var d3_svg_brushResizes = [ [ "n", "e", "s", "w", "nw", "ne", "se", "sw" ], [ "e", "w" ], [ "n", "s" ], [] ];
  d3.behavior = {};
  d3.behavior.drag = function() {
  function drag() {
  this.on("mousedown.drag", mousedown).on("touchstart.drag", mousedown);
  }
  function mousedown() {
  function point() {
  var p = target.parentNode;
  return touchId ? d3.touches(p).filter(function(p) {
  return p.identifier === touchId;
  })[0] : d3.mouse(p);
  }
  function dragmove() {
  if (!target.parentNode) return dragend();
  var p = point(), dx = p[0] - origin_[0], dy = p[1] - origin_[1];
  moved |= dx | dy;
  origin_ = p;
  d3_eventCancel();
  event_({
  type: "drag",
  x: p[0] + offset[0],
  y: p[1] + offset[1],
  dx: dx,
  dy: dy
  });
  }
  function dragend() {
  event_({
  type: "dragend"
  });
  if (moved) {
  d3_eventCancel();
  if (d3.event.target === eventTarget) w.on("click.drag", click, true);
  }
  w.on(touchId ? "touchmove.drag-" + touchId : "mousemove.drag", null).on(touchId ? "touchend.drag-" + touchId : "mouseup.drag", null);
  }
  function click() {
  d3_eventCancel();
  w.on("click.drag", null);
  }
  var target = this, event_ = event.of(target, arguments), eventTarget = d3.event.target, touchId = d3.event.touches && d3.event.changedTouches[0].identifier, offset, origin_ = point(), moved = 0;
  var w = d3.select(window).on(touchId ? "touchmove.drag-" + touchId : "mousemove.drag", dragmove).on(touchId ? "touchend.drag-" + touchId : "mouseup.drag", dragend, true);
  if (origin) {
  offset = origin.apply(target, arguments);
  offset = [ offset.x - origin_[0], offset.y - origin_[1] ];
  } else {
  offset = [ 0, 0 ];
  }
  if (!touchId) d3_eventCancel();
  event_({
  type: "dragstart"
  });
  }
  var event = d3_eventDispatch(drag, "drag", "dragstart", "dragend"), origin = null;
  drag.origin = function(x) {
  if (!arguments.length) return origin;
  origin = x;
  return drag;
  };
  return d3.rebind(drag, event, "on");
  };
  d3.behavior.zoom = function() {
  function zoom() {
  this.on("mousedown.zoom", mousedown).on("mousewheel.zoom", mousewheel).on("mousemove.zoom", mousemove).on("DOMMouseScroll.zoom", mousewheel).on("dblclick.zoom", dblclick).on("touchstart.zoom", touchstart).on("touchmove.zoom", touchmove).on("touchend.zoom", touchstart);
  }
  function location(p) {
  return [ (p[0] - translate[0]) / scale, (p[1] - translate[1]) / scale ];
  }
  function point(l) {
  return [ l[0] * scale + translate[0], l[1] * scale + translate[1] ];
  }
  function scaleTo(s) {
  scale = Math.max(scaleExtent[0], Math.min(scaleExtent[1], s));
  }
  function translateTo(p, l) {
  l = point(l);
  translate[0] += p[0] - l[0];
  translate[1] += p[1] - l[1];
  }
  function dispatch(event) {
  if (x1) x1.domain(x0.range().map(function(x) {
  return (x - translate[0]) / scale;
  }).map(x0.invert));
  if (y1) y1.domain(y0.range().map(function(y) {
  return (y - translate[1]) / scale;
  }).map(y0.invert));
  d3.event.preventDefault();
  event({
  type: "zoom",
  scale: scale,
  translate: translate
  });
  }
  function mousedown() {
  function mousemove() {
  moved = 1;
  translateTo(d3.mouse(target), l);
  dispatch(event_);
  }
  function mouseup() {
  if (moved) d3_eventCancel();
  w.on("mousemove.zoom", null).on("mouseup.zoom", null);
  if (moved && d3.event.target === eventTarget) w.on("click.zoom", click, true);
  }
  function click() {
  d3_eventCancel();
  w.on("click.zoom", null);
  }
  var target = this, event_ = event.of(target, arguments), eventTarget = d3.event.target, moved = 0, w = d3.select(window).on("mousemove.zoom", mousemove).on("mouseup.zoom", mouseup), l = location(d3.mouse(target));
  window.focus();
  d3_eventCancel();
  }
  function mousewheel() {
  if (!translate0) translate0 = location(d3.mouse(this));
  scaleTo(Math.pow(2, d3_behavior_zoomDelta() * .002) * scale);
  translateTo(d3.mouse(this), translate0);
  dispatch(event.of(this, arguments));
  }
  function mousemove() {
  translate0 = null;
  }
  function dblclick() {
  var p = d3.mouse(this), l = location(p);
  scaleTo(d3.event.shiftKey ? scale / 2 : scale * 2);
  translateTo(p, l);
  dispatch(event.of(this, arguments));
  }
  function touchstart() {
  var touches = d3.touches(this), now = Date.now();
  scale0 = scale;
  translate0 = {};
  touches.forEach(function(t) {
  translate0[t.identifier] = location(t);
  });
  d3_eventCancel();
  if (touches.length === 1) {
  if (now - touchtime < 500) {
  var p = touches[0], l = location(touches[0]);
  scaleTo(scale * 2);
  translateTo(p, l);
  dispatch(event.of(this, arguments));
  }
  touchtime = now;
  }
  }
  function touchmove() {
  var touches = d3.touches(this), p0 = touches[0], l0 = translate0[p0.identifier];
  if (p1 = touches[1]) {
  var p1, l1 = translate0[p1.identifier];
  p0 = [ (p0[0] + p1[0]) / 2, (p0[1] + p1[1]) / 2 ];
  l0 = [ (l0[0] + l1[0]) / 2, (l0[1] + l1[1]) / 2 ];
  scaleTo(d3.event.scale * scale0);
  }
  translateTo(p0, l0);
  touchtime = null;
  dispatch(event.of(this, arguments));
  }
  var translate = [ 0, 0 ], translate0, scale = 1, scale0, scaleExtent = d3_behavior_zoomInfinity, event = d3_eventDispatch(zoom, "zoom"), x0, x1, y0, y1, touchtime;
  zoom.translate = function(x) {
  if (!arguments.length) return translate;
  translate = x.map(Number);
  return zoom;
  };
  zoom.scale = function(x) {
  if (!arguments.length) return scale;
  scale = +x;
  return zoom;
  };
  zoom.scaleExtent = function(x) {
  if (!arguments.length) return scaleExtent;
  scaleExtent = x == null ? d3_behavior_zoomInfinity : x.map(Number);
  return zoom;
  };
  zoom.x = function(z) {
  if (!arguments.length) return x1;
  x1 = z;
  x0 = z.copy();
  return zoom;
  };
  zoom.y = function(z) {
  if (!arguments.length) return y1;
  y1 = z;
  y0 = z.copy();
  return zoom;
  };
  return d3.rebind(zoom, event, "on");
  };
  var d3_behavior_zoomDiv, d3_behavior_zoomInfinity = [ 0, Infinity ];
  d3.layout = {};
  d3.layout.bundle = function() {
  return function(links) {
  var paths = [], i = -1, n = links.length;
  while (++i < n) paths.push(d3_layout_bundlePath(links[i]));
  return paths;
  };
  };
  d3.layout.chord = function() {
  function relayout() {
  var subgroups = {}, groupSums = [], groupIndex = d3.range(n), subgroupIndex = [], k, x, x0, i, j;
  chords = [];
  groups = [];
  k = 0, i = -1;
  while (++i < n) {
  x = 0, j = -1;
  while (++j < n) {
  x += matrix[i][j];
  }
  groupSums.push(x);
  subgroupIndex.push(d3.range(n));
  k += x;
  }
  if (sortGroups) {
  groupIndex.sort(function(a, b) {
  return sortGroups(groupSums[a], groupSums[b]);
  });
  }
  if (sortSubgroups) {
  subgroupIndex.forEach(function(d, i) {
  d.sort(function(a, b) {
  return sortSubgroups(matrix[i][a], matrix[i][b]);
  });
  });
  }
  k = (2 * Math.PI - padding * n) / k;
  x = 0, i = -1;
  while (++i < n) {
  x0 = x, j = -1;
  while (++j < n) {
  var di = groupIndex[i], dj = subgroupIndex[di][j], v = matrix[di][dj], a0 = x, a1 = x += v * k;
  subgroups[di + "-" + dj] = {
  index: di,
  subindex: dj,
  startAngle: a0,
  endAngle: a1,
  value: v
  };
  }
  groups[di] = {
  index: di,
  startAngle: x0,
  endAngle: x,
  value: (x - x0) / k
  };
  x += padding;
  }
  i = -1;
  while (++i < n) {
  j = i - 1;
  while (++j < n) {
  var source = subgroups[i + "-" + j], target = subgroups[j + "-" + i];
  if (source.value || target.value) {
  chords.push(source.value < target.value ? {
  source: target,
  target: source
  } : {
  source: source,
  target: target
  });
  }
  }
  }
  if (sortChords) resort();
  }
  function resort() {
  chords.sort(function(a, b) {
  return sortChords((a.source.value + a.target.value) / 2, (b.source.value + b.target.value) / 2);
  });
  }
  var chord = {}, chords, groups, matrix, n, padding = 0, sortGroups, sortSubgroups, sortChords;
  chord.matrix = function(x) {
  if (!arguments.length) return matrix;
  n = (matrix = x) && matrix.length;
  chords = groups = null;
  return chord;
  };
  chord.padding = function(x) {
  if (!arguments.length) return padding;
  padding = x;
  chords = groups = null;
  return chord;
  };
  chord.sortGroups = function(x) {
  if (!arguments.length) return sortGroups;
  sortGroups = x;
  chords = groups = null;
  return chord;
  };
  chord.sortSubgroups = function(x) {
  if (!arguments.length) return sortSubgroups;
  sortSubgroups = x;
  chords = null;
  return chord;
  };
  chord.sortChords = function(x) {
  if (!arguments.length) return sortChords;
  sortChords = x;
  if (chords) resort();
  return chord;
  };
  chord.chords = function() {
  if (!chords) relayout();
  return chords;
  };
  chord.groups = function() {
  if (!groups) relayout();
  return groups;
  };
  return chord;
  };
  d3.layout.force = function() {
  function repulse(node) {
  return function(quad, x1, y1, x2, y2) {
  if (quad.point !== node) {
  var dx = quad.cx - node.x, dy = quad.cy - node.y, dn = 1 / Math.sqrt(dx * dx + dy * dy);
  if ((x2 - x1) * dn < theta) {
  var k = quad.charge * dn * dn;
  node.px -= dx * k;
  node.py -= dy * k;
  return true;
  }
  if (quad.point && isFinite(dn)) {
  var k = quad.pointCharge * dn * dn;
  node.px -= dx * k;
  node.py -= dy * k;
  }
  }
  return !quad.charge;
  };
  }
  function dragmove(d) {
  d.px = d3.event.x;
  d.py = d3.event.y;
  force.resume();
  }
  var force = {}, event = d3.dispatch("start", "tick", "end"), size = [ 1, 1 ], drag, alpha, friction = .9, linkDistance = d3_layout_forceLinkDistance, linkStrength = d3_layout_forceLinkStrength, charge = -30, gravity = .1, theta = .8, interval, nodes = [], links = [], distances, strengths, charges;
  force.tick = function() {
  if ((alpha *= .99) < .005) {
  event.end({
  type: "end",
  alpha: alpha = 0
  });
  return true;
  }
  var n = nodes.length, m = links.length, q, i, o, s, t, l, k, x, y;
  for (i = 0; i < m; ++i) {
  o = links[i];
  s = o.source;
  t = o.target;
  x = t.x - s.x;
  y = t.y - s.y;
  if (l = x * x + y * y) {
  l = alpha * strengths[i] * ((l = Math.sqrt(l)) - distances[i]) / l;
  x *= l;
  y *= l;
  t.x -= x * (k = s.weight / (t.weight + s.weight));
  t.y -= y * k;
  s.x += x * (k = 1 - k);
  s.y += y * k;
  }
  }
  if (k = alpha * gravity) {
  x = size[0] / 2;
  y = size[1] / 2;
  i = -1;
  if (k) while (++i < n) {
  o = nodes[i];
  o.x += (x - o.x) * k;
  o.y += (y - o.y) * k;
  }
  }
  if (charge) {
  d3_layout_forceAccumulate(q = d3.geom.quadtree(nodes), alpha, charges);
  i = -1;
  while (++i < n) {
  if (!(o = nodes[i]).fixed) {
  q.visit(repulse(o));
  }
  }
  }
  i = -1;
  while (++i < n) {
  o = nodes[i];
  if (o.fixed) {
  o.x = o.px;
  o.y = o.py;
  } else {
  o.x -= (o.px - (o.px = o.x)) * friction;
  o.y -= (o.py - (o.py = o.y)) * friction;
  }
  }
  event.tick({
  type: "tick",
  alpha: alpha
  });
  };
  force.nodes = function(x) {
  if (!arguments.length) return nodes;
  nodes = x;
  return force;
  };
  force.links = function(x) {
  if (!arguments.length) return links;
  links = x;
  return force;
  };
  force.size = function(x) {
  if (!arguments.length) return size;
  size = x;
  return force;
  };
  force.linkDistance = function(x) {
  if (!arguments.length) return linkDistance;
  linkDistance = d3_functor(x);
  return force;
  };
  force.distance = force.linkDistance;
  force.linkStrength = function(x) {
  if (!arguments.length) return linkStrength;
  linkStrength = d3_functor(x);
  return force;
  };
  force.friction = function(x) {
  if (!arguments.length) return friction;
  friction = x;
  return force;
  };
  force.charge = function(x) {
  if (!arguments.length) return charge;
  charge = typeof x === "function" ? x : +x;
  return force;
  };
  force.gravity = function(x) {
  if (!arguments.length) return gravity;
  gravity = x;
  return force;
  };
  force.theta = function(x) {
  if (!arguments.length) return theta;
  theta = x;
  return force;
  };
  force.alpha = function(x) {
  if (!arguments.length) return alpha;
  if (alpha) {
  if (x > 0) alpha = x; else alpha = 0;
  } else if (x > 0) {
  event.start({
  type: "start",
  alpha: alpha = x
  });
  d3.timer(force.tick);
  }
  return force;
  };
  force.start = function() {
  function position(dimension, size) {
  var neighbors = neighbor(i), j = -1, m = neighbors.length, x;
  while (++j < m) if (!isNaN(x = neighbors[j][dimension])) return x;
  return Math.random() * size;
  }
  function neighbor() {
  if (!neighbors) {
  neighbors = [];
  for (j = 0; j < n; ++j) {
  neighbors[j] = [];
  }
  for (j = 0; j < m; ++j) {
  var o = links[j];
  neighbors[o.source.index].push(o.target);
  neighbors[o.target.index].push(o.source);
  }
  }
  return neighbors[i];
  }
  var i, j, n = nodes.length, m = links.length, w = size[0], h = size[1], neighbors, o;
  for (i = 0; i < n; ++i) {
  (o = nodes[i]).index = i;
  o.weight = 0;
  }
  distances = [];
  strengths = [];
  for (i = 0; i < m; ++i) {
  o = links[i];
  if (typeof o.source == "number") o.source = nodes[o.source];
  if (typeof o.target == "number") o.target = nodes[o.target];
  distances[i] = linkDistance.call(this, o, i);
  strengths[i] = linkStrength.call(this, o, i);
  ++o.source.weight;
  ++o.target.weight;
  }
  for (i = 0; i < n; ++i) {
  o = nodes[i];
  if (isNaN(o.x)) o.x = position("x", w);
  if (isNaN(o.y)) o.y = position("y", h);
  if (isNaN(o.px)) o.px = o.x;
  if (isNaN(o.py)) o.py = o.y;
  }
  charges = [];
  if (typeof charge === "function") {
  for (i = 0; i < n; ++i) {
  charges[i] = +charge.call(this, nodes[i], i);
  }
  } else {
  for (i = 0; i < n; ++i) {
  charges[i] = charge;
  }
  }
  return force.resume();
  };
  force.resume = function() {
  return force.alpha(.1);
  };
  force.stop = function() {
  return force.alpha(0);
  };
  force.drag = function() {
  if (!drag) drag = d3.behavior.drag().origin(d3_identity).on("dragstart", d3_layout_forceDragstart).on("drag", dragmove).on("dragend", d3_layout_forceDragend);
  this.on("mouseover.force", d3_layout_forceMouseover).on("mouseout.force", d3_layout_forceMouseout).call(drag);
  };
  return d3.rebind(force, event, "on");
  };
  d3.layout.partition = function() {
  function position(node, x, dx, dy) {
  var children = node.children;
  node.x = x;
  node.y = node.depth * dy;
  node.dx = dx;
  node.dy = dy;
  if (children && (n = children.length)) {
  var i = -1, n, c, d;
  dx = node.value ? dx / node.value : 0;
  while (++i < n) {
  position(c = children[i], x, d = c.value * dx, dy);
  x += d;
  }
  }
  }
  function depth(node) {
  var children = node.children, d = 0;
  if (children && (n = children.length)) {
  var i = -1, n;
  while (++i < n) d = Math.max(d, depth(children[i]));
  }
  return 1 + d;
  }
  function partition(d, i) {
  var nodes = hierarchy.call(this, d, i);
  position(nodes[0], 0, size[0], size[1] / depth(nodes[0]));
  return nodes;
  }
  var hierarchy = d3.layout.hierarchy(), size = [ 1, 1 ];
  partition.size = function(x) {
  if (!arguments.length) return size;
  size = x;
  return partition;
  };
  return d3_layout_hierarchyRebind(partition, hierarchy);
  };
  d3.layout.pie = function() {
  function pie(data, i) {
  var values = data.map(function(d, i) {
  return +value.call(pie, d, i);
  });
  var a = +(typeof startAngle === "function" ? startAngle.apply(this, arguments) : startAngle);
  var k = ((typeof endAngle === "function" ? endAngle.apply(this, arguments) : endAngle) - startAngle) / d3.sum(values);
  var index = d3.range(data.length);
  if (sort != null) index.sort(sort === d3_layout_pieSortByValue ? function(i, j) {
  return values[j] - values[i];
  } : function(i, j) {
  return sort(data[i], data[j]);
  });
  var arcs = [];
  index.forEach(function(i) {
  var d;
  arcs[i] = {
  data: data[i],
  value: d = values[i],
  startAngle: a,
  endAngle: a += d * k
  };
  });
  return arcs;
  }
  var value = Number, sort = d3_layout_pieSortByValue, startAngle = 0, endAngle = 2 * Math.PI;
  pie.value = function(x) {
  if (!arguments.length) return value;
  value = x;
  return pie;
  };
  pie.sort = function(x) {
  if (!arguments.length) return sort;
  sort = x;
  return pie;
  };
  pie.startAngle = function(x) {
  if (!arguments.length) return startAngle;
  startAngle = x;
  return pie;
  };
  pie.endAngle = function(x) {
  if (!arguments.length) return endAngle;
  endAngle = x;
  return pie;
  };
  return pie;
  };
  var d3_layout_pieSortByValue = {};
  d3.layout.stack = function() {
  function stack(data, index) {
  var series = data.map(function(d, i) {
  return values.call(stack, d, i);
  });
  var points = series.map(function(d, i) {
  return d.map(function(v, i) {
  return [ x.call(stack, v, i), y.call(stack, v, i) ];
  });
  });
  var orders = order.call(stack, points, index);
  series = d3.permute(series, orders);
  points = d3.permute(points, orders);
  var offsets = offset.call(stack, points, index);
  var n = series.length, m = series[0].length, i, j, o;
  for (j = 0; j < m; ++j) {
  out.call(stack, series[0][j], o = offsets[j], points[0][j][1]);
  for (i = 1; i < n; ++i) {
  out.call(stack, series[i][j], o += points[i - 1][j][1], points[i][j][1]);
  }
  }
  return data;
  }
  var values = d3_identity, order = d3_layout_stackOrderDefault, offset = d3_layout_stackOffsetZero, out = d3_layout_stackOut, x = d3_layout_stackX, y = d3_layout_stackY;
  stack.values = function(x) {
  if (!arguments.length) return values;
  values = x;
  return stack;
  };
  stack.order = function(x) {
  if (!arguments.length) return order;
  order = typeof x === "function" ? x : d3_layout_stackOrders.get(x) || d3_layout_stackOrderDefault;
  return stack;
  };
  stack.offset = function(x) {
  if (!arguments.length) return offset;
  offset = typeof x === "function" ? x : d3_layout_stackOffsets.get(x) || d3_layout_stackOffsetZero;
  return stack;
  };
  stack.x = function(z) {
  if (!arguments.length) return x;
  x = z;
  return stack;
  };
  stack.y = function(z) {
  if (!arguments.length) return y;
  y = z;
  return stack;
  };
  stack.out = function(z) {
  if (!arguments.length) return out;
  out = z;
  return stack;
  };
  return stack;
  };
  var d3_layout_stackOrders = d3.map({
  "inside-out": function(data) {
  var n = data.length, i, j, max = data.map(d3_layout_stackMaxIndex), sums = data.map(d3_layout_stackReduceSum), index = d3.range(n).sort(function(a, b) {
  return max[a] - max[b];
  }), top = 0, bottom = 0, tops = [], bottoms = [];
  for (i = 0; i < n; ++i) {
  j = index[i];
  if (top < bottom) {
  top += sums[j];
  tops.push(j);
  } else {
  bottom += sums[j];
  bottoms.push(j);
  }
  }
  return bottoms.reverse().concat(tops);
  },
  reverse: function(data) {
  return d3.range(data.length).reverse();
  },
  "default": d3_layout_stackOrderDefault
  });
  var d3_layout_stackOffsets = d3.map({
  silhouette: function(data) {
  var n = data.length, m = data[0].length, sums = [], max = 0, i, j, o, y0 = [];
  for (j = 0; j < m; ++j) {
  for (i = 0, o = 0; i < n; i++) o += data[i][j][1];
  if (o > max) max = o;
  sums.push(o);
  }
  for (j = 0; j < m; ++j) {
  y0[j] = (max - sums[j]) / 2;
  }
  return y0;
  },
  wiggle: function(data) {
  var n = data.length, x = data[0], m = x.length, max = 0, i, j, k, s1, s2, s3, dx, o, o0, y0 = [];
  y0[0] = o = o0 = 0;
  for (j = 1; j < m; ++j) {
  for (i = 0, s1 = 0; i < n; ++i) s1 += data[i][j][1];
  for (i = 0, s2 = 0, dx = x[j][0] - x[j - 1][0]; i < n; ++i) {
  for (k = 0, s3 = (data[i][j][1] - data[i][j - 1][1]) / (2 * dx); k < i; ++k) {
  s3 += (data[k][j][1] - data[k][j - 1][1]) / dx;
  }
  s2 += s3 * data[i][j][1];
  }
  y0[j] = o -= s1 ? s2 / s1 * dx : 0;
  if (o < o0) o0 = o;
  }
  for (j = 0; j < m; ++j) y0[j] -= o0;
  return y0;
  },
  expand: function(data) {
  var n = data.length, m = data[0].length, k = 1 / n, i, j, o, y0 = [];
  for (j = 0; j < m; ++j) {
  for (i = 0, o = 0; i < n; i++) o += data[i][j][1];
  if (o) for (i = 0; i < n; i++) data[i][j][1] /= o; else for (i = 0; i < n; i++) data[i][j][1] = k;
  }
  for (j = 0; j < m; ++j) y0[j] = 0;
  return y0;
  },
  zero: d3_layout_stackOffsetZero
  });
  d3.layout.histogram = function() {
  function histogram(data, i) {
  var bins = [], values = data.map(valuer, this), range = ranger.call(this, values, i), thresholds = binner.call(this, range, values, i), bin, i = -1, n = values.length, m = thresholds.length - 1, k = frequency ? 1 : 1 / n, x;
  while (++i < m) {
  bin = bins[i] = [];
  bin.dx = thresholds[i + 1] - (bin.x = thresholds[i]);
  bin.y = 0;
  }
  if (m > 0) {
  i = -1;
  while (++i < n) {
  x = values[i];
  if (x >= range[0] && x <= range[1]) {
  bin = bins[d3.bisect(thresholds, x, 1, m) - 1];
  bin.y += k;
  bin.push(data[i]);
  }
  }
  }
  return bins;
  }
  var frequency = true, valuer = Number, ranger = d3_layout_histogramRange, binner = d3_layout_histogramBinSturges;
  histogram.value = function(x) {
  if (!arguments.length) return valuer;
  valuer = x;
  return histogram;
  };
  histogram.range = function(x) {
  if (!arguments.length) return ranger;
  ranger = d3_functor(x);
  return histogram;
  };
  histogram.bins = function(x) {
  if (!arguments.length) return binner;
  binner = typeof x === "number" ? function(range) {
  return d3_layout_histogramBinFixed(range, x);
  } : d3_functor(x);
  return histogram;
  };
  histogram.frequency = function(x) {
  if (!arguments.length) return frequency;
  frequency = !!x;
  return histogram;
  };
  return histogram;
  };
  d3.layout.hierarchy = function() {
  function recurse(data, depth, nodes) {
  var childs = children.call(hierarchy, data, depth), node = d3_layout_hierarchyInline ? data : {
  data: data
  };
  node.depth = depth;
  nodes.push(node);
  if (childs && (n = childs.length)) {
  var i = -1, n, c = node.children = [], v = 0, j = depth + 1, d;
  while (++i < n) {
  d = recurse(childs[i], j, nodes);
  d.parent = node;
  c.push(d);
  v += d.value;
  }
  if (sort) c.sort(sort);
  if (value) node.value = v;
  } else if (value) {
  node.value = +value.call(hierarchy, data, depth) || 0;
  }
  return node;
  }
  function revalue(node, depth) {
  var children = node.children, v = 0;
  if (children && (n = children.length)) {
  var i = -1, n, j = depth + 1;
  while (++i < n) v += revalue(children[i], j);
  } else if (value) {
  v = +value.call(hierarchy, d3_layout_hierarchyInline ? node : node.data, depth) || 0;
  }
  if (value) node.value = v;
  return v;
  }
  function hierarchy(d) {
  var nodes = [];
  recurse(d, 0, nodes);
  return nodes;
  }
  var sort = d3_layout_hierarchySort, children = d3_layout_hierarchyChildren, value = d3_layout_hierarchyValue;
  hierarchy.sort = function(x) {
  if (!arguments.length) return sort;
  sort = x;
  return hierarchy;
  };
  hierarchy.children = function(x) {
  if (!arguments.length) return children;
  children = x;
  return hierarchy;
  };
  hierarchy.value = function(x) {
  if (!arguments.length) return value;
  value = x;
  return hierarchy;
  };
  hierarchy.revalue = function(root) {
  revalue(root, 0);
  return root;
  };
  return hierarchy;
  };
  var d3_layout_hierarchyInline = false;
  d3.layout.pack = function() {
  function pack(d, i) {
  var nodes = hierarchy.call(this, d, i), root = nodes[0];
  root.x = 0;
  root.y = 0;
  d3_layout_treeVisitAfter(root, function(d) {
  d.r = Math.sqrt(d.value);
  });
  d3_layout_treeVisitAfter(root, d3_layout_packSiblings);
  var w = size[0], h = size[1], k = Math.max(2 * root.r / w, 2 * root.r / h);
  if (padding > 0) {
  var dr = padding * k / 2;
  d3_layout_treeVisitAfter(root, function(d) {
  d.r += dr;
  });
  d3_layout_treeVisitAfter(root, d3_layout_packSiblings);
  d3_layout_treeVisitAfter(root, function(d) {
  d.r -= dr;
  });
  k = Math.max(2 * root.r / w, 2 * root.r / h);
  }
  d3_layout_packTransform(root, w / 2, h / 2, 1 / k);
  return nodes;
  }
  var hierarchy = d3.layout.hierarchy().sort(d3_layout_packSort), padding = 0, size = [ 1, 1 ];
  pack.size = function(x) {
  if (!arguments.length) return size;
  size = x;
  return pack;
  };
  pack.padding = function(_) {
  if (!arguments.length) return padding;
  padding = +_;
  return pack;
  };
  return d3_layout_hierarchyRebind(pack, hierarchy);
  };
  d3.layout.cluster = function() {
  function cluster(d, i) {
  var nodes = hierarchy.call(this, d, i), root = nodes[0], previousNode, x = 0, kx, ky;
  d3_layout_treeVisitAfter(root, function(node) {
  var children = node.children;
  if (children && children.length) {
  node.x = d3_layout_clusterX(children);
  node.y = d3_layout_clusterY(children);
  } else {
  node.x = previousNode ? x += separation(node, previousNode) : 0;
  node.y = 0;
  previousNode = node;
  }
  });
  var left = d3_layout_clusterLeft(root), right = d3_layout_clusterRight(root), x0 = left.x - separation(left, right) / 2, x1 = right.x + separation(right, left) / 2;
  d3_layout_treeVisitAfter(root, function(node) {
  node.x = (node.x - x0) / (x1 - x0) * size[0];
  node.y = (1 - (root.y ? node.y / root.y : 1)) * size[1];
  });
  return nodes;
  }
  var hierarchy = d3.layout.hierarchy().sort(null).value(null), separation = d3_layout_treeSeparation, size = [ 1, 1 ];
  cluster.separation = function(x) {
  if (!arguments.length) return separation;
  separation = x;
  return cluster;
  };
  cluster.size = function(x) {
  if (!arguments.length) return size;
  size = x;
  return cluster;
  };
  return d3_layout_hierarchyRebind(cluster, hierarchy);
  };
  d3.layout.tree = function() {
  function tree(d, i) {
  function firstWalk(node, previousSibling) {
  var children = node.children, layout = node._tree;
  if (children && (n = children.length)) {
  var n, firstChild = children[0], previousChild, ancestor = firstChild, child, i = -1;
  while (++i < n) {
  child = children[i];
  firstWalk(child, previousChild);
  ancestor = apportion(child, previousChild, ancestor);
  previousChild = child;
  }
  d3_layout_treeShift(node);
  var midpoint = .5 * (firstChild._tree.prelim + child._tree.prelim);
  if (previousSibling) {
  layout.prelim = previousSibling._tree.prelim + separation(node, previousSibling);
  layout.mod = layout.prelim - midpoint;
  } else {
  layout.prelim = midpoint;
  }
  } else {
  if (previousSibling) {
  layout.prelim = previousSibling._tree.prelim + separation(node, previousSibling);
  }
  }
  }
  function secondWalk(node, x) {
  node.x = node._tree.prelim + x;
  var children = node.children;
  if (children && (n = children.length)) {
  var i = -1, n;
  x += node._tree.mod;
  while (++i < n) {
  secondWalk(children[i], x);
  }
  }
  }
  function apportion(node, previousSibling, ancestor) {
  if (previousSibling) {
  var vip = node, vop = node, vim = previousSibling, vom = node.parent.children[0], sip = vip._tree.mod, sop = vop._tree.mod, sim = vim._tree.mod, som = vom._tree.mod, shift;
  while (vim = d3_layout_treeRight(vim), vip = d3_layout_treeLeft(vip), vim && vip) {
  vom = d3_layout_treeLeft(vom);
  vop = d3_layout_treeRight(vop);
  vop._tree.ancestor = node;
  shift = vim._tree.prelim + sim - vip._tree.prelim - sip + separation(vim, vip);
  if (shift > 0) {
  d3_layout_treeMove(d3_layout_treeAncestor(vim, node, ancestor), node, shift);
  sip += shift;
  sop += shift;
  }
  sim += vim._tree.mod;
  sip += vip._tree.mod;
  som += vom._tree.mod;
  sop += vop._tree.mod;
  }
  if (vim && !d3_layout_treeRight(vop)) {
  vop._tree.thread = vim;
  vop._tree.mod += sim - sop;
  }
  if (vip && !d3_layout_treeLeft(vom)) {
  vom._tree.thread = vip;
  vom._tree.mod += sip - som;
  ancestor = node;
  }
  }
  return ancestor;
  }
  var nodes = hierarchy.call(this, d, i), root = nodes[0];
  d3_layout_treeVisitAfter(root, function(node, previousSibling) {
  node._tree = {
  ancestor: node,
  prelim: 0,
  mod: 0,
  change: 0,
  shift: 0,
  number: previousSibling ? previousSibling._tree.number + 1 : 0
  };
  });
  firstWalk(root);
  secondWalk(root, -root._tree.prelim);
  var left = d3_layout_treeSearch(root, d3_layout_treeLeftmost), right = d3_layout_treeSearch(root, d3_layout_treeRightmost), deep = d3_layout_treeSearch(root, d3_layout_treeDeepest), x0 = left.x - separation(left, right) / 2, x1 = right.x + separation(right, left) / 2, y1 = deep.depth || 1;
  d3_layout_treeVisitAfter(root, function(node) {
  node.x = (node.x - x0) / (x1 - x0) * size[0];
  node.y = node.depth / y1 * size[1];
  delete node._tree;
  });
  return nodes;
  }
  var hierarchy = d3.layout.hierarchy().sort(null).value(null), separation = d3_layout_treeSeparation, size = [ 1, 1 ];
  tree.separation = function(x) {
  if (!arguments.length) return separation;
  separation = x;
  return tree;
  };
  tree.size = function(x) {
  if (!arguments.length) return size;
  size = x;
  return tree;
  };
  return d3_layout_hierarchyRebind(tree, hierarchy);
  };
  d3.layout.treemap = function() {
  function scale(children, k) {
  var i = -1, n = children.length, child, area;
  while (++i < n) {
  area = (child = children[i]).value * (k < 0 ? 0 : k);
  child.area = isNaN(area) || area <= 0 ? 0 : area;
  }
  }
  function squarify(node) {
  var children = node.children;
  if (children && children.length) {
  var rect = pad(node), row = [], remaining = children.slice(), child, best = Infinity, score, u = Math.min(rect.dx, rect.dy), n;
  scale(remaining, rect.dx * rect.dy / node.value);
  row.area = 0;
  while ((n = remaining.length) > 0) {
  row.push(child = remaining[n - 1]);
  row.area += child.area;
  if ((score = worst(row, u)) <= best) {
  remaining.pop();
  best = score;
  } else {
  row.area -= row.pop().area;
  position(row, u, rect, false);
  u = Math.min(rect.dx, rect.dy);
  row.length = row.area = 0;
  best = Infinity;
  }
  }
  if (row.length) {
  position(row, u, rect, true);
  row.length = row.area = 0;
  }
  children.forEach(squarify);
  }
  }
  function stickify(node) {
  var children = node.children;
  if (children && children.length) {
  var rect = pad(node), remaining = children.slice(), child, row = [];
  scale(remaining, rect.dx * rect.dy / node.value);
  row.area = 0;
  while (child = remaining.pop()) {
  row.push(child);
  row.area += child.area;
  if (child.z != null) {
  position(row, child.z ? rect.dx : rect.dy, rect, !remaining.length);
  row.length = row.area = 0;
  }
  }
  children.forEach(stickify);
  }
  }
  function worst(row, u) {
  var s = row.area, r, rmax = 0, rmin = Infinity, i = -1, n = row.length;
  while (++i < n) {
  if (!(r = row[i].area)) continue;
  if (r < rmin) rmin = r;
  if (r > rmax) rmax = r;
  }
  s *= s;
  u *= u;
  return s ? Math.max(u * rmax * ratio / s, s / (u * rmin * ratio)) : Infinity;
  }
  function position(row, u, rect, flush) {
  var i = -1, n = row.length, x = rect.x, y = rect.y, v = u ? round(row.area / u) : 0, o;
  if (u == rect.dx) {
  if (flush || v > rect.dy) v = rect.dy;
  while (++i < n) {
  o = row[i];
  o.x = x;
  o.y = y;
  o.dy = v;
  x += o.dx = Math.min(rect.x + rect.dx - x, v ? round(o.area / v) : 0);
  }
  o.z = true;
  o.dx += rect.x + rect.dx - x;
  rect.y += v;
  rect.dy -= v;
  } else {
  if (flush || v > rect.dx) v = rect.dx;
  while (++i < n) {
  o = row[i];
  o.x = x;
  o.y = y;
  o.dx = v;
  y += o.dy = Math.min(rect.y + rect.dy - y, v ? round(o.area / v) : 0);
  }
  o.z = false;
  o.dy += rect.y + rect.dy - y;
  rect.x += v;
  rect.dx -= v;
  }
  }
  function treemap(d) {
  var nodes = stickies || hierarchy(d), root = nodes[0];
  root.x = 0;
  root.y = 0;
  root.dx = size[0];
  root.dy = size[1];
  if (stickies) hierarchy.revalue(root);
  scale([ root ], root.dx * root.dy / root.value);
  (stickies ? stickify : squarify)(root);
  if (sticky) stickies = nodes;
  return nodes;
  }
  var hierarchy = d3.layout.hierarchy(), round = Math.round, size = [ 1, 1 ], padding = null, pad = d3_layout_treemapPadNull, sticky = false, stickies, ratio = .5 * (1 + Math.sqrt(5));
  treemap.size = function(x) {
  if (!arguments.length) return size;
  size = x;
  return treemap;
  };
  treemap.padding = function(x) {
  function padFunction(node) {
  var p = x.call(treemap, node, node.depth);
  return p == null ? d3_layout_treemapPadNull(node) : d3_layout_treemapPad(node, typeof p === "number" ? [ p, p, p, p ] : p);
  }
  function padConstant(node) {
  return d3_layout_treemapPad(node, x);
  }
  if (!arguments.length) return padding;
  var type;
  pad = (padding = x) == null ? d3_layout_treemapPadNull : (type = typeof x) === "function" ? padFunction : type === "number" ? (x = [ x, x, x, x ], padConstant) : padConstant;
  return treemap;
  };
  treemap.round = function(x) {
  if (!arguments.length) return round != Number;
  round = x ? Math.round : Number;
  return treemap;
  };
  treemap.sticky = function(x) {
  if (!arguments.length) return sticky;
  sticky = x;
  stickies = null;
  return treemap;
  };
  treemap.ratio = function(x) {
  if (!arguments.length) return ratio;
  ratio = x;
  return treemap;
  };
  return d3_layout_hierarchyRebind(treemap, hierarchy);
  };
  d3.csv = d3_dsv(",", "text/csv");
  d3.tsv = d3_dsv(" ", "text/tab-separated-values");
  d3.geo = {};
  var d3_geo_radians = Math.PI / 180;
  d3.geo.azimuthal = function() {
  function azimuthal(coordinates) {
  var x1 = coordinates[0] * d3_geo_radians - x0, y1 = coordinates[1] * d3_geo_radians, cx1 = Math.cos(x1), sx1 = Math.sin(x1), cy1 = Math.cos(y1), sy1 = Math.sin(y1), cc = mode !== "orthographic" ? sy0 * sy1 + cy0 * cy1 * cx1 : null, c, k = mode === "stereographic" ? 1 / (1 + cc) : mode === "gnomonic" ? 1 / cc : mode === "equidistant" ? (c = Math.acos(cc), c ? c / Math.sin(c) : 0) : mode === "equalarea" ? Math.sqrt(2 / (1 + cc)) : 1, x = k * cy1 * sx1, y = k * (sy0 * cy1 * cx1 - cy0 * sy1);
  return [ scale * x + translate[0], scale * y + translate[1] ];
  }
  var mode = "orthographic", origin, scale = 200, translate = [ 480, 250 ], x0, y0, cy0, sy0;
  azimuthal.invert = function(coordinates) {
  var x = (coordinates[0] - translate[0]) / scale, y = (coordinates[1] - translate[1]) / scale, p = Math.sqrt(x * x + y * y), c = mode === "stereographic" ? 2 * Math.atan(p) : mode === "gnomonic" ? Math.atan(p) : mode === "equidistant" ? p : mode === "equalarea" ? 2 * Math.asin(.5 * p) : Math.asin(p), sc = Math.sin(c), cc = Math.cos(c);
  return [ (x0 + Math.atan2(x * sc, p * cy0 * cc + y * sy0 * sc)) / d3_geo_radians, Math.asin(cc * sy0 - (p ? y * sc * cy0 / p : 0)) / d3_geo_radians ];
  };
  azimuthal.mode = function(x) {
  if (!arguments.length) return mode;
  mode = x + "";
  return azimuthal;
  };
  azimuthal.origin = function(x) {
  if (!arguments.length) return origin;
  origin = x;
  x0 = origin[0] * d3_geo_radians;
  y0 = origin[1] * d3_geo_radians;
  cy0 = Math.cos(y0);
  sy0 = Math.sin(y0);
  return azimuthal;
  };
  azimuthal.scale = function(x) {
  if (!arguments.length) return scale;
  scale = +x;
  return azimuthal;
  };
  azimuthal.translate = function(x) {
  if (!arguments.length) return translate;
  translate = [ +x[0], +x[1] ];
  return azimuthal;
  };
  return azimuthal.origin([ 0, 0 ]);
  };
  d3.geo.albers = function() {
  function albers(coordinates) {
  var t = n * (d3_geo_radians * coordinates[0] - lng0), p = Math.sqrt(C - 2 * n * Math.sin(d3_geo_radians * coordinates[1])) / n;
  return [ scale * p * Math.sin(t) + translate[0], scale * (p * Math.cos(t) - p0) + translate[1] ];
  }
  function reload() {
  var phi1 = d3_geo_radians * parallels[0], phi2 = d3_geo_radians * parallels[1], lat0 = d3_geo_radians * origin[1], s = Math.sin(phi1), c = Math.cos(phi1);
  lng0 = d3_geo_radians * origin[0];
  n = .5 * (s + Math.sin(phi2));
  C = c * c + 2 * n * s;
  p0 = Math.sqrt(C - 2 * n * Math.sin(lat0)) / n;
  return albers;
  }
  var origin = [ -98, 38 ], parallels = [ 29.5, 45.5 ], scale = 1e3, translate = [ 480, 250 ], lng0, n, C, p0;
  albers.invert = function(coordinates) {
  var x = (coordinates[0] - translate[0]) / scale, y = (coordinates[1] - translate[1]) / scale, p0y = p0 + y, t = Math.atan2(x, p0y), p = Math.sqrt(x * x + p0y * p0y);
  return [ (lng0 + t / n) / d3_geo_radians, Math.asin((C - p * p * n * n) / (2 * n)) / d3_geo_radians ];
  };
  albers.origin = function(x) {
  if (!arguments.length) return origin;
  origin = [ +x[0], +x[1] ];
  return reload();
  };
  albers.parallels = function(x) {
  if (!arguments.length) return parallels;
  parallels = [ +x[0], +x[1] ];
  return reload();
  };
  albers.scale = function(x) {
  if (!arguments.length) return scale;
  scale = +x;
  return albers;
  };
  albers.translate = function(x) {
  if (!arguments.length) return translate;
  translate = [ +x[0], +x[1] ];
  return albers;
  };
  return reload();
  };
  d3.geo.albersUsa = function() {
  function albersUsa(coordinates) {
  var lon = coordinates[0], lat = coordinates[1];
  return (lat > 50 ? alaska : lon < -140 ? hawaii : lat < 21 ? puertoRico : lower48)(coordinates);
  }
  var lower48 = d3.geo.albers();
  var alaska = d3.geo.albers().origin([ -160, 60 ]).parallels([ 55, 65 ]);
  var hawaii = d3.geo.albers().origin([ -160, 20 ]).parallels([ 8, 18 ]);
  var puertoRico = d3.geo.albers().origin([ -60, 10 ]).parallels([ 8, 18 ]);
  albersUsa.scale = function(x) {
  if (!arguments.length) return lower48.scale();
  lower48.scale(x);
  alaska.scale(x * .6);
  hawaii.scale(x);
  puertoRico.scale(x * 1.5);
  return albersUsa.translate(lower48.translate());
  };
  albersUsa.translate = function(x) {
  if (!arguments.length) return lower48.translate();
  var dz = lower48.scale() / 1e3, dx = x[0], dy = x[1];
  lower48.translate(x);
  alaska.translate([ dx - 400 * dz, dy + 170 * dz ]);
  hawaii.translate([ dx - 190 * dz, dy + 200 * dz ]);
  puertoRico.translate([ dx + 580 * dz, dy + 430 * dz ]);
  return albersUsa;
  };
  return albersUsa.scale(lower48.scale());
  };
  d3.geo.bonne = function() {
  function bonne(coordinates) {
  var x = coordinates[0] * d3_geo_radians - x0, y = coordinates[1] * d3_geo_radians - y0;
  if (y1) {
  var p = c1 + y1 - y, E = x * Math.cos(y) / p;
  x = p * Math.sin(E);
  y = p * Math.cos(E) - c1;
  } else {
  x *= Math.cos(y);
  y *= -1;
  }
  return [ scale * x + translate[0], scale * y + translate[1] ];
  }
  var scale = 200, translate = [ 480, 250 ], x0, y0, y1, c1;
  bonne.invert = function(coordinates) {
  var x = (coordinates[0] - translate[0]) / scale, y = (coordinates[1] - translate[1]) / scale;
  if (y1) {
  var c = c1 + y, p = Math.sqrt(x * x + c * c);
  y = c1 + y1 - p;
  x = x0 + p * Math.atan2(x, c) / Math.cos(y);
  } else {
  y *= -1;
  x /= Math.cos(y);
  }
  return [ x / d3_geo_radians, y / d3_geo_radians ];
  };
  bonne.parallel = function(x) {
  if (!arguments.length) return y1 / d3_geo_radians;
  c1 = 1 / Math.tan(y1 = x * d3_geo_radians);
  return bonne;
  };
  bonne.origin = function(x) {
  if (!arguments.length) return [ x0 / d3_geo_radians, y0 / d3_geo_radians ];
  x0 = x[0] * d3_geo_radians;
  y0 = x[1] * d3_geo_radians;
  return bonne;
  };
  bonne.scale = function(x) {
  if (!arguments.length) return scale;
  scale = +x;
  return bonne;
  };
  bonne.translate = function(x) {
  if (!arguments.length) return translate;
  translate = [ +x[0], +x[1] ];
  return bonne;
  };
  return bonne.origin([ 0, 0 ]).parallel(45);
  };
  d3.geo.equirectangular = function() {
  function equirectangular(coordinates) {
  var x = coordinates[0] / 360, y = -coordinates[1] / 360;
  return [ scale * x + translate[0], scale * y + translate[1] ];
  }
  var scale = 500, translate = [ 480, 250 ];
  equirectangular.invert = function(coordinates) {
  var x = (coordinates[0] - translate[0]) / scale, y = (coordinates[1] - translate[1]) / scale;
  return [ 360 * x, -360 * y ];
  };
  equirectangular.scale = function(x) {
  if (!arguments.length) return scale;
  scale = +x;
  return equirectangular;
  };
  equirectangular.translate = function(x) {
  if (!arguments.length) return translate;
  translate = [ +x[0], +x[1] ];
  return equirectangular;
  };
  return equirectangular;
  };
  d3.geo.mercator = function() {
  function mercator(coordinates) {
  var x = coordinates[0] / 360, y = -(Math.log(Math.tan(Math.PI / 4 + coordinates[1] * d3_geo_radians / 2)) / d3_geo_radians) / 360;
  return [ scale * x + translate[0], scale * Math.max(-.5, Math.min(.5, y)) + translate[1] ];
  }
  var scale = 500, translate = [ 480, 250 ];
  mercator.invert = function(coordinates) {
  var x = (coordinates[0] - translate[0]) / scale, y = (coordinates[1] - translate[1]) / scale;
  return [ 360 * x, 2 * Math.atan(Math.exp(-360 * y * d3_geo_radians)) / d3_geo_radians - 90 ];
  };
  mercator.scale = function(x) {
  if (!arguments.length) return scale;
  scale = +x;
  return mercator;
  };
  mercator.translate = function(x) {
  if (!arguments.length) return translate;
  translate = [ +x[0], +x[1] ];
  return mercator;
  };
  return mercator;
  };
  d3.geo.path = function() {
  function path(d, i) {
  if (typeof pointRadius === "function") pointCircle = d3_path_circle(pointRadius.apply(this, arguments));
  pathType(d);
  var result = buffer.length ? buffer.join("") : null;
  buffer = [];
  return result;
  }
  function project(coordinates) {
  return projection(coordinates).join(",");
  }
  function polygonArea(coordinates) {
  var sum = area(coordinates[0]), i = 0, n = coordinates.length;
  while (++i < n) sum -= area(coordinates[i]);
  return sum;
  }
  function polygonCentroid(coordinates) {
  var polygon = d3.geom.polygon(coordinates[0].map(projection)), area = polygon.area(), centroid = polygon.centroid(area < 0 ? (area *= -1, 1) : -1), x = centroid[0], y = centroid[1], z = area, i = 0, n = coordinates.length;
  while (++i < n) {
  polygon = d3.geom.polygon(coordinates[i].map(projection));
  area = polygon.area();
  centroid = polygon.centroid(area < 0 ? (area *= -1, 1) : -1);
  x -= centroid[0];
  y -= centroid[1];
  z -= area;
  }
  return [ x, y, 6 * z ];
  }
  function area(coordinates) {
  return Math.abs(d3.geom.polygon(coordinates.map(projection)).area());
  }
  var pointRadius = 4.5, pointCircle = d3_path_circle(pointRadius), projection = d3.geo.albersUsa(), buffer = [];
  var pathType = d3_geo_type({
  FeatureCollection: function(o) {
  var features = o.features, i = -1, n = features.length;
  while (++i < n) buffer.push(pathType(features[i].geometry));
  },
  Feature: function(o) {
  pathType(o.geometry);
  },
  Point: function(o) {
  buffer.push("M", project(o.coordinates), pointCircle);
  },
  MultiPoint: function(o) {
  var coordinates = o.coordinates, i = -1, n = coordinates.length;
  while (++i < n) buffer.push("M", project(coordinates[i]), pointCircle);
  },
  LineString: function(o) {
  var coordinates = o.coordinates, i = -1, n = coordinates.length;
  buffer.push("M");
  while (++i < n) buffer.push(project(coordinates[i]), "L");
  buffer.pop();
  },
  MultiLineString: function(o) {
  var coordinates = o.coordinates, i = -1, n = coordinates.length, subcoordinates, j, m;
  while (++i < n) {
  subcoordinates = coordinates[i];
  j = -1;
  m = subcoordinates.length;
  buffer.push("M");
  while (++j < m) buffer.push(project(subcoordinates[j]), "L");
  buffer.pop();
  }
  },
  Polygon: function(o) {
  var coordinates = o.coordinates, i = -1, n = coordinates.length, subcoordinates, j, m;
  while (++i < n) {
  subcoordinates = coordinates[i];
  j = -1;
  if ((m = subcoordinates.length - 1) > 0) {
  buffer.push("M");
  while (++j < m) buffer.push(project(subcoordinates[j]), "L");
  buffer[buffer.length - 1] = "Z";
  }
  }
  },
  MultiPolygon: function(o) {
  var coordinates = o.coordinates, i = -1, n = coordinates.length, subcoordinates, j, m, subsubcoordinates, k, p;
  while (++i < n) {
  subcoordinates = coordinates[i];
  j = -1;
  m = subcoordinates.length;
  while (++j < m) {
  subsubcoordinates = subcoordinates[j];
  k = -1;
  if ((p = subsubcoordinates.length - 1) > 0) {
  buffer.push("M");
  while (++k < p) buffer.push(project(subsubcoordinates[k]), "L");
  buffer[buffer.length - 1] = "Z";
  }
  }
  }
  },
  GeometryCollection: function(o) {
  var geometries = o.geometries, i = -1, n = geometries.length;
  while (++i < n) buffer.push(pathType(geometries[i]));
  }
  });
  var areaType = path.area = d3_geo_type({
  FeatureCollection: function(o) {
  var area = 0, features = o.features, i = -1, n = features.length;
  while (++i < n) area += areaType(features[i]);
  return area;
  },
  Feature: function(o) {
  return areaType(o.geometry);
  },
  Polygon: function(o) {
  return polygonArea(o.coordinates);
  },
  MultiPolygon: function(o) {
  var sum = 0, coordinates = o.coordinates, i = -1, n = coordinates.length;
  while (++i < n) sum += polygonArea(coordinates[i]);
  return sum;
  },
  GeometryCollection: function(o) {
  var sum = 0, geometries = o.geometries, i = -1, n = geometries.length;
  while (++i < n) sum += areaType(geometries[i]);
  return sum;
  }
  }, 0);
  var centroidType = path.centroid = d3_geo_type({
  Feature: function(o) {
  return centroidType(o.geometry);
  },
  Polygon: function(o) {
  var centroid = polygonCentroid(o.coordinates);
  return [ centroid[0] / centroid[2], centroid[1] / centroid[2] ];
  },
  MultiPolygon: function(o) {
  var area = 0, coordinates = o.coordinates, centroid, x = 0, y = 0, z = 0, i = -1, n = coordinates.length;
  while (++i < n) {
  centroid = polygonCentroid(coordinates[i]);
  x += centroid[0];
  y += centroid[1];
  z += centroid[2];
  }
  return [ x / z, y / z ];
  }
  });
  path.projection = function(x) {
  projection = x;
  return path;
  };
  path.pointRadius = function(x) {
  if (typeof x === "function") pointRadius = x; else {
  pointRadius = +x;
  pointCircle = d3_path_circle(pointRadius);
  }
  return path;
  };
  return path;
  };
  d3.geo.bounds = function(feature) {
  var left = Infinity, bottom = Infinity, right = -Infinity, top = -Infinity;
  d3_geo_bounds(feature, function(x, y) {
  if (x < left) left = x;
  if (x > right) right = x;
  if (y < bottom) bottom = y;
  if (y > top) top = y;
  });
  return [ [ left, bottom ], [ right, top ] ];
  };
  var d3_geo_boundsTypes = {
  Feature: d3_geo_boundsFeature,
  FeatureCollection: d3_geo_boundsFeatureCollection,
  GeometryCollection: d3_geo_boundsGeometryCollection,
  LineString: d3_geo_boundsLineString,
  MultiLineString: d3_geo_boundsMultiLineString,
  MultiPoint: d3_geo_boundsLineString,
  MultiPolygon: d3_geo_boundsMultiPolygon,
  Point: d3_geo_boundsPoint,
  Polygon: d3_geo_boundsPolygon
  };
  d3.geo.circle = function() {
  function circle() {}
  function visible(point) {
  return arc.distance(point) < radians;
  }
  function clip(coordinates) {
  var i = -1, n = coordinates.length, clipped = [], p0, p1, p2, d0, d1;
  while (++i < n) {
  d1 = arc.distance(p2 = coordinates[i]);
  if (d1 < radians) {
  if (p1) clipped.push(d3_geo_greatArcInterpolate(p1, p2)((d0 - radians) / (d0 - d1)));
  clipped.push(p2);
  p0 = p1 = null;
  } else {
  p1 = p2;
  if (!p0 && clipped.length) {
  clipped.push(d3_geo_greatArcInterpolate(clipped[clipped.length - 1], p1)((radians - d0) / (d1 - d0)));
  p0 = p1;
  }
  }
  d0 = d1;
  }
  p0 = coordinates[0];
  p1 = clipped[0];
  if (p1 && p2[0] === p0[0] && p2[1] === p0[1] && !(p2[0] === p1[0] && p2[1] === p1[1])) {
  clipped.push(p1);
  }
  return resample(clipped);
  }
  function resample(coordinates) {
  var i = 0, n = coordinates.length, j, m, resampled = n ? [ coordinates[0] ] : coordinates, resamples, origin = arc.source();
  while (++i < n) {
  resamples = arc.source(coordinates[i - 1])(coordinates[i]).coordinates;
  for (j = 0, m = resamples.length; ++j < m; ) resampled.push(resamples[j]);
  }
  arc.source(origin);
  return resampled;
  }
  var origin = [ 0, 0 ], degrees = 90 - .01, radians = degrees * d3_geo_radians, arc = d3.geo.greatArc().source(origin).target(d3_identity);
  circle.clip = function(d) {
  if (typeof origin === "function") arc.source(origin.apply(this, arguments));
  return clipType(d) || null;
  };
  var clipType = d3_geo_type({
  FeatureCollection: function(o) {
  var features = o.features.map(clipType).filter(d3_identity);
  return features && (o = Object.create(o), o.features = features, o);
  },
  Feature: function(o) {
  var geometry = clipType(o.geometry);
  return geometry && (o = Object.create(o), o.geometry = geometry, o);
  },
  Point: function(o) {
  return visible(o.coordinates) && o;
  },
  MultiPoint: function(o) {
  var coordinates = o.coordinates.filter(visible);
  return coordinates.length && {
  type: o.type,
  coordinates: coordinates
  };
  },
  LineString: function(o) {
  var coordinates = clip(o.coordinates);
  return coordinates.length && (o = Object.create(o), o.coordinates = coordinates, o);
  },
  MultiLineString: function(o) {
  var coordinates = o.coordinates.map(clip).filter(function(d) {
  return d.length;
  });
  return coordinates.length && (o = Object.create(o), o.coordinates = coordinates, o);
  },
  Polygon: function(o) {
  var coordinates = o.coordinates.map(clip);
  return coordinates[0].length && (o = Object.create(o), o.coordinates = coordinates, o);
  },
  MultiPolygon: function(o) {
  var coordinates = o.coordinates.map(function(d) {
  return d.map(clip);
  }).filter(function(d) {
  return d[0].length;
  });
  return coordinates.length && (o = Object.create(o), o.coordinates = coordinates, o);
  },
  GeometryCollection: function(o) {
  var geometries = o.geometries.map(clipType).filter(d3_identity);
  return geometries.length && (o = Object.create(o), o.geometries = geometries, o);
  }
  });
  circle.origin = function(x) {
  if (!arguments.length) return origin;
  origin = x;
  if (typeof origin !== "function") arc.source(origin);
  return circle;
  };
  circle.angle = function(x) {
  if (!arguments.length) return degrees;
  radians = (degrees = +x) * d3_geo_radians;
  return circle;
  };
  return d3.rebind(circle, arc, "precision");
  };
  d3.geo.greatArc = function() {
  function greatArc() {
  var d = greatArc.distance.apply(this, arguments), t = 0, dt = precision / d, coordinates = [ p0 ];
  while ((t += dt) < 1) coordinates.push(interpolate(t));
  coordinates.push(p1);
  return {
  type: "LineString",
  coordinates: coordinates
  };
  }
  var source = d3_geo_greatArcSource, p0, target = d3_geo_greatArcTarget, p1, precision = 6 * d3_geo_radians, interpolate = d3_geo_greatArcInterpolator();
  greatArc.distance = function() {
  if (typeof source === "function") interpolate.source(p0 = source.apply(this, arguments));
  if (typeof target === "function") interpolate.target(p1 = target.apply(this, arguments));
  return interpolate.distance();
  };
  greatArc.source = function(_) {
  if (!arguments.length) return source;
  source = _;
  if (typeof source !== "function") interpolate.source(p0 = source);
  return greatArc;
  };
  greatArc.target = function(_) {
  if (!arguments.length) return target;
  target = _;
  if (typeof target !== "function") interpolate.target(p1 = target);
  return greatArc;
  };
  greatArc.precision = function(_) {
  if (!arguments.length) return precision / d3_geo_radians;
  precision = _ * d3_geo_radians;
  return greatArc;
  };
  return greatArc;
  };
  d3.geo.greatCircle = d3.geo.circle;
  d3.geom = {};
  d3.geom.contour = function(grid, start) {
  var s = start || d3_geom_contourStart(grid), c = [], x = s[0], y = s[1], dx = 0, dy = 0, pdx = NaN, pdy = NaN, i = 0;
  do {
  i = 0;
  if (grid(x - 1, y - 1)) i += 1;
  if (grid(x, y - 1)) i += 2;
  if (grid(x - 1, y)) i += 4;
  if (grid(x, y)) i += 8;
  if (i === 6) {
  dx = pdy === -1 ? -1 : 1;
  dy = 0;
  } else if (i === 9) {
  dx = 0;
  dy = pdx === 1 ? -1 : 1;
  } else {
  dx = d3_geom_contourDx[i];
  dy = d3_geom_contourDy[i];
  }
  if (dx != pdx && dy != pdy) {
  c.push([ x, y ]);
  pdx = dx;
  pdy = dy;
  }
  x += dx;
  y += dy;
  } while (s[0] != x || s[1] != y);
  return c;
  };
  var d3_geom_contourDx = [ 1, 0, 1, 1, -1, 0, -1, 1, 0, 0, 0, 0, -1, 0, -1, NaN ], d3_geom_contourDy = [ 0, -1, 0, 0, 0, -1, 0, 0, 1, -1, 1, 1, 0, -1, 0, NaN ];
  d3.geom.hull = function(vertices) {
  if (vertices.length < 3) return [];
  var len = vertices.length, plen = len - 1, points = [], stack = [], i, j, h = 0, x1, y1, x2, y2, u, v, a, sp;
  for (i = 1; i < len; ++i) {
  if (vertices[i][1] < vertices[h][1]) {
  h = i;
  } else if (vertices[i][1] == vertices[h][1]) {
  h = vertices[i][0] < vertices[h][0] ? i : h;
  }
  }
  for (i = 0; i < len; ++i) {
  if (i === h) continue;
  y1 = vertices[i][1] - vertices[h][1];
  x1 = vertices[i][0] - vertices[h][0];
  points.push({
  angle: Math.atan2(y1, x1),
  index: i
  });
  }
  points.sort(function(a, b) {
  return a.angle - b.angle;
  });
  a = points[0].angle;
  v = points[0].index;
  u = 0;
  for (i = 1; i < plen; ++i) {
  j = points[i].index;
  if (a == points[i].angle) {
  x1 = vertices[v][0] - vertices[h][0];
  y1 = vertices[v][1] - vertices[h][1];
  x2 = vertices[j][0] - vertices[h][0];
  y2 = vertices[j][1] - vertices[h][1];
  if (x1 * x1 + y1 * y1 >= x2 * x2 + y2 * y2) {
  points[i].index = -1;
  } else {
  points[u].index = -1;
  a = points[i].angle;
  u = i;
  v = j;
  }
  } else {
  a = points[i].angle;
  u = i;
  v = j;
  }
  }
  stack.push(h);
  for (i = 0, j = 0; i < 2; ++j) {
  if (points[j].index !== -1) {
  stack.push(points[j].index);
  i++;
  }
  }
  sp = stack.length;
  for (; j < plen; ++j) {
  if (points[j].index === -1) continue;
  while (!d3_geom_hullCCW(stack[sp - 2], stack[sp - 1], points[j].index, vertices)) {
  --sp;
  }
  stack[sp++] = points[j].index;
  }
  var poly = [];
  for (i = 0; i < sp; ++i) {
  poly.push(vertices[stack[i]]);
  }
  return poly;
  };
  d3.geom.polygon = function(coordinates) {
  coordinates.area = function() {
  var i = 0, n = coordinates.length, a = coordinates[n - 1][0] * coordinates[0][1], b = coordinates[n - 1][1] * coordinates[0][0];
  while (++i < n) {
  a += coordinates[i - 1][0] * coordinates[i][1];
  b += coordinates[i - 1][1] * coordinates[i][0];
  }
  return (b - a) * .5;
  };
  coordinates.centroid = function(k) {
  var i = -1, n = coordinates.length, x = 0, y = 0, a, b = coordinates[n - 1], c;
  if (!arguments.length) k = -1 / (6 * coordinates.area());
  while (++i < n) {
  a = b;
  b = coordinates[i];
  c = a[0] * b[1] - b[0] * a[1];
  x += (a[0] + b[0]) * c;
  y += (a[1] + b[1]) * c;
  }
  return [ x * k, y * k ];
  };
  coordinates.clip = function(subject) {
  var input, i = -1, n = coordinates.length, j, m, a = coordinates[n - 1], b, c, d;
  while (++i < n) {
  input = subject.slice();
  subject.length = 0;
  b = coordinates[i];
  c = input[(m = input.length) - 1];
  j = -1;
  while (++j < m) {
  d = input[j];
  if (d3_geom_polygonInside(d, a, b)) {
  if (!d3_geom_polygonInside(c, a, b)) {
  subject.push(d3_geom_polygonIntersect(c, d, a, b));
  }
  subject.push(d);
  } else if (d3_geom_polygonInside(c, a, b)) {
  subject.push(d3_geom_polygonIntersect(c, d, a, b));
  }
  c = d;
  }
  a = b;
  }
  return subject;
  };
  return coordinates;
  };
  d3.geom.voronoi = function(vertices) {
  var polygons = vertices.map(function() {
  return [];
  });
  d3_voronoi_tessellate(vertices, function(e) {
  var s1, s2, x1, x2, y1, y2;
  if (e.a === 1 && e.b >= 0) {
  s1 = e.ep.r;
  s2 = e.ep.l;
  } else {
  s1 = e.ep.l;
  s2 = e.ep.r;
  }
  if (e.a === 1) {
  y1 = s1 ? s1.y : -1e6;
  x1 = e.c - e.b * y1;
  y2 = s2 ? s2.y : 1e6;
  x2 = e.c - e.b * y2;
  } else {
  x1 = s1 ? s1.x : -1e6;
  y1 = e.c - e.a * x1;
  x2 = s2 ? s2.x : 1e6;
  y2 = e.c - e.a * x2;
  }
  var v1 = [ x1, y1 ], v2 = [ x2, y2 ];
  polygons[e.region.l.index].push(v1, v2);
  polygons[e.region.r.index].push(v1, v2);
  });
  return polygons.map(function(polygon, i) {
  var cx = vertices[i][0], cy = vertices[i][1];
  polygon.forEach(function(v) {
  v.angle = Math.atan2(v[0] - cx, v[1] - cy);
  });
  return polygon.sort(function(a, b) {
  return a.angle - b.angle;
  }).filter(function(d, i) {
  return !i || d.angle - polygon[i - 1].angle > 1e-10;
  });
  });
  };
  var d3_voronoi_opposite = {
  l: "r",
  r: "l"
  };
  d3.geom.delaunay = function(vertices) {
  var edges = vertices.map(function() {
  return [];
  }), triangles = [];
  d3_voronoi_tessellate(vertices, function(e) {
  edges[e.region.l.index].push(vertices[e.region.r.index]);
  });
  edges.forEach(function(edge, i) {
  var v = vertices[i], cx = v[0], cy = v[1];
  edge.forEach(function(v) {
  v.angle = Math.atan2(v[0] - cx, v[1] - cy);
  });
  edge.sort(function(a, b) {
  return a.angle - b.angle;
  });
  for (var j = 0, m = edge.length - 1; j < m; j++) {
  triangles.push([ v, edge[j], edge[j + 1] ]);
  }
  });
  return triangles;
  };
  d3.geom.quadtree = function(points, x1, y1, x2, y2) {
  function insert(n, p, x1, y1, x2, y2) {
  if (isNaN(p.x) || isNaN(p.y)) return;
  if (n.leaf) {
  var v = n.point;
  if (v) {
  if (Math.abs(v.x - p.x) + Math.abs(v.y - p.y) < .01) {
  insertChild(n, p, x1, y1, x2, y2);
  } else {
  n.point = null;
  insertChild(n, v, x1, y1, x2, y2);
  insertChild(n, p, x1, y1, x2, y2);
  }
  } else {
  n.point = p;
  }
  } else {
  insertChild(n, p, x1, y1, x2, y2);
  }
  }
  function insertChild(n, p, x1, y1, x2, y2) {
  var sx = (x1 + x2) * .5, sy = (y1 + y2) * .5, right = p.x >= sx, bottom = p.y >= sy, i = (bottom << 1) + right;
  n.leaf = false;
  n = n.nodes[i] || (n.nodes[i] = d3_geom_quadtreeNode());
  if (right) x1 = sx; else x2 = sx;
  if (bottom) y1 = sy; else y2 = sy;
  insert(n, p, x1, y1, x2, y2);
  }
  var p, i = -1, n = points.length;
  if (n && isNaN(points[0].x)) points = points.map(d3_geom_quadtreePoint);
  if (arguments.length < 5) {
  if (arguments.length === 3) {
  y2 = x2 = y1;
  y1 = x1;
  } else {
  x1 = y1 = Infinity;
  x2 = y2 = -Infinity;
  while (++i < n) {
  p = points[i];
  if (p.x < x1) x1 = p.x;
  if (p.y < y1) y1 = p.y;
  if (p.x > x2) x2 = p.x;
  if (p.y > y2) y2 = p.y;
  }
  var dx = x2 - x1, dy = y2 - y1;
  if (dx > dy) y2 = y1 + dx; else x2 = x1 + dy;
  }
  }
  var root = d3_geom_quadtreeNode();
  root.add = function(p) {
  insert(root, p, x1, y1, x2, y2);
  };
  root.visit = function(f) {
  d3_geom_quadtreeVisit(f, root, x1, y1, x2, y2);
  };
  points.forEach(root.add);
  return root;
  };
  d3.time = {};
  var d3_time = Date, d3_time_daySymbols = [ "Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday" ];
  d3_time_utc.prototype = {
  getDate: function() {
  return this._.getUTCDate();
  },
  getDay: function() {
  return this._.getUTCDay();
  },
  getFullYear: function() {
  return this._.getUTCFullYear();
  },
  getHours: function() {
  return this._.getUTCHours();
  },
  getMilliseconds: function() {
  return this._.getUTCMilliseconds();
  },
  getMinutes: function() {
  return this._.getUTCMinutes();
  },
  getMonth: function() {
  return this._.getUTCMonth();
  },
  getSeconds: function() {
  return this._.getUTCSeconds();
  },
  getTime: function() {
  return this._.getTime();
  },
  getTimezoneOffset: function() {
  return 0;
  },
  valueOf: function() {
  return this._.valueOf();
  },
  setDate: function() {
  d3_time_prototype.setUTCDate.apply(this._, arguments);
  },
  setDay: function() {
  d3_time_prototype.setUTCDay.apply(this._, arguments);
  },
  setFullYear: function() {
  d3_time_prototype.setUTCFullYear.apply(this._, arguments);
  },
  setHours: function() {
  d3_time_prototype.setUTCHours.apply(this._, arguments);
  },
  setMilliseconds: function() {
  d3_time_prototype.setUTCMilliseconds.apply(this._, arguments);
  },
  setMinutes: function() {
  d3_time_prototype.setUTCMinutes.apply(this._, arguments);
  },
  setMonth: function() {
  d3_time_prototype.setUTCMonth.apply(this._, arguments);
  },
  setSeconds: function() {
  d3_time_prototype.setUTCSeconds.apply(this._, arguments);
  },
  setTime: function() {
  d3_time_prototype.setTime.apply(this._, arguments);
  }
  };
  var d3_time_prototype = Date.prototype;
  var d3_time_formatDateTime = "%a %b %e %H:%M:%S %Y", d3_time_formatDate = "%m/%d/%y", d3_time_formatTime = "%H:%M:%S";
  var d3_time_days = d3_time_daySymbols, d3_time_dayAbbreviations = d3_time_days.map(d3_time_formatAbbreviate), d3_time_months = [ "January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December" ], d3_time_monthAbbreviations = d3_time_months.map(d3_time_formatAbbreviate);
  d3.time.format = function(template) {
  function format(date) {
  var string = [], i = -1, j = 0, c, f;
  while (++i < n) {
  if (template.charCodeAt(i) == 37) {
  string.push(template.substring(j, i), (f = d3_time_formats[c = template.charAt(++i)]) ? f(date) : c);
  j = i + 1;
  }
  }
  string.push(template.substring(j, i));
  return string.join("");
  }
  var n = template.length;
  format.parse = function(string) {
  var d = {
  y: 1900,
  m: 0,
  d: 1,
  H: 0,
  M: 0,
  S: 0,
  L: 0
  }, i = d3_time_parse(d, template, string, 0);
  if (i != string.length) return null;
  if ("p" in d) d.H = d.H % 12 + d.p * 12;
  var date = new d3_time;
  date.setFullYear(d.y, d.m, d.d);
  date.setHours(d.H, d.M, d.S, d.L);
  return date;
  };
  format.toString = function() {
  return template;
  };
  return format;
  };
  var d3_time_zfill2 = d3.format("02d"), d3_time_zfill3 = d3.format("03d"), d3_time_zfill4 = d3.format("04d"), d3_time_sfill2 = d3.format("2d");
  var d3_time_dayRe = d3_time_formatRe(d3_time_days), d3_time_dayAbbrevRe = d3_time_formatRe(d3_time_dayAbbreviations), d3_time_monthRe = d3_time_formatRe(d3_time_months), d3_time_monthLookup = d3_time_formatLookup(d3_time_months), d3_time_monthAbbrevRe = d3_time_formatRe(d3_time_monthAbbreviations), d3_time_monthAbbrevLookup = d3_time_formatLookup(d3_time_monthAbbreviations);
  var d3_time_formats = {
  a: function(d) {
  return d3_time_dayAbbreviations[d.getDay()];
  },
  A: function(d) {
  return d3_time_days[d.getDay()];
  },
  b: function(d) {
  return d3_time_monthAbbreviations[d.getMonth()];
  },
  B: function(d) {
  return d3_time_months[d.getMonth()];
  },
  c: d3.time.format(d3_time_formatDateTime),
  d: function(d) {
  return d3_time_zfill2(d.getDate());
  },
  e: function(d) {
  return d3_time_sfill2(d.getDate());
  },
  H: function(d) {
  return d3_time_zfill2(d.getHours());
  },
  I: function(d) {
  return d3_time_zfill2(d.getHours() % 12 || 12);
  },
  j: function(d) {
  return d3_time_zfill3(1 + d3.time.dayOfYear(d));
  },
  L: function(d) {
  return d3_time_zfill3(d.getMilliseconds());
  },
  m: function(d) {
  return d3_time_zfill2(d.getMonth() + 1);
  },
  M: function(d) {
  return d3_time_zfill2(d.getMinutes());
  },
  p: function(d) {
  return d.getHours() >= 12 ? "PM" : "AM";
  },
  S: function(d) {
  return d3_time_zfill2(d.getSeconds());
  },
  U: function(d) {
  return d3_time_zfill2(d3.time.sundayOfYear(d));
  },
  w: function(d) {
  return d.getDay();
  },
  W: function(d) {
  return d3_time_zfill2(d3.time.mondayOfYear(d));
  },
  x: d3.time.format(d3_time_formatDate),
  X: d3.time.format(d3_time_formatTime),
  y: function(d) {
  return d3_time_zfill2(d.getFullYear() % 100);
  },
  Y: function(d) {
  return d3_time_zfill4(d.getFullYear() % 1e4);
  },
  Z: d3_time_zone,
  "%": function(d) {
  return "%";
  }
  };
  var d3_time_parsers = {
  a: d3_time_parseWeekdayAbbrev,
  A: d3_time_parseWeekday,
  b: d3_time_parseMonthAbbrev,
  B: d3_time_parseMonth,
  c: d3_time_parseLocaleFull,
  d: d3_time_parseDay,
  e: d3_time_parseDay,
  H: d3_time_parseHour24,
  I: d3_time_parseHour24,
  L: d3_time_parseMilliseconds,
  m: d3_time_parseMonthNumber,
  M: d3_time_parseMinutes,
  p: d3_time_parseAmPm,
  S: d3_time_parseSeconds,
  x: d3_time_parseLocaleDate,
  X: d3_time_parseLocaleTime,
  y: d3_time_parseYear,
  Y: d3_time_parseFullYear
  };
  var d3_time_numberRe = /^\s*\d+/;
  var d3_time_amPmLookup = d3.map({
  am: 0,
  pm: 1
  });
  d3.time.format.utc = function(template) {
  function format(date) {
  try {
  d3_time = d3_time_utc;
  var utc = new d3_time;
  utc._ = date;
  return local(utc);
  } finally {
  d3_time = Date;
  }
  }
  var local = d3.time.format(template);
  format.parse = function(string) {
  try {
  d3_time = d3_time_utc;
  var date = local.parse(string);
  return date && date._;
  } finally {
  d3_time = Date;
  }
  };
  format.toString = local.toString;
  return format;
  };
  var d3_time_formatIso = d3.time.format.utc("%Y-%m-%dT%H:%M:%S.%LZ");
  d3.time.format.iso = Date.prototype.toISOString ? d3_time_formatIsoNative : d3_time_formatIso;
  d3_time_formatIsoNative.parse = function(string) {
  var date = new Date(string);
  return isNaN(date) ? null : date;
  };
  d3_time_formatIsoNative.toString = d3_time_formatIso.toString;
  d3.time.second = d3_time_interval(function(date) {
  return new d3_time(Math.floor(date / 1e3) * 1e3);
  }, function(date, offset) {
  date.setTime(date.getTime() + Math.floor(offset) * 1e3);
  }, function(date) {
  return date.getSeconds();
  });
  d3.time.seconds = d3.time.second.range;
  d3.time.seconds.utc = d3.time.second.utc.range;
  d3.time.minute = d3_time_interval(function(date) {
  return new d3_time(Math.floor(date / 6e4) * 6e4);
  }, function(date, offset) {
  date.setTime(date.getTime() + Math.floor(offset) * 6e4);
  }, function(date) {
  return date.getMinutes();
  });
  d3.time.minutes = d3.time.minute.range;
  d3.time.minutes.utc = d3.time.minute.utc.range;
  d3.time.hour = d3_time_interval(function(date) {
  var timezone = date.getTimezoneOffset() / 60;
  return new d3_time((Math.floor(date / 36e5 - timezone) + timezone) * 36e5);
  }, function(date, offset) {
  date.setTime(date.getTime() + Math.floor(offset) * 36e5);
  }, function(date) {
  return date.getHours();
  });
  d3.time.hours = d3.time.hour.range;
  d3.time.hours.utc = d3.time.hour.utc.range;
  d3.time.day = d3_time_interval(function(date) {
  var day = new d3_time(1970, 0);
  day.setFullYear(date.getFullYear(), date.getMonth(), date.getDate());
  return day;
  }, function(date, offset) {
  date.setDate(date.getDate() + offset);
  }, function(date) {
  return date.getDate() - 1;
  });
  d3.time.days = d3.time.day.range;
  d3.time.days.utc = d3.time.day.utc.range;
  d3.time.dayOfYear = function(date) {
  var year = d3.time.year(date);
  return Math.floor((date - year - (date.getTimezoneOffset() - year.getTimezoneOffset()) * 6e4) / 864e5);
  };
  d3_time_daySymbols.forEach(function(day, i) {
  day = day.toLowerCase();
  i = 7 - i;
  var interval = d3.time[day] = d3_time_interval(function(date) {
  (date = d3.time.day(date)).setDate(date.getDate() - (date.getDay() + i) % 7);
  return date;
  }, function(date, offset) {
  date.setDate(date.getDate() + Math.floor(offset) * 7);
  }, function(date) {
  var day = d3.time.year(date).getDay();
  return Math.floor((d3.time.dayOfYear(date) + (day + i) % 7) / 7) - (day !== i);
  });
  d3.time[day + "s"] = interval.range;
  d3.time[day + "s"].utc = interval.utc.range;
  d3.time[day + "OfYear"] = function(date) {
  var day = d3.time.year(date).getDay();
  return Math.floor((d3.time.dayOfYear(date) + (day + i) % 7) / 7);
  };
  });
  d3.time.week = d3.time.sunday;
  d3.time.weeks = d3.time.sunday.range;
  d3.time.weeks.utc = d3.time.sunday.utc.range;
  d3.time.weekOfYear = d3.time.sundayOfYear;
  d3.time.month = d3_time_interval(function(date) {
  date = d3.time.day(date);
  date.setDate(1);
  return date;
  }, function(date, offset) {
  date.setMonth(date.getMonth() + offset);
  }, function(date) {
  return date.getMonth();
  });
  d3.time.months = d3.time.month.range;
  d3.time.months.utc = d3.time.month.utc.range;
  d3.time.year = d3_time_interval(function(date) {
  date = d3.time.day(date);
  date.setMonth(0, 1);
  return date;
  }, function(date, offset) {
  date.setFullYear(date.getFullYear() + offset);
  }, function(date) {
  return date.getFullYear();
  });
  d3.time.years = d3.time.year.range;
  d3.time.years.utc = d3.time.year.utc.range;
  var d3_time_scaleSteps = [ 1e3, 5e3, 15e3, 3e4, 6e4, 3e5, 9e5, 18e5, 36e5, 108e5, 216e5, 432e5, 864e5, 1728e5, 6048e5, 2592e6, 7776e6, 31536e6 ];
  var d3_time_scaleLocalMethods = [ [ d3.time.second, 1 ], [ d3.time.second, 5 ], [ d3.time.second, 15 ], [ d3.time.second, 30 ], [ d3.time.minute, 1 ], [ d3.time.minute, 5 ], [ d3.time.minute, 15 ], [ d3.time.minute, 30 ], [ d3.time.hour, 1 ], [ d3.time.hour, 3 ], [ d3.time.hour, 6 ], [ d3.time.hour, 12 ], [ d3.time.day, 1 ], [ d3.time.day, 2 ], [ d3.time.week, 1 ], [ d3.time.month, 1 ], [ d3.time.month, 3 ], [ d3.time.year, 1 ] ];
  var d3_time_scaleLocalFormats = [ [ d3.time.format("%Y"), function(d) {
  return true;
  } ], [ d3.time.format("%B"), function(d) {
  return d.getMonth();
  } ], [ d3.time.format("%b %d"), function(d) {
  return d.getDate() != 1;
  } ], [ d3.time.format("%a %d"), function(d) {
  return d.getDay() && d.getDate() != 1;
  } ], [ d3.time.format("%I %p"), function(d) {
  return d.getHours();
  } ], [ d3.time.format("%I:%M"), function(d) {
  return d.getMinutes();
  } ], [ d3.time.format(":%S"), function(d) {
  return d.getSeconds();
  } ], [ d3.time.format(".%L"), function(d) {
  return d.getMilliseconds();
  } ] ];
  var d3_time_scaleLinear = d3.scale.linear(), d3_time_scaleLocalFormat = d3_time_scaleFormat(d3_time_scaleLocalFormats);
  d3_time_scaleLocalMethods.year = function(extent, m) {
  return d3_time_scaleLinear.domain(extent.map(d3_time_scaleGetYear)).ticks(m).map(d3_time_scaleSetYear);
  };
  d3.time.scale = function() {
  return d3_time_scale(d3.scale.linear(), d3_time_scaleLocalMethods, d3_time_scaleLocalFormat);
  };
  var d3_time_scaleUTCMethods = d3_time_scaleLocalMethods.map(function(m) {
  return [ m[0].utc, m[1] ];
  });
  var d3_time_scaleUTCFormats = [ [ d3.time.format.utc("%Y"), function(d) {
  return true;
  } ], [ d3.time.format.utc("%B"), function(d) {
  return d.getUTCMonth();
  } ], [ d3.time.format.utc("%b %d"), function(d) {
  return d.getUTCDate() != 1;
  } ], [ d3.time.format.utc("%a %d"), function(d) {
  return d.getUTCDay() && d.getUTCDate() != 1;
  } ], [ d3.time.format.utc("%I %p"), function(d) {
  return d.getUTCHours();
  } ], [ d3.time.format.utc("%I:%M"), function(d) {
  return d.getUTCMinutes();
  } ], [ d3.time.format.utc(":%S"), function(d) {
  return d.getUTCSeconds();
  } ], [ d3.time.format.utc(".%L"), function(d) {
  return d.getUTCMilliseconds();
  } ] ];
  var d3_time_scaleUTCFormat = d3_time_scaleFormat(d3_time_scaleUTCFormats);
  d3_time_scaleUTCMethods.year = function(extent, m) {
  return d3_time_scaleLinear.domain(extent.map(d3_time_scaleUTCGetYear)).ticks(m).map(d3_time_scaleUTCSetYear);
  };
  d3.time.scale.utc = function() {
  return d3_time_scale(d3.scale.linear(), d3_time_scaleUTCMethods, d3_time_scaleUTCFormat);
  };
  })();
  /*
  * This file has been modified!
  * I've added a static Tooltip option.
  * - Tom Rees
  * - January 2013
  */
  /**
  *
  * jquery.sparkline.js
  *
  * v2.1
  * (c) Splunk, Inc
  * Contact: Gareth Watts (gareth@splunk.com)
  * http://omnipotent.net/jquery.sparkline/
  *
  * Generates inline sparkline charts from data supplied either to the method
  * or inline in HTML
  *
  * Compatible with Internet Explorer 6.0+ and modern browsers equipped with the canvas tag
  * (Firefox 2.0+, Safari, Opera, etc)
  *
  * License: New BSD License
  *
  * Copyright (c) 2012, Splunk Inc.
  * All rights reserved.
  *
  * Redistribution and use in source and binary forms, with or without modification,
  * are permitted provided that the following conditions are met:
  *
  * * Redistributions of source code must retain the above copyright notice,
  * this list of conditions and the following disclaimer.
  * * Redistributions in binary form must reproduce the above copyright notice,
  * this list of conditions and the following disclaimer in the documentation
  * and/or other materials provided with the distribution.
  * * Neither the name of Splunk Inc nor the names of its contributors may
  * be used to endorse or promote products derived from this software without
  * specific prior written permission.
  *
  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
  * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
  * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT
  * SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
  * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT
  * OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
  * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
  * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  *
  *
  * Usage:
  * $(selector).sparkline(values, options)
  *
  * If values is undefined or set to 'html' then the data values are read from the specified tag:
  * <p>Sparkline: <span class="sparkline">1,4,6,6,8,5,3,5</span></p>
  * $('.sparkline').sparkline();
  * There must be no spaces in the enclosed data set
  *
  * Otherwise values must be an array of numbers or null values
  * <p>Sparkline: <span id="sparkline1">This text replaced if the browser is compatible</span></p>
  * $('#sparkline1').sparkline([1,4,6,6,8,5,3,5])
  * $('#sparkline2').sparkline([1,4,6,null,null,5,3,5])
  *
  * Values can also be specified in an HTML comment, or as a values attribute:
  * <p>Sparkline: <span class="sparkline"><!--1,4,6,6,8,5,3,5 --></span></p>
  * <p>Sparkline: <span class="sparkline" values="1,4,6,6,8,5,3,5"></span></p>
  * $('.sparkline').sparkline();
  *
  * For line charts, x values can also be specified:
  * <p>Sparkline: <span class="sparkline">1:1,2.7:4,3.4:6,5:6,6:8,8.7:5,9:3,10:5</span></p>
  * $('#sparkline1').sparkline([ [1,1], [2.7,4], [3.4,6], [5,6], [6,8], [8.7,5], [9,3], [10,5] ])
  *
  * By default, options should be passed in as teh second argument to the sparkline function:
  * $('.sparkline').sparkline([1,2,3,4], {type: 'bar'})
  *
  * Options can also be set by passing them on the tag itself. This feature is disabled by default though
  * as there's a slight performance overhead:
  * $('.sparkline').sparkline([1,2,3,4], {enableTagOptions: true})
  * <p>Sparkline: <span class="sparkline" sparkType="bar" sparkBarColor="red">loading</span></p>
  * Prefix all options supplied as tag attribute with "spark" (configurable by setting tagOptionPrefix)
  *
  * Supported options:
  * lineColor - Color of the line used for the chart
  * fillColor - Color used to fill in the chart - Set to '' or false for a transparent chart
  * width - Width of the chart - Defaults to 3 times the number of values in pixels
  * height - Height of the chart - Defaults to the height of the containing element
  * chartRangeMin - Specify the minimum value to use for the Y range of the chart - Defaults to the minimum value supplied
  * chartRangeMax - Specify the maximum value to use for the Y range of the chart - Defaults to the maximum value supplied
  * chartRangeClip - Clip out of range values to the max/min specified by chartRangeMin and chartRangeMax
  * chartRangeMinX - Specify the minimum value to use for the X range of the chart - Defaults to the minimum value supplied
  * chartRangeMaxX - Specify the maximum value to use for the X range of the chart - Defaults to the maximum value supplied
  * composite - If true then don't erase any existing chart attached to the tag, but draw
  * another chart over the top - Note that width and height are ignored if an
  * existing chart is detected.
  * tagValuesAttribute - Name of tag attribute to check for data values - Defaults to 'values'
  * enableTagOptions - Whether to check tags for sparkline options
  * tagOptionPrefix - Prefix used for options supplied as tag attributes - Defaults to 'spark'
  * disableHiddenCheck - If set to true, then the plugin will assume that charts will never be drawn into a
  * hidden dom element, avoding a browser reflow
  * disableInteraction - If set to true then all mouseover/click interaction behaviour will be disabled,
  * making the plugin perform much like it did in 1.x
  * disableTooltips - If set to true then tooltips will be disabled - Defaults to false (tooltips enabled)
  * disableHighlight - If set to true then highlighting of selected chart elements on mouseover will be disabled
  * defaults to false (highlights enabled)
  * highlightLighten - Factor to lighten/darken highlighted chart values by - Defaults to 1.4 for a 40% increase
  * tooltipContainer - Specify which DOM element the tooltip should be rendered into - defaults to document.body
  * tooltipClassname - Optional CSS classname to apply to tooltips - If not specified then a default style will be applied
  * tooltipOffsetX - How many pixels away from the mouse pointer to render the tooltip on the X axis
  * tooltipOffsetY - How many pixels away from the mouse pointer to render the tooltip on the r axis
  * tooltipFormatter - Optional callback that allows you to override the HTML displayed in the tooltip
  * callback is given arguments of (sparkline, options, fields)
  * tooltipChartTitle - If specified then the tooltip uses the string specified by this setting as a title
  * tooltipFormat - A format string or SPFormat object (or an array thereof for multiple entries)
  * to control the format of the tooltip
  * tooltipPrefix - A string to prepend to each field displayed in a tooltip
  * tooltipSuffix - A string to append to each field displayed in a tooltip
  * tooltipSkipNull - If true then null values will not have a tooltip displayed (defaults to true)
  * tooltipValueLookups - An object or range map to map field values to tooltip strings
  * (eg. to map -1 to "Lost", 0 to "Draw", and 1 to "Win")
  * numberFormatter - Optional callback for formatting numbers in tooltips
  * numberDigitGroupSep - Character to use for group separator in numbers "1,234" - Defaults to ","
  * numberDecimalMark - Character to use for the decimal point when formatting numbers - Defaults to "."
  * numberDigitGroupCount - Number of digits between group separator - Defaults to 3
  *
  * There are 7 types of sparkline, selected by supplying a "type" option of 'line' (default),
  * 'bar', 'tristate', 'bullet', 'discrete', 'pie' or 'box'
  * line - Line chart. Options:
  * spotColor - Set to '' to not end each line in a circular spot
  * minSpotColor - If set, color of spot at minimum value
  * maxSpotColor - If set, color of spot at maximum value
  * spotRadius - Radius in pixels
  * lineWidth - Width of line in pixels
  * normalRangeMin
  * normalRangeMax - If set draws a filled horizontal bar between these two values marking the "normal"
  * or expected range of values
  * normalRangeColor - Color to use for the above bar
  * drawNormalOnTop - Draw the normal range above the chart fill color if true
  * defaultPixelsPerValue - Defaults to 3 pixels of width for each value in the chart
  * highlightSpotColor - The color to use for drawing a highlight spot on mouseover - Set to null to disable
  * highlightLineColor - The color to use for drawing a highlight line on mouseover - Set to null to disable
  * valueSpots - Specify which points to draw spots on, and in which color. Accepts a range map
  *
  * bar - Bar chart. Options:
  * barColor - Color of bars for postive values
  * negBarColor - Color of bars for negative values
  * zeroColor - Color of bars with zero values
  * nullColor - Color of bars with null values - Defaults to omitting the bar entirely
  * barWidth - Width of bars in pixels
  * colorMap - Optional mappnig of values to colors to override the *BarColor values above
  * can be an Array of values to control the color of individual bars or a range map
  * to specify colors for individual ranges of values
  * barSpacing - Gap between bars in pixels
  * zeroAxis - Centers the y-axis around zero if true
  *
  * tristate - Charts values of win (>0), lose (<0) or draw (=0)
  * posBarColor - Color of win values
  * negBarColor - Color of lose values
  * zeroBarColor - Color of draw values
  * barWidth - Width of bars in pixels
  * barSpacing - Gap between bars in pixels
  * colorMap - Optional mappnig of values to colors to override the *BarColor values above
  * can be an Array of values to control the color of individual bars or a range map
  * to specify colors for individual ranges of values
  *
  * discrete - Options:
  * lineHeight - Height of each line in pixels - Defaults to 30% of the graph height
  * thesholdValue - Values less than this value will be drawn using thresholdColor instead of lineColor
  * thresholdColor
  *
  * bullet - Values for bullet graphs msut be in the order: target, performance, range1, range2, range3, ...
  * options:
  * targetColor - The color of the vertical target marker
  * targetWidth - The width of the target marker in pixels
  * performanceColor - The color of the performance measure horizontal bar
  * rangeColors - Colors to use for each qualitative range background color
  *
  * pie - Pie chart. Options:
  * sliceColors - An array of colors to use for pie slices
  * offset - Angle in degrees to offset the first slice - Try -90 or +90
  * borderWidth - Width of border to draw around the pie chart, in pixels - Defaults to 0 (no border)
  * borderColor - Color to use for the pie chart border - Defaults to #000
  *
  * box - Box plot. Options:
  * raw - Set to true to supply pre-computed plot points as values
  * values should be: low_outlier, low_whisker, q1, median, q3, high_whisker, high_outlier
  * When set to false you can supply any number of values and the box plot will
  * be computed for you. Default is false.
  * showOutliers - Set to true (default) to display outliers as circles
  * outlierIQR - Interquartile range used to determine outliers. Default 1.5
  * boxLineColor - Outline color of the box
  * boxFillColor - Fill color for the box
  * whiskerColor - Line color used for whiskers
  * outlierLineColor - Outline color of outlier circles
  * outlierFillColor - Fill color of the outlier circles
  * spotRadius - Radius of outlier circles
  * medianColor - Line color of the median line
  * target - Draw a target cross hair at the supplied value (default undefined)
  *
  *
  *
  * Examples:
  * $('#sparkline1').sparkline(myvalues, { lineColor: '#f00', fillColor: false });
  * $('.barsparks').sparkline('html', { type:'bar', height:'40px', barWidth:5 });
  * $('#tristate').sparkline([1,1,-1,1,0,0,-1], { type:'tristate' }):
  * $('#discrete').sparkline([1,3,4,5,5,3,4,5], { type:'discrete' });
  * $('#bullet').sparkline([10,12,12,9,7], { type:'bullet' });
  * $('#pie').sparkline([1,1,2], { type:'pie' });
  */
 
  /*jslint regexp: true, browser: true, jquery: true, white: true, nomen: false, plusplus: false, maxerr: 500, indent: 4 */
 
  (function(factory) {
  if(typeof define === 'function' && define.amd) {
  define(['jquery'], factory);
  }
  else {
  factory(jQuery);
  }
  }
  (function($) {
  'use strict';
 
  var UNSET_OPTION = {},
  getDefaults, createClass, SPFormat, clipval, quartile, normalizeValue, normalizeValues,
  remove, isNumber, all, sum, addCSS, ensureArray, formatNumber, RangeMap,
  MouseHandler, Tooltip, barHighlightMixin,
  line, bar, tristate, discrete, bullet, pie, box, defaultStyles, initStyles,
  VShape, VCanvas_base, VCanvas_canvas, VCanvas_vml, pending, shapeCount = 0;
 
  /**
  * Default configuration settings
  */
  getDefaults = function () {
  return {
  // Settings common to most/all chart types
  common: {
  type: 'line',
  lineColor: '#00f',
  fillColor: '#cdf',
  defaultPixelsPerValue: 3,
  width: 'auto',
  height: 'auto',
  composite: false,
  tagValuesAttribute: 'values',
  tagOptionsPrefix: 'spark',
  enableTagOptions: false,
  enableHighlight: true,
  highlightLighten: 1.4,
  tooltipSkipNull: true,
  tooltipPrefix: '',
  tooltipSuffix: '',
  disableHiddenCheck: false,
  numberFormatter: false,
  tooltips: false,
  numberDigitGroupCount: 3,
  numberDigitGroupSep: ',',
  numberDecimalMark: '.',
  disableTooltips: false,
  disableInteraction: false
  },
  // Defaults for line charts
  line: {
  spotColor: '#f80',
  highlightSpotColor: '#5f5',
  highlightLineColor: '#f22',
  spotRadius: 1.5,
  minSpotColor: '#f80',
  maxSpotColor: '#f80',
  lineWidth: 1,
  normalRangeMin: undefined,
  normalRangeMax: undefined,
  normalRangeColor: '#ccc',
  drawNormalOnTop: false,
  chartRangeMin: undefined,
  chartRangeMax: undefined,
  chartRangeMinX: undefined,
  chartRangeMaxX: undefined,
  tooltipFormat: new SPFormat('<span style="color: {{color}}">&#9679;</span> {{prefix}}{{y}}{{suffix}}')
  },
  // Defaults for bar charts
  bar: {
  barColor: '#3366cc',
  negBarColor: '#f44',
  stackedBarColor: ['#3366cc', '#dc3912', '#ff9900', '#109618', '#66aa00',
  '#dd4477', '#0099c6', '#990099'],
  zeroColor: undefined,
  nullColor: undefined,
  zeroAxis: true,
  barWidth: 4,
  barSpacing: 1,
  chartRangeMax: undefined,
  chartRangeMin: undefined,
  chartRangeClip: false,
  colorMap: undefined,
  tooltipFormat: new SPFormat('<span style="color: {{color}}">&#9679;</span> {{prefix}}{{value}}{{suffix}}')
  },
  // Defaults for tristate charts
  tristate: {
  barWidth: 4,
  barSpacing: 1,
  posBarColor: '#6f6',
  negBarColor: '#f44',
  zeroBarColor: '#999',
  colorMap: {},
  tooltipFormat: new SPFormat('<span style="color: {{color}}">&#9679;</span> {{value:map}}'),
  tooltipValueLookups: { map: { '-1': 'Loss', '0': 'Draw', '1': 'Win' } }
  },
  // Defaults for discrete charts
  discrete: {
  lineHeight: 'auto',
  thresholdColor: undefined,
  thresholdValue: 0,
  chartRangeMax: undefined,
  chartRangeMin: undefined,
  chartRangeClip: false,
  tooltipFormat: new SPFormat('{{prefix}}{{value}}{{suffix}}')
  },
  // Defaults for bullet charts
  bullet: {
  targetColor: '#f33',
  targetWidth: 3, // width of the target bar in pixels
  performanceColor: '#33f',
  rangeColors: ['#d3dafe', '#a8b6ff', '#7f94ff'],
  base: undefined, // set this to a number to change the base start number
  tooltipFormat: new SPFormat('{{fieldkey:fields}} - {{value}}'),
  tooltipValueLookups: { fields: {r: 'Range', p: 'Performance', t: 'Target'} }
  },
  // Defaults for pie charts
  pie: {
  offset: 0,
  sliceColors: ['#3366cc', '#dc3912', '#ff9900', '#109618', '#66aa00',
  '#dd4477', '#0099c6', '#990099'],
  borderWidth: 0,
  borderColor: '#000',
  tooltipFormat: new SPFormat('<span style="color: {{color}}">&#9679;</span> {{value}} ({{percent.1}}%)')
  },
  // Defaults for box plots
  box: {
  raw: false,
  boxLineColor: '#000',
  boxFillColor: '#cdf',
  whiskerColor: '#000',
  outlierLineColor: '#333',
  outlierFillColor: '#fff',
  medianColor: '#f00',
  showOutliers: true,
  outlierIQR: 1.5,
  spotRadius: 1.5,
  target: undefined,
  targetColor: '#4a2',
  chartRangeMax: undefined,
  chartRangeMin: undefined,
  tooltipFormat: new SPFormat('{{field:fields}}: {{value}}'),
  tooltipFormatFieldlistKey: 'field',
  tooltipValueLookups: { fields: { lq: 'Lower Quartile', med: 'Median',
  uq: 'Upper Quartile', lo: 'Left Outlier', ro: 'Right Outlier',
  lw: 'Left Whisker', rw: 'Right Whisker'} }
  }
  };
  };
 
  // You can have tooltips use a css class other than jqstooltip by specifying tooltipClassname
  defaultStyles = '.jqstooltip { ' +
  'position: absolute;' +
  'left: 0px;' +
  'top: 0px;' +
  'visibility: hidden;' +
  'background: rgb(0, 0, 0) transparent;' +
  'background-color: rgba(0,0,0,0.6);' +
  'filter:progid:DXImageTransform.Microsoft.gradient(startColorstr=#99000000, endColorstr=#99000000);' +
  '-ms-filter: "progid:DXImageTransform.Microsoft.gradient(startColorstr=#99000000, endColorstr=#99000000)";' +
  'color: white;' +
  'font: 10px arial, san serif;' +
  'text-align: left;' +
  'white-space: nowrap;' +
  'padding: 5px;' +
  'border: 1px solid white;' +
  'z-index: 10000;' +
  '}' +
  '.jqsfield { ' +
  'color: white;' +
  'font: 10px arial, san serif;' +
  'text-align: left;' +
  '}';
 
  /**
  * Utilities
  */
 
  createClass = function (/* [baseclass, [mixin, ...]], definition */) {
  var Class, args;
  Class = function () {
  this.init.apply(this, arguments);
  };
  if (arguments.length > 1) {
  if (arguments[0]) {
  Class.prototype = $.extend(new arguments[0](), arguments[arguments.length - 1]);
  Class._super = arguments[0].prototype;
  } else {
  Class.prototype = arguments[arguments.length - 1];
  }
  if (arguments.length > 2) {
  args = Array.prototype.slice.call(arguments, 1, -1);
  args.unshift(Class.prototype);
  $.extend.apply($, args);
  }
  } else {
  Class.prototype = arguments[0];
  }
  Class.prototype.cls = Class;
  return Class;
  };
 
  /**
  * Wraps a format string for tooltips
  * {{x}}
  * {{x.2}
  * {{x:months}}
  */
  $.SPFormatClass = SPFormat = createClass({
  fre: /\{\{([\w.]+?)(:(.+?))?\}\}/g,
  precre: /(\w+)\.(\d+)/,
 
  init: function (format, fclass) {
  this.format = format;
  this.fclass = fclass;
  },
 
  render: function (fieldset, lookups, options) {
  var self = this,
  fields = fieldset,
  match, token, lookupkey, fieldvalue, prec;
  return this.format.replace(this.fre, function () {
  var lookup;
  token = arguments[1];
  lookupkey = arguments[3];
  match = self.precre.exec(token);
  if (match) {
  prec = match[2];
  token = match[1];
  } else {
  prec = false;
  }
  fieldvalue = fields[token];
  if (fieldvalue === undefined) {
  return '';
  }
  if (lookupkey && lookups && lookups[lookupkey]) {
  lookup = lookups[lookupkey];
  if (lookup.get) { // RangeMap
  return lookups[lookupkey].get(fieldvalue) || fieldvalue;
  } else {
  return lookups[lookupkey][fieldvalue] || fieldvalue;
  }
  }
  if (isNumber(fieldvalue)) {
  if (options.get('tooltips')) {
  var tooltipArray = options.get('tooltips').split(',');
  fieldvalue = tooltipArray[ fields['x'] ];
  }
  else if (options.get('numberFormatter')) {
  fieldvalue = options.get('numberFormatter')(fieldvalue);
  } else {
  fieldvalue = formatNumber(fieldvalue, prec,
  options.get('numberDigitGroupCount'),
  options.get('numberDigitGroupSep'),
  options.get('numberDecimalMark'));
  }
  }
  return fieldvalue;
  });
  }
  });
 
  // convience method to avoid needing the new operator
  $.spformat = function(format, fclass) {
  return new SPFormat(format, fclass);
  };
 
  clipval = function (val, min, max) {
  if (val < min) {
  return min;
  }
  if (val > max) {
  return max;
  }
  return val;
  };
 
  quartile = function (values, q) {
  var vl;
  if (q === 2) {
  vl = Math.floor(values.length / 2);
  return values.length % 2 ? values[vl] : (values[vl-1] + values[vl]) / 2;
  } else {
  if (values.length % 2 ) { // odd
  vl = (values.length * q + q) / 4;
  return vl % 1 ? (values[Math.floor(vl)] + values[Math.floor(vl) - 1]) / 2 : values[vl-1];
  } else { //even
  vl = (values.length * q + 2) / 4;
  return vl % 1 ? (values[Math.floor(vl)] + values[Math.floor(vl) - 1]) / 2 : values[vl-1];
 
  }
  }
  };
 
  normalizeValue = function (val) {
  var nf;
  switch (val) {
  case 'undefined':
  val = undefined;
  break;
  case 'null':
  val = null;
  break;
  case 'true':
  val = true;
  break;
  case 'false':
  val = false;
  break;
  default:
  nf = parseFloat(val);
  if (val == nf) {
  val = nf;
  }
  }
  return val;
  };
 
  normalizeValues = function (vals) {
  var i, result = [];
  for (i = vals.length; i--;) {
  result[i] = normalizeValue(vals[i]);
  }
  return result;
  };
 
  remove = function (vals, filter) {
  var i, vl, result = [];
  for (i = 0, vl = vals.length; i < vl; i++) {
  if (vals[i] !== filter) {
  result.push(vals[i]);
  }
  }
  return result;
  };
 
  isNumber = function (num) {
  return !isNaN(parseFloat(num)) && isFinite(num);
  };
 
  formatNumber = function (num, prec, groupsize, groupsep, decsep) {
  var p, i;
  num = (prec === false ? parseFloat(num).toString() : num.toFixed(prec)).split('');
  p = (p = $.inArray('.', num)) < 0 ? num.length : p;
  if (p < num.length) {
  num[p] = decsep;
  }
  for (i = p - groupsize; i > 0; i -= groupsize) {
  num.splice(i, 0, groupsep);
  }
  return num.join('');
  };
 
  // determine if all values of an array match a value
  // returns true if the array is empty
  all = function (val, arr, ignoreNull) {
  var i;
  for (i = arr.length; i--; ) {
  if (ignoreNull && arr[i] === null) continue;
  if (arr[i] !== val) {
  return false;
  }
  }
  return true;
  };
 
  // sums the numeric values in an array, ignoring other values
  sum = function (vals) {
  var total = 0, i;
  for (i = vals.length; i--;) {
  total += typeof vals[i] === 'number' ? vals[i] : 0;
  }
  return total;
  };
 
  ensureArray = function (val) {
  return $.isArray(val) ? val : [val];
  };
 
  // http://paulirish.com/2008/bookmarklet-inject-new-css-rules/
  addCSS = function(css) {
  var tag;
  //if ('\v' == 'v') /* ie only */ {
  if (document.createStyleSheet) {
  document.createStyleSheet().cssText = css;
  } else {
  tag = document.createElement('style');
  tag.type = 'text/css';
  document.getElementsByTagName('head')[0].appendChild(tag);
  tag[(typeof document.body.style.WebkitAppearance == 'string') /* webkit only */ ? 'innerText' : 'innerHTML'] = css;
  }
  };
 
  // Provide a cross-browser interface to a few simple drawing primitives
  $.fn.simpledraw = function (width, height, useExisting, interact) {
  var target, mhandler;
  if (useExisting && (target = this.data('_jqs_vcanvas'))) {
  return target;
  }
  if (width === undefined) {
  width = $(this).innerWidth();
  }
  if (height === undefined) {
  height = $(this).innerHeight();
  }
  if ($.browser.hasCanvas) {
  target = new VCanvas_canvas(width, height, this, interact);
  } else if ($.browser.msie) {
  target = new VCanvas_vml(width, height, this);
  } else {
  return false;
  }
  mhandler = $(this).data('_jqs_mhandler');
  if (mhandler) {
  mhandler.registerCanvas(target);
  }
  return target;
  };
 
  $.fn.cleardraw = function () {
  var target = this.data('_jqs_vcanvas');
  if (target) {
  target.reset();
  }
  };
 
  $.RangeMapClass = RangeMap = createClass({
  init: function (map) {
  var key, range, rangelist = [];
  for (key in map) {
  if (map.hasOwnProperty(key) && typeof key === 'string' && key.indexOf(':') > -1) {
  range = key.split(':');
  range[0] = range[0].length === 0 ? -Infinity : parseFloat(range[0]);
  range[1] = range[1].length === 0 ? Infinity : parseFloat(range[1]);
  range[2] = map[key];
  rangelist.push(range);
  }
  }
  this.map = map;
  this.rangelist = rangelist || false;
  },
 
  get: function (value) {
  var rangelist = this.rangelist,
  i, range, result;
  if ((result = this.map[value]) !== undefined) {
  return result;
  }
  if (rangelist) {
  for (i = rangelist.length; i--;) {
  range = rangelist[i];
  if (range[0] <= value && range[1] >= value) {
  return range[2];
  }
  }
  }
  return undefined;
  }
  });
 
  // Convenience function
  $.range_map = function(map) {
  return new RangeMap(map);
  };
 
  MouseHandler = createClass({
  init: function (el, options) {
  var $el = $(el);
  this.$el = $el;
  this.options = options;
  this.currentPageX = 0;
  this.currentPageY = 0;
  this.el = el;
  this.splist = [];
  this.tooltip = null;
  this.over = false;
  this.displayTooltips = !options.get('disableTooltips');
  this.highlightEnabled = !options.get('disableHighlight');
  },
 
  registerSparkline: function (sp) {
  this.splist.push(sp);
  if (this.over) {
  this.updateDisplay();
  }
  },
 
  registerCanvas: function (canvas) {
  var $canvas = $(canvas.canvas);
  this.canvas = canvas;
  this.$canvas = $canvas;
  $canvas.mouseenter($.proxy(this.mouseenter, this));
  $canvas.mouseleave($.proxy(this.mouseleave, this));
  $canvas.click($.proxy(this.mouseclick, this));
  },
 
  reset: function (removeTooltip) {
  this.splist = [];
  if (this.tooltip && removeTooltip) {
  this.tooltip.remove();
  this.tooltip = undefined;
  }
  },
 
  mouseclick: function (e) {
  var clickEvent = $.Event('sparklineClick');
  clickEvent.originalEvent = e;
  clickEvent.sparklines = this.splist;
  this.$el.trigger(clickEvent);
  },
 
  mouseenter: function (e) {
  $(document.body).unbind('mousemove.jqs');
  $(document.body).bind('mousemove.jqs', $.proxy(this.mousemove, this));
  this.over = true;
  this.currentPageX = e.pageX;
  this.currentPageY = e.pageY;
  this.currentEl = e.target;
  if (!this.tooltip && this.displayTooltips) {
  this.tooltip = new Tooltip(this.options);
  this.tooltip.updatePosition(e.pageX, e.pageY);
  }
  this.updateDisplay();
  },
 
  mouseleave: function () {
  $(document.body).unbind('mousemove.jqs');
  var splist = this.splist,
  spcount = splist.length,
  needsRefresh = false,
  sp, i;
  this.over = false;
  this.currentEl = null;
 
  if (this.tooltip) {
  this.tooltip.remove();
  this.tooltip = null;
  }
 
  for (i = 0; i < spcount; i++) {
  sp = splist[i];
  if (sp.clearRegionHighlight()) {
  needsRefresh = true;
  }
  }
 
  if (needsRefresh) {
  this.canvas.render();
  }
  },
 
  mousemove: function (e) {
  this.currentPageX = e.pageX;
  this.currentPageY = e.pageY;
  this.currentEl = e.target;
  if (this.tooltip) {
  this.tooltip.updatePosition(e.pageX, e.pageY);
  }
  this.updateDisplay();
  },
 
  updateDisplay: function () {
  var splist = this.splist,
  spcount = splist.length,
  needsRefresh = false,
  offset = this.$canvas.offset(),
  localX = this.currentPageX - offset.left,
  localY = this.currentPageY - offset.top,
  tooltiphtml, sp, i, result, changeEvent;
  if (!this.over) {
  return;
  }
  for (i = 0; i < spcount; i++) {
  sp = splist[i];
  result = sp.setRegionHighlight(this.currentEl, localX, localY);
  if (result) {
  needsRefresh = true;
  }
  }
  if (needsRefresh) {
  changeEvent = $.Event('sparklineRegionChange');
  changeEvent.sparklines = this.splist;
  this.$el.trigger(changeEvent);
  if (this.tooltip) {
  tooltiphtml = '';
  for (i = 0; i < spcount; i++) {
  sp = splist[i];
  tooltiphtml += sp.getCurrentRegionTooltip();
  }
  this.tooltip.setContent(tooltiphtml);
  }
  if (!this.disableHighlight) {
  this.canvas.render();
  }
  }
  if (result === null) {
  this.mouseleave();
  }
  }
  });
 
 
  Tooltip = createClass({
  sizeStyle: 'position: static !important;' +
  'display: block !important;' +
  'visibility: hidden !important;' +
  'float: left !important;',
 
  init: function (options) {
  var tooltipClassname = options.get('tooltipClassname', 'jqstooltip'),
  sizetipStyle = this.sizeStyle,
  offset;
  this.container = options.get('tooltipContainer') || document.body;
  this.tooltipOffsetX = options.get('tooltipOffsetX', 10);
  this.tooltipOffsetY = options.get('tooltipOffsetY', 12);
  // remove any previous lingering tooltip
  $('#jqssizetip').remove();
  $('#jqstooltip').remove();
  this.sizetip = $('<div/>', {
  id: 'jqssizetip',
  style: sizetipStyle,
  'class': tooltipClassname
  });
  this.tooltip = $('<div/>', {
  id: 'jqstooltip',
  'class': tooltipClassname
  }).appendTo(this.container);
  // account for the container's location
  offset = this.tooltip.offset();
  this.offsetLeft = offset.left;
  this.offsetTop = offset.top;
  this.hidden = true;
  $(window).unbind('resize.jqs scroll.jqs');
  $(window).bind('resize.jqs scroll.jqs', $.proxy(this.updateWindowDims, this));
  this.updateWindowDims();
  },
 
  updateWindowDims: function () {
  this.scrollTop = $(window).scrollTop();
  this.scrollLeft = $(window).scrollLeft();
  this.scrollRight = this.scrollLeft + $(window).width();
  this.updatePosition();
  },
 
  getSize: function (content) {
  this.sizetip.html(content).appendTo(this.container);
  this.width = this.sizetip.width() + 1;
  this.height = this.sizetip.height();
  this.sizetip.remove();
  },
 
  setContent: function (content) {
  if (!content) {
  this.tooltip.css('visibility', 'hidden');
  this.hidden = true;
  return;
  }
  this.getSize(content);
  this.tooltip.html(content)
  .css({
  'width': this.width,
  'height': this.height,
  'visibility': 'visible'
  });
  if (this.hidden) {
  this.hidden = false;
  this.updatePosition();
  }
  },
 
  updatePosition: function (x, y) {
  if (x === undefined) {
  if (this.mousex === undefined) {
  return;
  }
  x = this.mousex - this.offsetLeft;
  y = this.mousey - this.offsetTop;
 
  } else {
  this.mousex = x = x - this.offsetLeft;
  this.mousey = y = y - this.offsetTop;
  }
  if (!this.height || !this.width || this.hidden) {
  return;
  }
 
  y -= this.height + this.tooltipOffsetY;
  x += this.tooltipOffsetX;
 
  if (y < this.scrollTop) {
  y = this.scrollTop;
  }
  if (x < this.scrollLeft) {
  x = this.scrollLeft;
  } else if (x + this.width > this.scrollRight) {
  x = this.scrollRight - this.width;
  }
 
  this.tooltip.css({
  'left': x,
  'top': y
  });
  },
 
  remove: function () {
  this.tooltip.remove();
  this.sizetip.remove();
  this.sizetip = this.tooltip = undefined;
  $(window).unbind('resize.jqs scroll.jqs');
  }
  });
 
  initStyles = function() {
  addCSS(defaultStyles);
  };
 
  $(initStyles);
 
  pending = [];
  $.fn.sparkline = function (userValues, userOptions) {
  return this.each(function () {
  var options = new $.fn.sparkline.options(this, userOptions),
  $this = $(this),
  render, i;
  render = function () {
  var values, width, height, tmp, mhandler, sp, vals;
  if (userValues === 'html' || userValues === undefined) {
  vals = this.getAttribute(options.get('tagValuesAttribute'));
  if (vals === undefined || vals === null) {
  vals = $this.html();
  }
  values = vals.replace(/(^\s*<!--)|(-->\s*$)|\s+/g, '').split(',');
  } else {
  values = userValues;
  }
 
  width = options.get('width') === 'auto' ? values.length * options.get('defaultPixelsPerValue') : options.get('width');
  if (options.get('height') === 'auto') {
  if (!options.get('composite') || !$.data(this, '_jqs_vcanvas')) {
  // must be a better way to get the line height
  tmp = document.createElement('span');
  tmp.innerHTML = 'a';
  $this.html(tmp);
  height = $(tmp).innerHeight() || $(tmp).height();
  $(tmp).remove();
  tmp = null;
  }
  } else {
  height = options.get('height');
  }
 
  if (!options.get('disableInteraction')) {
  mhandler = $.data(this, '_jqs_mhandler');
  if (!mhandler) {
  mhandler = new MouseHandler(this, options);
  $.data(this, '_jqs_mhandler', mhandler);
  } else if (!options.get('composite')) {
  mhandler.reset();
  }
  } else {
  mhandler = false;
  }
 
  if (options.get('composite') && !$.data(this, '_jqs_vcanvas')) {
  if (!$.data(this, '_jqs_errnotify')) {
  alert('Attempted to attach a composite sparkline to an element with no existing sparkline');
  $.data(this, '_jqs_errnotify', true);
  }
  return;
  }
 
  sp = new $.fn.sparkline[options.get('type')](this, values, options, width, height);
 
  sp.render();
 
  if (mhandler) {
  mhandler.registerSparkline(sp);
  }
  };
  // jQuery 1.3.0 completely changed the meaning of :hidden :-/
  if (($(this).html() && !options.get('disableHiddenCheck') && $(this).is(':hidden')) || ($.fn.jquery < '1.3.0' && $(this).parents().is(':hidden')) || !$(this).parents('body').length) {
  if (!options.get('composite') && $.data(this, '_jqs_pending')) {
  // remove any existing references to the element
  for (i = pending.length; i; i--) {
  if (pending[i - 1][0] == this) {
  pending.splice(i - 1, 1);
  }
  }
  }
  pending.push([this, render]);
  $.data(this, '_jqs_pending', true);
  } else {
  render.call(this);
  }
  });
  };
 
  $.fn.sparkline.defaults = getDefaults();
 
 
  $.sparkline_display_visible = function () {
  var el, i, pl;
  var done = [];
  for (i = 0, pl = pending.length; i < pl; i++) {
  el = pending[i][0];
  if ($(el).is(':visible') && !$(el).parents().is(':hidden')) {
  pending[i][1].call(el);
  $.data(pending[i][0], '_jqs_pending', false);
  done.push(i);
  } else if (!$(el).closest('html').length && !$.data(el, '_jqs_pending')) {
  // element has been inserted and removed from the DOM
  // If it was not yet inserted into the dom then the .data request
  // will return true.
  // removing from the dom causes the data to be removed.
  $.data(pending[i][0], '_jqs_pending', false);
  done.push(i);
  }
  }
  for (i = done.length; i; i--) {
  pending.splice(done[i - 1], 1);
  }
  };
 
 
  /**
  * User option handler
  */
  $.fn.sparkline.options = createClass({
  init: function (tag, userOptions) {
  var extendedOptions, defaults, base, tagOptionType;
  this.userOptions = userOptions = userOptions || {};
  this.tag = tag;
  this.tagValCache = {};
  defaults = $.fn.sparkline.defaults;
  base = defaults.common;
  this.tagOptionsPrefix = userOptions.enableTagOptions && (userOptions.tagOptionsPrefix || base.tagOptionsPrefix);
 
  tagOptionType = this.getTagSetting('type');
  if (tagOptionType === UNSET_OPTION) {
  extendedOptions = defaults[userOptions.type || base.type];
  } else {
  extendedOptions = defaults[tagOptionType];
  }
  this.mergedOptions = $.extend({}, base, extendedOptions, userOptions);
  },
 
 
  getTagSetting: function (key) {
  var prefix = this.tagOptionsPrefix,
  val, i, pairs, keyval;
  if (prefix === false || prefix === undefined) {
  return UNSET_OPTION;
  }
  if (this.tagValCache.hasOwnProperty(key)) {
  val = this.tagValCache.key;
  } else {
  val = this.tag.getAttribute(prefix + key);
  if (val === undefined || val === null) {
  val = UNSET_OPTION;
  } else if (val.substr(0, 1) === '[') {
  val = val.substr(1, val.length - 2).split(',');
  for (i = val.length; i--;) {
  val[i] = normalizeValue(val[i].replace(/(^\s*)|(\s*$)/g, ''));
  }
  } else if (val.substr(0, 1) === '{') {
  pairs = val.substr(1, val.length - 2).split(',');
  val = {};
  for (i = pairs.length; i--;) {
  keyval = pairs[i].split(':', 2);
  val[keyval[0].replace(/(^\s*)|(\s*$)/g, '')] = normalizeValue(keyval[1].replace(/(^\s*)|(\s*$)/g, ''));
  }
  } else {
  val = normalizeValue(val);
  }
  this.tagValCache.key = val;
  }
  return val;
  },
 
  get: function (key, defaultval) {
  var tagOption = this.getTagSetting(key),
  result;
  if (tagOption !== UNSET_OPTION) {
  return tagOption;
  }
  return (result = this.mergedOptions[key]) === undefined ? defaultval : result;
  }
  });
 
 
  $.fn.sparkline._base = createClass({
  disabled: false,
 
  init: function (el, values, options, width, height) {
  this.el = el;
  this.$el = $(el);
  this.values = values;
  this.options = options;
  this.width = width;
  this.height = height;
  this.currentRegion = undefined;
  },
 
  /**
  * Setup the canvas
  */
  initTarget: function () {
  var interactive = !this.options.get('disableInteraction');
  if (!(this.target = this.$el.simpledraw(this.width, this.height, this.options.get('composite'), interactive))) {
  this.disabled = true;
  } else {
  this.canvasWidth = this.target.pixelWidth;
  this.canvasHeight = this.target.pixelHeight;
  }
  },
 
  /**
  * Actually render the chart to the canvas
  */
  render: function () {
  if (this.disabled) {
  this.el.innerHTML = '';
  return false;
  }
  return true;
  },
 
  /**
  * Return a region id for a given x/y co-ordinate
  */
  getRegion: function (x, y) {
  },
 
  /**
  * Highlight an item based on the moused-over x,y co-ordinate
  */
  setRegionHighlight: function (el, x, y) {
  var currentRegion = this.currentRegion,
  highlightEnabled = !this.options.get('disableHighlight'),
  newRegion;
  if (x > this.canvasWidth || y > this.canvasHeight || x < 0 || y < 0) {
  return null;
  }
  newRegion = this.getRegion(el, x, y);
  if (currentRegion !== newRegion) {
  if (currentRegion !== undefined && highlightEnabled) {
  this.removeHighlight();
  }
  this.currentRegion = newRegion;
  if (newRegion !== undefined && highlightEnabled) {
  this.renderHighlight();
  }
  return true;
  }
  return false;
  },
 
  /**
  * Reset any currently highlighted item
  */
  clearRegionHighlight: function () {
  if (this.currentRegion !== undefined) {
  this.removeHighlight();
  this.currentRegion = undefined;
  return true;
  }
  return false;
  },
 
  renderHighlight: function () {
  this.changeHighlight(true);
  },
 
  removeHighlight: function () {
  this.changeHighlight(false);
  },
 
  changeHighlight: function (highlight) {},
 
  /**
  * Fetch the HTML to display as a tooltip
  */
  getCurrentRegionTooltip: function () {
  var options = this.options,
  header = '',
  entries = [],
  fields, formats, formatlen, fclass, text, i,
  showFields, showFieldsKey, newFields, fv,
  formatter, format, fieldlen, j;
  if (this.currentRegion === undefined) {
  return '';
  }
  fields = this.getCurrentRegionFields();
  formatter = options.get('tooltipFormatter');
  if (formatter) {
  return formatter(this, options, fields);
  }
  if (options.get('tooltipChartTitle')) {
  header += '<div class="jqs jqstitle">' + options.get('tooltipChartTitle') + '</div>\n';
  }
  formats = this.options.get('tooltipFormat');
  if (!formats) {
  return '';
  }
  if (!$.isArray(formats)) {
  formats = [formats];
  }
  if (!$.isArray(fields)) {
  fields = [fields];
  }
  showFields = this.options.get('tooltipFormatFieldlist');
  showFieldsKey = this.options.get('tooltipFormatFieldlistKey');
  if (showFields && showFieldsKey) {
  // user-selected ordering of fields
  newFields = [];
  for (i = fields.length; i--;) {
  fv = fields[i][showFieldsKey];
  if ((j = $.inArray(fv, showFields)) != -1) {
  newFields[j] = fields[i];
  }
  }
  fields = newFields;
  }
  formatlen = formats.length;
  fieldlen = fields.length;
  for (i = 0; i < formatlen; i++) {
  format = formats[i];
  if (typeof format === 'string') {
  format = new SPFormat(format);
  }
  fclass = format.fclass || 'jqsfield';
  for (j = 0; j < fieldlen; j++) {
  if (!fields[j].isNull || !options.get('tooltipSkipNull')) {
  $.extend(fields[j], {
  prefix: options.get('tooltipPrefix'),
  suffix: options.get('tooltipSuffix')
  });
  text = format.render(fields[j], options.get('tooltipValueLookups'), options);
  entries.push('<div class="' + fclass + '">' + text + '</div>');
  }
  }
  }
  if (entries.length) {
  return header + entries.join('\n');
  }
  return '';
  },
 
  getCurrentRegionFields: function () {},
 
  calcHighlightColor: function (color, options) {
  var highlightColor = options.get('highlightColor'),
  lighten = options.get('highlightLighten'),
  parse, mult, rgbnew, i;
  if (highlightColor) {
  return highlightColor;
  }
  if (lighten) {
  // extract RGB values
  parse = /^#([0-9a-f])([0-9a-f])([0-9a-f])$/i.exec(color) || /^#([0-9a-f]{2})([0-9a-f]{2})([0-9a-f]{2})$/i.exec(color);
  if (parse) {
  rgbnew = [];
  mult = color.length === 4 ? 16 : 1;
  for (i = 0; i < 3; i++) {
  rgbnew[i] = clipval(Math.round(parseInt(parse[i + 1], 16) * mult * lighten), 0, 255);
  }
  return 'rgb(' + rgbnew.join(',') + ')';
  }
 
  }
  return color;
  }
 
  });
 
  barHighlightMixin = {
  changeHighlight: function (highlight) {
  var currentRegion = this.currentRegion,
  target = this.target,
  shapeids = this.regionShapes[currentRegion],
  newShapes;
  // will be null if the region value was null
  if (shapeids) {
  newShapes = this.renderRegion(currentRegion, highlight);
  if ($.isArray(newShapes) || $.isArray(shapeids)) {
  target.replaceWithShapes(shapeids, newShapes);
  this.regionShapes[currentRegion] = $.map(newShapes, function (newShape) {
  return newShape.id;
  });
  } else {
  target.replaceWithShape(shapeids, newShapes);
  this.regionShapes[currentRegion] = newShapes.id;
  }
  }
  },
 
  render: function () {
  var values = this.values,
  target = this.target,
  regionShapes = this.regionShapes,
  shapes, ids, i, j;
 
  if (!this.cls._super.render.call(this)) {
  return;
  }
  for (i = values.length; i--;) {
  shapes = this.renderRegion(i);
  if (shapes) {
  if ($.isArray(shapes)) {
  ids = [];
  for (j = shapes.length; j--;) {
  shapes[j].append();
  ids.push(shapes[j].id);
  }
  regionShapes[i] = ids;
  } else {
  shapes.append();
  regionShapes[i] = shapes.id; // store just the shapeid
  }
  } else {
  // null value
  regionShapes[i] = null;
  }
  }
  target.render();
  }
  };
 
  /**
  * Line charts
  */
  $.fn.sparkline.line = line = createClass($.fn.sparkline._base, {
  type: 'line',
 
  init: function (el, values, options, width, height) {
  line._super.init.call(this, el, values, options, width, height);
  this.vertices = [];
  this.regionMap = [];
  this.xvalues = [];
  this.yvalues = [];
  this.yminmax = [];
  this.hightlightSpotId = null;
  this.lastShapeId = null;
  this.initTarget();
  },
 
  getRegion: function (el, x, y) {
  var i,
  regionMap = this.regionMap; // maps regions to value positions
  for (i = regionMap.length; i--;) {
  if (regionMap[i] !== null && x >= regionMap[i][0] && x <= regionMap[i][1]) {
  return regionMap[i][2];
  }
  }
  return undefined;
  },
 
  getCurrentRegionFields: function () {
  var currentRegion = this.currentRegion;
  return {
  isNull: this.yvalues[currentRegion] === null,
  x: this.xvalues[currentRegion],
  y: this.yvalues[currentRegion],
  color: this.options.get('lineColor'),
  fillColor: this.options.get('fillColor'),
  offset: currentRegion
  };
  },
 
  renderHighlight: function () {
  var currentRegion = this.currentRegion,
  target = this.target,
  vertex = this.vertices[currentRegion],
  options = this.options,
  spotRadius = options.get('spotRadius'),
  highlightSpotColor = options.get('highlightSpotColor'),
  highlightLineColor = options.get('highlightLineColor'),
  highlightSpot, highlightLine;
 
  if (!vertex) {
  return;
  }
  if (spotRadius && highlightSpotColor) {
  highlightSpot = target.drawCircle(vertex[0], vertex[1],
  spotRadius, undefined, highlightSpotColor);
  this.highlightSpotId = highlightSpot.id;
  target.insertAfterShape(this.lastShapeId, highlightSpot);
  }
  if (highlightLineColor) {
  highlightLine = target.drawLine(vertex[0], this.canvasTop, vertex[0],
  this.canvasTop + this.canvasHeight, highlightLineColor);
  this.highlightLineId = highlightLine.id;
  target.insertAfterShape(this.lastShapeId, highlightLine);
  }
  },
 
  removeHighlight: function () {
  var target = this.target;
  if (this.highlightSpotId) {
  target.removeShapeId(this.highlightSpotId);
  this.highlightSpotId = null;
  }
  if (this.highlightLineId) {
  target.removeShapeId(this.highlightLineId);
  this.highlightLineId = null;
  }
  },
 
  scanValues: function () {
  var values = this.values,
  valcount = values.length,
  xvalues = this.xvalues,
  yvalues = this.yvalues,
  yminmax = this.yminmax,
  i, val, isStr, isArray, sp;
  for (i = 0; i < valcount; i++) {
  val = values[i];
  isStr = typeof(values[i]) === 'string';
  isArray = typeof(values[i]) === 'object' && values[i] instanceof Array;
  sp = isStr && values[i].split(':');
  if (isStr && sp.length === 2) { // x:y
  xvalues.push(Number(sp[0]));
  yvalues.push(Number(sp[1]));
  yminmax.push(Number(sp[1]));
  } else if (isArray) {
  xvalues.push(val[0]);
  yvalues.push(val[1]);
  yminmax.push(val[1]);
  } else {
  xvalues.push(i);
  if (values[i] === null || values[i] === 'null') {
  yvalues.push(null);
  } else {
  yvalues.push(Number(val));
  yminmax.push(Number(val));
  }
  }
  }
  if (this.options.get('xvalues')) {
  xvalues = this.options.get('xvalues');
  }
 
  this.maxy = this.maxyorg = Math.max.apply(Math, yminmax);
  this.miny = this.minyorg = Math.min.apply(Math, yminmax);
 
  this.maxx = Math.max.apply(Math, xvalues);
  this.minx = Math.min.apply(Math, xvalues);
 
  this.xvalues = xvalues;
  this.yvalues = yvalues;
  this.yminmax = yminmax;
 
  },
 
  processRangeOptions: function () {
  var options = this.options,
  normalRangeMin = options.get('normalRangeMin'),
  normalRangeMax = options.get('normalRangeMax');
 
  if (normalRangeMin !== undefined) {
  if (normalRangeMin < this.miny) {
  this.miny = normalRangeMin;
  }
  if (normalRangeMax > this.maxy) {
  this.maxy = normalRangeMax;
  }
  }
  if (options.get('chartRangeMin') !== undefined && (options.get('chartRangeClip') || options.get('chartRangeMin') < this.miny)) {
  this.miny = options.get('chartRangeMin');
  }
  if (options.get('chartRangeMax') !== undefined && (options.get('chartRangeClip') || options.get('chartRangeMax') > this.maxy)) {
  this.maxy = options.get('chartRangeMax');
  }
  if (options.get('chartRangeMinX') !== undefined && (options.get('chartRangeClipX') || options.get('chartRangeMinX') < this.minx)) {
  this.minx = options.get('chartRangeMinX');
  }
  if (options.get('chartRangeMaxX') !== undefined && (options.get('chartRangeClipX') || options.get('chartRangeMaxX') > this.maxx)) {
  this.maxx = options.get('chartRangeMaxX');
  }
 
  },
 
  drawNormalRange: function (canvasLeft, canvasTop, canvasHeight, canvasWidth, rangey) {
  var normalRangeMin = this.options.get('normalRangeMin'),
  normalRangeMax = this.options.get('normalRangeMax'),
  ytop = canvasTop + Math.round(canvasHeight - (canvasHeight * ((normalRangeMax - this.miny) / rangey))),
  height = Math.round((canvasHeight * (normalRangeMax - normalRangeMin)) / rangey);
  this.target.drawRect(canvasLeft, ytop, canvasWidth, height, undefined, this.options.get('normalRangeColor')).append();
  },
 
  render: function () {
  var options = this.options,
  target = this.target,
  canvasWidth = this.canvasWidth,
  canvasHeight = this.canvasHeight,
  vertices = this.vertices,
  spotRadius = options.get('spotRadius'),
  regionMap = this.regionMap,
  rangex, rangey, yvallast,
  canvasTop, canvasLeft,
  vertex, path, paths, x, y, xnext, xpos, xposnext,
  last, next, yvalcount, lineShapes, fillShapes, plen,
  valueSpots, hlSpotsEnabled, color, xvalues, yvalues, i;
 
  if (!line._super.render.call(this)) {
  return;
  }
 
  this.scanValues();
  this.processRangeOptions();
 
  xvalues = this.xvalues;
  yvalues = this.yvalues;
 
  if (!this.yminmax.length || this.yvalues.length < 2) {
  // empty or all null valuess
  return;
  }
 
  canvasTop = canvasLeft = 0;
 
  rangex = this.maxx - this.minx === 0 ? 1 : this.maxx - this.minx;
  rangey = this.maxy - this.miny === 0 ? 1 : this.maxy - this.miny;
  yvallast = this.yvalues.length - 1;
 
  if (spotRadius && (canvasWidth < (spotRadius * 4) || canvasHeight < (spotRadius * 4))) {
  spotRadius = 0;
  }
  if (spotRadius) {
  // adjust the canvas size as required so that spots will fit
  hlSpotsEnabled = options.get('highlightSpotColor') && !options.get('disableInteraction');
  if (hlSpotsEnabled || options.get('minSpotColor') || (options.get('spotColor') && yvalues[yvallast] === this.miny)) {
  canvasHeight -= Math.ceil(spotRadius);
  }
  if (hlSpotsEnabled || options.get('maxSpotColor') || (options.get('spotColor') && yvalues[yvallast] === this.maxy)) {
  canvasHeight -= Math.ceil(spotRadius);
  canvasTop += Math.ceil(spotRadius);
  }
  if (hlSpotsEnabled ||
  ((options.get('minSpotColor') || options.get('maxSpotColor')) && (yvalues[0] === this.miny || yvalues[0] === this.maxy))) {
  canvasLeft += Math.ceil(spotRadius);
  canvasWidth -= Math.ceil(spotRadius);
  }
  if (hlSpotsEnabled || options.get('spotColor') ||
  (options.get('minSpotColor') || options.get('maxSpotColor') &&
  (yvalues[yvallast] === this.miny || yvalues[yvallast] === this.maxy))) {
  canvasWidth -= Math.ceil(spotRadius);
  }
  }
 
 
  canvasHeight--;
 
  if (options.get('normalRangeMin') !== undefined && !options.get('drawNormalOnTop')) {
  this.drawNormalRange(canvasLeft, canvasTop, canvasHeight, canvasWidth, rangey);
  }
 
  path = [];
  paths = [path];
  last = next = null;
  yvalcount = yvalues.length;
  for (i = 0; i < yvalcount; i++) {
  x = xvalues[i];
  xnext = xvalues[i + 1];
  y = yvalues[i];
  xpos = canvasLeft + Math.round((x - this.minx) * (canvasWidth / rangex));
  xposnext = i < yvalcount - 1 ? canvasLeft + Math.round((xnext - this.minx) * (canvasWidth / rangex)) : canvasWidth;
  next = xpos + ((xposnext - xpos) / 2);
  regionMap[i] = [last || 0, next, i];
  last = next;
  if (y === null) {
  if (i) {
  if (yvalues[i - 1] !== null) {
  path = [];
  paths.push(path);
  }
  vertices.push(null);
  }
  } else {
  if (y < this.miny) {
  y = this.miny;
  }
  if (y > this.maxy) {
  y = this.maxy;
  }
  if (!path.length) {
  // previous value was null
  path.push([xpos, canvasTop + canvasHeight]);
  }
  vertex = [xpos, canvasTop + Math.round(canvasHeight - (canvasHeight * ((y - this.miny) / rangey)))];
  path.push(vertex);
  vertices.push(vertex);
  }
  }
 
  lineShapes = [];
  fillShapes = [];
  plen = paths.length;
  for (i = 0; i < plen; i++) {
  path = paths[i];
  if (path.length) {
  if (options.get('fillColor')) {
  path.push([path[path.length - 1][0], (canvasTop + canvasHeight)]);
  fillShapes.push(path.slice(0));
  path.pop();
  }
  // if there's only a single point in this path, then we want to display it
  // as a vertical line which means we keep path[0] as is
  if (path.length > 2) {
  // else we want the first value
  path[0] = [path[0][0], path[1][1]];
  }
  lineShapes.push(path);
  }
  }
 
  // draw the fill first, then optionally the normal range, then the line on top of that
  plen = fillShapes.length;
  for (i = 0; i < plen; i++) {
  target.drawShape(fillShapes[i],
  options.get('fillColor'), options.get('fillColor')).append();
  }
 
  if (options.get('normalRangeMin') !== undefined && options.get('drawNormalOnTop')) {
  this.drawNormalRange(canvasLeft, canvasTop, canvasHeight, canvasWidth, rangey);
  }
 
  plen = lineShapes.length;
  for (i = 0; i < plen; i++) {
  target.drawShape(lineShapes[i], options.get('lineColor'), undefined,
  options.get('lineWidth')).append();
  }
 
  if (spotRadius && options.get('valueSpots')) {
  valueSpots = options.get('valueSpots');
  if (valueSpots.get === undefined) {
  valueSpots = new RangeMap(valueSpots);
  }
  for (i = 0; i < yvalcount; i++) {
  color = valueSpots.get(yvalues[i]);
  if (color) {
  target.drawCircle(canvasLeft + Math.round((xvalues[i] - this.minx) * (canvasWidth / rangex)),
  canvasTop + Math.round(canvasHeight - (canvasHeight * ((yvalues[i] - this.miny) / rangey))),
  spotRadius, undefined,
  color).append();
  }
  }
 
  }
  if (spotRadius && options.get('spotColor')) {
  target.drawCircle(canvasLeft + Math.round((xvalues[xvalues.length - 1] - this.minx) * (canvasWidth / rangex)),
  canvasTop + Math.round(canvasHeight - (canvasHeight * ((yvalues[yvallast] - this.miny) / rangey))),
  spotRadius, undefined,
  options.get('spotColor')).append();
  }
  if (this.maxy !== this.minyorg) {
  if (spotRadius && options.get('minSpotColor')) {
  x = xvalues[$.inArray(this.minyorg, yvalues)];
  target.drawCircle(canvasLeft + Math.round((x - this.minx) * (canvasWidth / rangex)),
  canvasTop + Math.round(canvasHeight - (canvasHeight * ((this.minyorg - this.miny) / rangey))),
  spotRadius, undefined,
  options.get('minSpotColor')).append();
  }
  if (spotRadius && options.get('maxSpotColor')) {
  x = xvalues[$.inArray(this.maxyorg, yvalues)];
  target.drawCircle(canvasLeft + Math.round((x - this.minx) * (canvasWidth / rangex)),
  canvasTop + Math.round(canvasHeight - (canvasHeight * ((this.maxyorg - this.miny) / rangey))),
  spotRadius, undefined,
  options.get('maxSpotColor')).append();
  }
  }
 
  this.lastShapeId = target.getLastShapeId();
  this.canvasTop = canvasTop;
  target.render();
  }
  });
 
  /**
  * Bar charts
  */
  $.fn.sparkline.bar = bar = createClass($.fn.sparkline._base, barHighlightMixin, {
  type: 'bar',
 
  init: function (el, values, options, width, height) {
  var barWidth = parseInt(options.get('barWidth'), 10),
  barSpacing = parseInt(options.get('barSpacing'), 10),
  chartRangeMin = options.get('chartRangeMin'),
  chartRangeMax = options.get('chartRangeMax'),
  chartRangeClip = options.get('chartRangeClip'),
  stackMin = Infinity,
  stackMax = -Infinity,
  isStackString, groupMin, groupMax, stackRanges,
  numValues, i, vlen, range, zeroAxis, xaxisOffset, min, max, clipMin, clipMax,
  stacked, vlist, j, slen, svals, val, yoffset, yMaxCalc, canvasHeightEf;
  bar._super.init.call(this, el, values, options, width, height);
 
  // scan values to determine whether to stack bars
  for (i = 0, vlen = values.length; i < vlen; i++) {
  val = values[i];
  isStackString = typeof(val) === 'string' && val.indexOf(':') > -1;
  if (isStackString || $.isArray(val)) {
  stacked = true;
  if (isStackString) {
  val = values[i] = normalizeValues(val.split(':'));
  }
  val = remove(val, null); // min/max will treat null as zero
  groupMin = Math.min.apply(Math, val);
  groupMax = Math.max.apply(Math, val);
  if (groupMin < stackMin) {
  stackMin = groupMin;
  }
  if (groupMax > stackMax) {
  stackMax = groupMax;
  }
  }
  }
 
  this.stacked = stacked;
  this.regionShapes = {};
  this.barWidth = barWidth;
  this.barSpacing = barSpacing;
  this.totalBarWidth = barWidth + barSpacing;
  this.width = width = (values.length * barWidth) + ((values.length - 1) * barSpacing);
 
  this.initTarget();
 
  if (chartRangeClip) {
  clipMin = chartRangeMin === undefined ? -Infinity : chartRangeMin;
  clipMax = chartRangeMax === undefined ? Infinity : chartRangeMax;
  }
 
  numValues = [];
  stackRanges = stacked ? [] : numValues;
  var stackTotals = [];
  var stackRangesNeg = [];
  for (i = 0, vlen = values.length; i < vlen; i++) {
  if (stacked) {
  vlist = values[i];
  values[i] = svals = [];
  stackTotals[i] = 0;
  stackRanges[i] = stackRangesNeg[i] = 0;
  for (j = 0, slen = vlist.length; j < slen; j++) {
  val = svals[j] = chartRangeClip ? clipval(vlist[j], clipMin, clipMax) : vlist[j];
  if (val !== null) {
  if (val > 0) {
  stackTotals[i] += val;
  }
  if (stackMin < 0 && stackMax > 0) {
  if (val < 0) {
  stackRangesNeg[i] += Math.abs(val);
  } else {
  stackRanges[i] += val;
  }
  } else {
  stackRanges[i] += Math.abs(val - (val < 0 ? stackMax : stackMin));
  }
  numValues.push(val);
  }
  }
  } else {
  val = chartRangeClip ? clipval(values[i], clipMin, clipMax) : values[i];
  val = values[i] = normalizeValue(val);
  if (val !== null) {
  numValues.push(val);
  }
  }
  }
  this.max = max = Math.max.apply(Math, numValues);
  this.min = min = Math.min.apply(Math, numValues);
  this.stackMax = stackMax = stacked ? Math.max.apply(Math, stackTotals) : max;
  this.stackMin = stackMin = stacked ? Math.min.apply(Math, numValues) : min;
 
  if (options.get('chartRangeMin') !== undefined && (options.get('chartRangeClip') || options.get('chartRangeMin') < min)) {
  min = options.get('chartRangeMin');
  }
  if (options.get('chartRangeMax') !== undefined && (options.get('chartRangeClip') || options.get('chartRangeMax') > max)) {
  max = options.get('chartRangeMax');
  }
 
  this.zeroAxis = zeroAxis = options.get('zeroAxis', true);
  if (min <= 0 && max >= 0 && zeroAxis) {
  xaxisOffset = 0;
  } else if (zeroAxis == false) {
  xaxisOffset = min;
  } else if (min > 0) {
  xaxisOffset = min;
  } else {
  xaxisOffset = max;
  }
  this.xaxisOffset = xaxisOffset;
 
  range = stacked ? (Math.max.apply(Math, stackRanges) + Math.max.apply(Math, stackRangesNeg)) : max - min;
 
  // as we plot zero/min values a single pixel line, we add a pixel to all other
  // values - Reduce the effective canvas size to suit
  this.canvasHeightEf = (zeroAxis && min < 0) ? this.canvasHeight - 2 : this.canvasHeight - 1;
 
  if (min < xaxisOffset) {
  yMaxCalc = (stacked && max >= 0) ? stackMax : max;
  yoffset = (yMaxCalc - xaxisOffset) / range * this.canvasHeight;
  if (yoffset !== Math.ceil(yoffset)) {
  this.canvasHeightEf -= 2;
  yoffset = Math.ceil(yoffset);
  }
  } else {
  yoffset = this.canvasHeight;
  }
  this.yoffset = yoffset;
 
  if ($.isArray(options.get('colorMap'))) {
  this.colorMapByIndex = options.get('colorMap');
  this.colorMapByValue = null;
  } else {
  this.colorMapByIndex = null;
  this.colorMapByValue = options.get('colorMap');
  if (this.colorMapByValue && this.colorMapByValue.get === undefined) {
  this.colorMapByValue = new RangeMap(this.colorMapByValue);
  }
  }
 
  this.range = range;
  },
 
  getRegion: function (el, x, y) {
  var result = Math.floor(x / this.totalBarWidth);
  return (result < 0 || result >= this.values.length) ? undefined : result;
  },
 
  getCurrentRegionFields: function () {
  var currentRegion = this.currentRegion,
  values = ensureArray(this.values[currentRegion]),
  result = [],
  value, i;
  for (i = values.length; i--;) {
  value = values[i];
  result.push({
  isNull: value === null,
  value: value,
  color: this.calcColor(i, value, currentRegion),
  offset: currentRegion
  });
  }
  return result;
  },
 
  calcColor: function (stacknum, value, valuenum) {
  var colorMapByIndex = this.colorMapByIndex,
  colorMapByValue = this.colorMapByValue,
  options = this.options,
  color, newColor;
  if (this.stacked) {
  color = options.get('stackedBarColor');
  } else {
  color = (value < 0) ? options.get('negBarColor') : options.get('barColor');
  }
  if (value === 0 && options.get('zeroColor') !== undefined) {
  color = options.get('zeroColor');
  }
  if (colorMapByValue && (newColor = colorMapByValue.get(value))) {
  color = newColor;
  } else if (colorMapByIndex && colorMapByIndex.length > valuenum) {
  color = colorMapByIndex[valuenum];
  }
  return $.isArray(color) ? color[stacknum % color.length] : color;
  },
 
  /**
  * Render bar(s) for a region
  */
  renderRegion: function (valuenum, highlight) {
  var vals = this.values[valuenum],
  options = this.options,
  xaxisOffset = this.xaxisOffset,
  result = [],
  range = this.range,
  stacked = this.stacked,
  target = this.target,
  x = valuenum * this.totalBarWidth,
  canvasHeightEf = this.canvasHeightEf,
  yoffset = this.yoffset,
  y, height, color, isNull, yoffsetNeg, i, valcount, val, minPlotted, allMin;
 
  vals = $.isArray(vals) ? vals : [vals];
  valcount = vals.length;
  val = vals[0];
  isNull = all(null, vals);
  allMin = all(xaxisOffset, vals, true);
 
  if (isNull) {
  if (options.get('nullColor')) {
  color = highlight ? options.get('nullColor') : this.calcHighlightColor(options.get('nullColor'), options);
  y = (yoffset > 0) ? yoffset - 1 : yoffset;
  return target.drawRect(x, y, this.barWidth - 1, 0, color, color);
  } else {
  return undefined;
  }
  }
  yoffsetNeg = yoffset;
  for (i = 0; i < valcount; i++) {
  val = vals[i];
 
  if (stacked && val === xaxisOffset) {
  if (!allMin || minPlotted) {
  continue;
  }
  minPlotted = true;
  }
 
  if (range > 0) {
  height = Math.floor(canvasHeightEf * ((Math.abs(val - xaxisOffset) / range))) + 1;
  } else {
  height = 1;
  }
  if (val < xaxisOffset || (val === xaxisOffset && yoffset === 0)) {
  y = yoffsetNeg;
  yoffsetNeg += height;
  } else {
  y = yoffset - height;
  yoffset -= height;
  }
  color = this.calcColor(i, val, valuenum);
  if (highlight) {
  color = this.calcHighlightColor(color, options);
  }
  result.push(target.drawRect(x, y, this.barWidth - 1, height - 1, color, color));
  }
  if (result.length === 1) {
  return result[0];
  }
  return result;
  }
  });
 
  /**
  * Tristate charts
  */
  $.fn.sparkline.tristate = tristate = createClass($.fn.sparkline._base, barHighlightMixin, {
  type: 'tristate',
 
  init: function (el, values, options, width, height) {
  var barWidth = parseInt(options.get('barWidth'), 10),
  barSpacing = parseInt(options.get('barSpacing'), 10);
  tristate._super.init.call(this, el, values, options, width, height);
 
  this.regionShapes = {};
  this.barWidth = barWidth;
  this.barSpacing = barSpacing;
  this.totalBarWidth = barWidth + barSpacing;
  this.values = $.map(values, Number);
  this.width = width = (values.length * barWidth) + ((values.length - 1) * barSpacing);
 
  if ($.isArray(options.get('colorMap'))) {
  this.colorMapByIndex = options.get('colorMap');
  this.colorMapByValue = null;
  } else {
  this.colorMapByIndex = null;
  this.colorMapByValue = options.get('colorMap');
  if (this.colorMapByValue && this.colorMapByValue.get === undefined) {
  this.colorMapByValue = new RangeMap(this.colorMapByValue);
  }
  }
  this.initTarget();
  },
 
  getRegion: function (el, x, y) {
  return Math.floor(x / this.totalBarWidth);
  },
 
  getCurrentRegionFields: function () {
  var currentRegion = this.currentRegion;
  return {
  isNull: this.values[currentRegion] === undefined,
  value: this.values[currentRegion],
  color: this.calcColor(this.values[currentRegion], currentRegion),
  offset: currentRegion
  };
  },
 
  calcColor: function (value, valuenum) {
  var values = this.values,
  options = this.options,
  colorMapByIndex = this.colorMapByIndex,
  colorMapByValue = this.colorMapByValue,
  color, newColor;
 
  if (colorMapByValue && (newColor = colorMapByValue.get(value))) {
  color = newColor;
  } else if (colorMapByIndex && colorMapByIndex.length > valuenum) {
  color = colorMapByIndex[valuenum];
  } else if (values[valuenum] < 0) {
  color = options.get('negBarColor');
  } else if (values[valuenum] > 0) {
  color = options.get('posBarColor');
  } else {
  color = options.get('zeroBarColor');
  }
  return color;
  },
 
  renderRegion: function (valuenum, highlight) {
  var values = this.values,
  options = this.options,
  target = this.target,
  canvasHeight, height, halfHeight,
  x, y, color;
 
  canvasHeight = target.pixelHeight;
  halfHeight = Math.round(canvasHeight / 2);
 
  x = valuenum * this.totalBarWidth;
  if (values[valuenum] < 0) {
  y = halfHeight;
  height = halfHeight - 1;
  } else if (values[valuenum] > 0) {
  y = 0;
  height = halfHeight - 1;
  } else {
  y = halfHeight - 1;
  height = 2;
  }
  color = this.calcColor(values[valuenum], valuenum);
  if (color === null) {
  return;
  }
  if (highlight) {
  color = this.calcHighlightColor(color, options);
  }
  return target.drawRect(x, y, this.barWidth - 1, height - 1, color, color);
  }
  });
 
  /**
  * Discrete charts
  */
  $.fn.sparkline.discrete = discrete = createClass($.fn.sparkline._base, barHighlightMixin, {
  type: 'discrete',
 
  init: function (el, values, options, width, height) {
  discrete._super.init.call(this, el, values, options, width, height);
 
  this.regionShapes = {};
  this.values = values = $.map(values, Number);
  this.min = Math.min.apply(Math, values);
  this.max = Math.max.apply(Math, values);
  this.range = this.max - this.min;
  this.width = width = options.get('width') === 'auto' ? values.length * 2 : this.width;
  this.interval = Math.floor(width / values.length);
  this.itemWidth = width / values.length;
  if (options.get('chartRangeMin') !== undefined && (options.get('chartRangeClip') || options.get('chartRangeMin') < this.min)) {
  this.min = options.get('chartRangeMin');
  }
  if (options.get('chartRangeMax') !== undefined && (options.get('chartRangeClip') || options.get('chartRangeMax') > this.max)) {
  this.max = options.get('chartRangeMax');
  }
  this.initTarget();
  if (this.target) {
  this.lineHeight = options.get('lineHeight') === 'auto' ? Math.round(this.canvasHeight * 0.3) : options.get('lineHeight');
  }
  },
 
  getRegion: function (el, x, y) {
  return Math.floor(x / this.itemWidth);
  },
 
  getCurrentRegionFields: function () {
  var currentRegion = this.currentRegion;
  return {
  isNull: this.values[currentRegion] === undefined,
  value: this.values[currentRegion],
  offset: currentRegion
  };
  },
 
  renderRegion: function (valuenum, highlight) {
  var values = this.values,
  options = this.options,
  min = this.min,
  max = this.max,
  range = this.range,
  interval = this.interval,
  target = this.target,
  canvasHeight = this.canvasHeight,
  lineHeight = this.lineHeight,
  pheight = canvasHeight - lineHeight,
  ytop, val, color, x;
 
  val = clipval(values[valuenum], min, max);
  x = valuenum * interval;
  ytop = Math.round(pheight - pheight * ((val - min) / range));
  color = (options.get('thresholdColor') && val < options.get('thresholdValue')) ? options.get('thresholdColor') : options.get('lineColor');
  if (highlight) {
  color = this.calcHighlightColor(color, options);
  }
  return target.drawLine(x, ytop, x, ytop + lineHeight, color);
  }
  });
 
  /**
  * Bullet charts
  */
  $.fn.sparkline.bullet = bullet = createClass($.fn.sparkline._base, {
  type: 'bullet',
 
  init: function (el, values, options, width, height) {
  var min, max, vals;
  bullet._super.init.call(this, el, values, options, width, height);
 
  // values: target, performance, range1, range2, range3
  this.values = values = normalizeValues(values);
  // target or performance could be null
  vals = values.slice();
  vals[0] = vals[0] === null ? vals[2] : vals[0];
  vals[1] = values[1] === null ? vals[2] : vals[1];
  min = Math.min.apply(Math, values);
  max = Math.max.apply(Math, values);
  if (options.get('base') === undefined) {
  min = min < 0 ? min : 0;
  } else {
  min = options.get('base');
  }
  this.min = min;
  this.max = max;
  this.range = max - min;
  this.shapes = {};
  this.valueShapes = {};
  this.regiondata = {};
  this.width = width = options.get('width') === 'auto' ? '4.0em' : width;
  this.target = this.$el.simpledraw(width, height, options.get('composite'));
  if (!values.length) {
  this.disabled = true;
  }
  this.initTarget();
  },
 
  getRegion: function (el, x, y) {
  var shapeid = this.target.getShapeAt(el, x, y);
  return (shapeid !== undefined && this.shapes[shapeid] !== undefined) ? this.shapes[shapeid] : undefined;
  },
 
  getCurrentRegionFields: function () {
  var currentRegion = this.currentRegion;
  return {
  fieldkey: currentRegion.substr(0, 1),
  value: this.values[currentRegion.substr(1)],
  region: currentRegion
  };
  },
 
  changeHighlight: function (highlight) {
  var currentRegion = this.currentRegion,
  shapeid = this.valueShapes[currentRegion],
  shape;
  delete this.shapes[shapeid];
  switch (currentRegion.substr(0, 1)) {
  case 'r':
  shape = this.renderRange(currentRegion.substr(1), highlight);
  break;
  case 'p':
  shape = this.renderPerformance(highlight);
  break;
  case 't':
  shape = this.renderTarget(highlight);
  break;
  }
  this.valueShapes[currentRegion] = shape.id;
  this.shapes[shape.id] = currentRegion;
  this.target.replaceWithShape(shapeid, shape);
  },
 
  renderRange: function (rn, highlight) {
  var rangeval = this.values[rn],
  rangewidth = Math.round(this.canvasWidth * ((rangeval - this.min) / this.range)),
  color = this.options.get('rangeColors')[rn - 2];
  if (highlight) {
  color = this.calcHighlightColor(color, this.options);
  }
  return this.target.drawRect(0, 0, rangewidth - 1, this.canvasHeight - 1, color, color);
  },
 
  renderPerformance: function (highlight) {
  var perfval = this.values[1],
  perfwidth = Math.round(this.canvasWidth * ((perfval - this.min) / this.range)),
  color = this.options.get('performanceColor');
  if (highlight) {
  color = this.calcHighlightColor(color, this.options);
  }
  return this.target.drawRect(0, Math.round(this.canvasHeight * 0.3), perfwidth - 1,
  Math.round(this.canvasHeight * 0.4) - 1, color, color);
  },
 
  renderTarget: function (highlight) {
  var targetval = this.values[0],
  x = Math.round(this.canvasWidth * ((targetval - this.min) / this.range) - (this.options.get('targetWidth') / 2)),
  targettop = Math.round(this.canvasHeight * 0.10),
  targetheight = this.canvasHeight - (targettop * 2),
  color = this.options.get('targetColor');
  if (highlight) {
  color = this.calcHighlightColor(color, this.options);
  }
  return this.target.drawRect(x, targettop, this.options.get('targetWidth') - 1, targetheight - 1, color, color);
  },
 
  render: function () {
  var vlen = this.values.length,
  target = this.target,
  i, shape;
  if (!bullet._super.render.call(this)) {
  return;
  }
  for (i = 2; i < vlen; i++) {
  shape = this.renderRange(i).append();
  this.shapes[shape.id] = 'r' + i;
  this.valueShapes['r' + i] = shape.id;
  }
  if (this.values[1] !== null) {
  shape = this.renderPerformance().append();
  this.shapes[shape.id] = 'p1';
  this.valueShapes.p1 = shape.id;
  }
  if (this.values[0] !== null) {
  shape = this.renderTarget().append();
  this.shapes[shape.id] = 't0';
  this.valueShapes.t0 = shape.id;
  }
  target.render();
  }
  });
 
  /**
  * Pie charts
  */
  $.fn.sparkline.pie = pie = createClass($.fn.sparkline._base, {
  type: 'pie',
 
  init: function (el, values, options, width, height) {
  var total = 0, i;
 
  pie._super.init.call(this, el, values, options, width, height);
 
  this.shapes = {}; // map shape ids to value offsets
  this.valueShapes = {}; // maps value offsets to shape ids
  this.values = values = $.map(values, Number);
 
  if (options.get('width') === 'auto') {
  this.width = this.height;
  }
 
  if (values.length > 0) {
  for (i = values.length; i--;) {
  total += values[i];
  }
  }
  this.total = total;
  this.initTarget();
  this.radius = Math.floor(Math.min(this.canvasWidth, this.canvasHeight) / 2);
  },
 
  getRegion: function (el, x, y) {
  var shapeid = this.target.getShapeAt(el, x, y);
  return (shapeid !== undefined && this.shapes[shapeid] !== undefined) ? this.shapes[shapeid] : undefined;
  },
 
  getCurrentRegionFields: function () {
  var currentRegion = this.currentRegion;
  return {
  isNull: this.values[currentRegion] === undefined,
  value: this.values[currentRegion],
  percent: this.values[currentRegion] / this.total * 100,
  color: this.options.get('sliceColors')[currentRegion % this.options.get('sliceColors').length],
  offset: currentRegion
  };
  },
 
  changeHighlight: function (highlight) {
  var currentRegion = this.currentRegion,
  newslice = this.renderSlice(currentRegion, highlight),
  shapeid = this.valueShapes[currentRegion];
  delete this.shapes[shapeid];
  this.target.replaceWithShape(shapeid, newslice);
  this.valueShapes[currentRegion] = newslice.id;
  this.shapes[newslice.id] = currentRegion;
  },
 
  renderSlice: function (valuenum, highlight) {
  var target = this.target,
  options = this.options,
  radius = this.radius,
  borderWidth = options.get('borderWidth'),
  offset = options.get('offset'),
  circle = 2 * Math.PI,
  values = this.values,
  total = this.total,
  next = offset ? (2*Math.PI)*(offset/360) : 0,
  start, end, i, vlen, color;
 
  vlen = values.length;
  for (i = 0; i < vlen; i++) {
  start = next;
  end = next;
  if (total > 0) { // avoid divide by zero
  end = next + (circle * (values[i] / total));
  }
  if (valuenum === i) {
  color = options.get('sliceColors')[i % options.get('sliceColors').length];
  if (highlight) {
  color = this.calcHighlightColor(color, options);
  }
 
  return target.drawPieSlice(radius, radius, radius - borderWidth, start, end, undefined, color);
  }
  next = end;
  }
  },
 
  render: function () {
  var target = this.target,
  values = this.values,
  options = this.options,
  radius = this.radius,
  borderWidth = options.get('borderWidth'),
  shape, i;
 
  if (!pie._super.render.call(this)) {
  return;
  }
  if (borderWidth) {
  target.drawCircle(radius, radius, Math.floor(radius - (borderWidth / 2)),
  options.get('borderColor'), undefined, borderWidth).append();
  }
  for (i = values.length; i--;) {
  if (values[i]) { // don't render zero values
  shape = this.renderSlice(i).append();
  this.valueShapes[i] = shape.id; // store just the shapeid
  this.shapes[shape.id] = i;
  }
  }
  target.render();
  }
  });
 
  /**
  * Box plots
  */
  $.fn.sparkline.box = box = createClass($.fn.sparkline._base, {
  type: 'box',
 
  init: function (el, values, options, width, height) {
  box._super.init.call(this, el, values, options, width, height);
  this.values = $.map(values, Number);
  this.width = options.get('width') === 'auto' ? '4.0em' : width;
  this.initTarget();
  if (!this.values.length) {
  this.disabled = 1;
  }
  },
 
  /**
  * Simulate a single region
  */
  getRegion: function () {
  return 1;
  },
 
  getCurrentRegionFields: function () {
  var result = [
  { field: 'lq', value: this.quartiles[0] },
  { field: 'med', value: this.quartiles[1] },
  { field: 'uq', value: this.quartiles[2] }
  ];
  if (this.loutlier !== undefined) {
  result.push({ field: 'lo', value: this.loutlier});
  }
  if (this.routlier !== undefined) {
  result.push({ field: 'ro', value: this.routlier});
  }
  if (this.lwhisker !== undefined) {
  result.push({ field: 'lw', value: this.lwhisker});
  }
  if (this.rwhisker !== undefined) {
  result.push({ field: 'rw', value: this.rwhisker});
  }
  return result;
  },
 
  render: function () {
  var target = this.target,
  values = this.values,
  vlen = values.length,
  options = this.options,
  canvasWidth = this.canvasWidth,
  canvasHeight = this.canvasHeight,
  minValue = options.get('chartRangeMin') === undefined ? Math.min.apply(Math, values) : options.get('chartRangeMin'),
  maxValue = options.get('chartRangeMax') === undefined ? Math.max.apply(Math, values) : options.get('chartRangeMax'),
  canvasLeft = 0,
  lwhisker, loutlier, iqr, q1, q2, q3, rwhisker, routlier, i,
  size, unitSize;
 
  if (!box._super.render.call(this)) {
  return;
  }
 
  if (options.get('raw')) {
  if (options.get('showOutliers') && values.length > 5) {
  loutlier = values[0];
  lwhisker = values[1];
  q1 = values[2];
  q2 = values[3];
  q3 = values[4];
  rwhisker = values[5];
  routlier = values[6];
  } else {
  lwhisker = values[0];
  q1 = values[1];
  q2 = values[2];
  q3 = values[3];
  rwhisker = values[4];
  }
  } else {
  values.sort(function (a, b) { return a - b; });
  q1 = quartile(values, 1);
  q2 = quartile(values, 2);
  q3 = quartile(values, 3);
  iqr = q3 - q1;
  if (options.get('showOutliers')) {
  lwhisker = rwhisker = undefined;
  for (i = 0; i < vlen; i++) {
  if (lwhisker === undefined && values[i] > q1 - (iqr * options.get('outlierIQR'))) {
  lwhisker = values[i];
  }
  if (values[i] < q3 + (iqr * options.get('outlierIQR'))) {
  rwhisker = values[i];
  }
  }
  loutlier = values[0];
  routlier = values[vlen - 1];
  } else {
  lwhisker = values[0];
  rwhisker = values[vlen - 1];
  }
  }
  this.quartiles = [q1, q2, q3];
  this.lwhisker = lwhisker;
  this.rwhisker = rwhisker;
  this.loutlier = loutlier;
  this.routlier = routlier;
 
  unitSize = canvasWidth / (maxValue - minValue + 1);
  if (options.get('showOutliers')) {
  canvasLeft = Math.ceil(options.get('spotRadius'));
  canvasWidth -= 2 * Math.ceil(options.get('spotRadius'));
  unitSize = canvasWidth / (maValue - minValue + 1);
  if (loutlier < lwhisker) {
  target.drawCircle((loutlier - minValue) * unitSize + canvasLeft,
  canvasHeight / 2,
  options.get('spotRadius'),
  options.get('outlierLineColor'),
  options.get('outlierFillColor')).append();
  }
  if (routlier > rwhisker) {
  target.drawCircle((routlier - minValue) * unitSize + canvasLeft,
  canvasHeight / 2,
  options.get('spotRadius'),
  options.get('outlierLineColor'),
  options.get('outlierFillColor')).append();
  }
  }
 
  // box
  target.drawRect(
  Math.round((q1 - minValue) * unitSize + canvasLeft),
  Math.round(canvasHeight * 0.1),
  Math.round((q3 - q1) * unitSize),
  Math.round(canvasHeight * 0.8),
  options.get('boxLineColor'),
  options.get('boxFillColor')).append();
  // left whisker
  target.drawLine(
  Math.round((lwhisker - minValue) * unitSize + canvasLeft),
  Math.round(canvasHeight / 2),
  Math.round((q1 - minValue) * unitSize + canvasLeft),
  Math.round(canvasHeight / 2),
  options.get('lineColor')).append();
  target.drawLine(
  Math.round((lwhisker - minValue) * unitSize + canvasLeft),
  Math.round(canvasHeight / 4),
  Math.round((lwhisker - minValue) * unitSize + canvasLeft),
  Math.round(canvasHeight - canvasHeight / 4),
  options.get('whiskerColor')).append();
  // right whisker
  target.drawLine(Math.round((rwhisker - minValue) * unitSize + canvasLeft),
  Math.round(canvasHeight / 2),
  Math.round((q3 - minValue) * unitSize + canvasLeft),
  Math.round(canvasHeight / 2),
  options.get('lineColor')).append();
  target.drawLine(
  Math.round((rwhisker - minValue) * unitSize + canvasLeft),
  Math.round(canvasHeight / 4),
  Math.round((rwhisker - minValue) * unitSize + canvasLeft),
  Math.round(canvasHeight - canvasHeight / 4),
  options.get('whiskerColor')).append();
  // median line
  target.drawLine(
  Math.round((q2 - minValue) * unitSize + canvasLeft),
  Math.round(canvasHeight * 0.1),
  Math.round((q2 - minValue) * unitSize + canvasLeft),
  Math.round(canvasHeight * 0.9),
  options.get('medianColor')).append();
  if (options.get('target')) {
  size = Math.ceil(options.get('spotRadius'));
  target.drawLine(
  Math.round((options.get('target') - minValue) * unitSize + canvasLeft),
  Math.round((canvasHeight / 2) - size),
  Math.round((options.get('target') - minValue) * unitSize + canvasLeft),
  Math.round((canvasHeight / 2) + size),
  options.get('targetColor')).append();
  target.drawLine(
  Math.round((options.get('target') - minValue) * unitSize + canvasLeft - size),
  Math.round(canvasHeight / 2),
  Math.round((options.get('target') - minValue) * unitSize + canvasLeft + size),
  Math.round(canvasHeight / 2),
  options.get('targetColor')).append();
  }
  target.render();
  }
  });
 
  // Setup a very simple "virtual canvas" to make drawing the few shapes we need easier
  // This is accessible as $(foo).simpledraw()
 
  if ($.browser.msie && document.namespaces && !document.namespaces.v) {
  document.namespaces.add('v', 'urn:schemas-microsoft-com:vml', '#default#VML');
  }
 
  if ($.browser.hasCanvas === undefined) {
  $.browser.hasCanvas = document.createElement('canvas').getContext !== undefined;
  }
 
  VShape = createClass({
  init: function (target, id, type, args) {
  this.target = target;
  this.id = id;
  this.type = type;
  this.args = args;
  },
  append: function () {
  this.target.appendShape(this);
  return this;
  }
  });
 
  VCanvas_base = createClass({
  _pxregex: /(\d+)(px)?\s*$/i,
 
  init: function (width, height, target) {
  if (!width) {
  return;
  }
  this.width = width;
  this.height = height;
  this.target = target;
  this.lastShapeId = null;
  if (target[0]) {
  target = target[0];
  }
  $.data(target, '_jqs_vcanvas', this);
  },
 
  drawLine: function (x1, y1, x2, y2, lineColor, lineWidth) {
  return this.drawShape([[x1, y1], [x2, y2]], lineColor, lineWidth);
  },
 
  drawShape: function (path, lineColor, fillColor, lineWidth) {
  return this._genShape('Shape', [path, lineColor, fillColor, lineWidth]);
  },
 
  drawCircle: function (x, y, radius, lineColor, fillColor, lineWidth) {
  return this._genShape('Circle', [x, y, radius, lineColor, fillColor, lineWidth]);
  },
 
  drawPieSlice: function (x, y, radius, startAngle, endAngle, lineColor, fillColor) {
  return this._genShape('PieSlice', [x, y, radius, startAngle, endAngle, lineColor, fillColor]);
  },
 
  drawRect: function (x, y, width, height, lineColor, fillColor) {
  return this._genShape('Rect', [x, y, width, height, lineColor, fillColor]);
  },
 
  getElement: function () {
  return this.canvas;
  },
 
  /**
  * Return the most recently inserted shape id
  */
  getLastShapeId: function () {
  return this.lastShapeId;
  },
 
  /**
  * Clear and reset the canvas
  */
  reset: function () {
  alert('reset not implemented');
  },
 
  _insert: function (el, target) {
  $(target).html(el);
  },
 
  /**
  * Calculate the pixel dimensions of the canvas
  */
  _calculatePixelDims: function (width, height, canvas) {
  // XXX This should probably be a configurable option
  var match;
  match = this._pxregex.exec(height);
  if (match) {
  this.pixelHeight = match[1];
  } else {
  this.pixelHeight = $(canvas).height();
  }
  match = this._pxregex.exec(width);
  if (match) {
  this.pixelWidth = match[1];
  } else {
  this.pixelWidth = $(canvas).width();
  }
  },
 
  /**
  * Generate a shape object and id for later rendering
  */
  _genShape: function (shapetype, shapeargs) {
  var id = shapeCount++;
  shapeargs.unshift(id);
  return new VShape(this, id, shapetype, shapeargs);
  },
 
  /**
  * Add a shape to the end of the render queue
  */
  appendShape: function (shape) {
  alert('appendShape not implemented');
  },
 
  /**
  * Replace one shape with another
  */
  replaceWithShape: function (shapeid, shape) {
  alert('replaceWithShape not implemented');
  },
 
  /**
  * Insert one shape after another in the render queue
  */
  insertAfterShape: function (shapeid, shape) {
  alert('insertAfterShape not implemented');
  },
 
  /**
  * Remove a shape from the queue
  */
  removeShapeId: function (shapeid) {
  alert('removeShapeId not implemented');
  },
 
  /**
  * Find a shape at the specified x/y co-ordinates
  */
  getShapeAt: function (el, x, y) {
  alert('getShapeAt not implemented');
  },
 
  /**
  * Render all queued shapes onto the canvas
  */
  render: function () {
  alert('render not implemented');
  }
  });
 
  VCanvas_canvas = createClass(VCanvas_base, {
  init: function (width, height, target, interact) {
  VCanvas_canvas._super.init.call(this, width, height, target);
  this.canvas = document.createElement('canvas');
  if (target[0]) {
  target = target[0];
  }
  $.data(target, '_jqs_vcanvas', this);
  $(this.canvas).css({ display: 'inline-block', width: width, height: height, verticalAlign: 'top' });
  this._insert(this.canvas, target);
  this._calculatePixelDims(width, height, this.canvas);
  this.canvas.width = this.pixelWidth;
  this.canvas.height = this.pixelHeight;
  this.interact = interact;
  this.shapes = {};
  this.shapeseq = [];
  this.currentTargetShapeId = undefined;
  $(this.canvas).css({width: this.pixelWidth, height: this.pixelHeight});
  },
 
  _getContext: function (lineColor, fillColor, lineWidth) {
  var context = this.canvas.getContext('2d');
  if (lineColor !== undefined) {
  context.strokeStyle = lineColor;
  }
  context.lineWidth = lineWidth === undefined ? 1 : lineWidth;
  if (fillColor !== undefined) {
  context.fillStyle = fillColor;
  }
  return context;
  },
 
  reset: function () {
  var context = this._getContext();
  context.clearRect(0, 0, this.pixelWidth, this.pixelHeight);
  this.shapes = {};
  this.shapeseq = [];
  this.currentTargetShapeId = undefined;
  },
 
  _drawShape: function (shapeid, path, lineColor, fillColor, lineWidth) {
  var context = this._getContext(lineColor, fillColor, lineWidth),
  i, plen;
  context.beginPath();
  context.moveTo(path[0][0] + 0.5, path[0][1] + 0.5);
  for (i = 1, plen = path.length; i < plen; i++) {
  context.lineTo(path[i][0] + 0.5, path[i][1] + 0.5); // the 0.5 offset gives us crisp pixel-width lines
  }
  if (lineColor !== undefined) {
  context.stroke();
  }
  if (fillColor !== undefined) {
  context.fill();
  }
  if (this.targetX !== undefined && this.targetY !== undefined &&
  context.isPointInPath(this.targetX, this.targetY)) {
  this.currentTargetShapeId = shapeid;
  }
  },
 
  _drawCircle: function (shapeid, x, y, radius, lineColor, fillColor, lineWidth) {
  var context = this._getContext(lineColor, fillColor, lineWidth);
  context.beginPath();
  context.arc(x, y, radius, 0, 2 * Math.PI, false);
  if (this.targetX !== undefined && this.targetY !== undefined &&
  context.isPointInPath(this.targetX, this.targetY)) {
  this.currentTargetShapeId = shapeid;
  }
  if (lineColor !== undefined) {
  context.stroke();
  }
  if (fillColor !== undefined) {
  context.fill();
  }
  },
 
  _drawPieSlice: function (shapeid, x, y, radius, startAngle, endAngle, lineColor, fillColor) {
  var context = this._getContext(lineColor, fillColor);
  context.beginPath();
  context.moveTo(x, y);
  context.arc(x, y, radius, startAngle, endAngle, false);
  context.lineTo(x, y);
  context.closePath();
  if (lineColor !== undefined) {
  context.stroke();
  }
  if (fillColor) {
  context.fill();
  }
  if (this.targetX !== undefined && this.targetY !== undefined &&
  context.isPointInPath(this.targetX, this.targetY)) {
  this.currentTargetShapeId = shapeid;
  }
  },
 
  _drawRect: function (shapeid, x, y, width, height, lineColor, fillColor) {
  return this._drawShape(shapeid, [[x, y], [x + width, y], [x + width, y + height], [x, y + height], [x, y]], lineColor, fillColor);
  },
 
  appendShape: function (shape) {
  this.shapes[shape.id] = shape;
  this.shapeseq.push(shape.id);
  this.lastShapeId = shape.id;
  return shape.id;
  },
 
  replaceWithShape: function (shapeid, shape) {
  var shapeseq = this.shapeseq,
  i;
  this.shapes[shape.id] = shape;
  for (i = shapeseq.length; i--;) {
  if (shapeseq[i] == shapeid) {
  shapeseq[i] = shape.id;
  }
  }
  delete this.shapes[shapeid];
  },
 
  replaceWithShapes: function (shapeids, shapes) {
  var shapeseq = this.shapeseq,
  shapemap = {},
  sid, i, first;
 
  for (i = shapeids.length; i--;) {
  shapemap[shapeids[i]] = true;
  }
  for (i = shapeseq.length; i--;) {
  sid = shapeseq[i];
  if (shapemap[sid]) {
  shapeseq.splice(i, 1);
  delete this.shapes[sid];
  first = i;
  }
  }
  for (i = shapes.length; i--;) {
  shapeseq.splice(first, 0, shapes[i].id);
  this.shapes[shapes[i].id] = shapes[i];
  }
 
  },
 
  insertAfterShape: function (shapeid, shape) {
  var shapeseq = this.shapeseq,
  i;
  for (i = shapeseq.length; i--;) {
  if (shapeseq[i] === shapeid) {
  shapeseq.splice(i + 1, 0, shape.id);
  this.shapes[shape.id] = shape;
  return;
  }
  }
  },
 
  removeShapeId: function (shapeid) {
  var shapeseq = this.shapeseq,
  i;
  for (i = shapeseq.length; i--;) {
  if (shapeseq[i] === shapeid) {
  shapeseq.splice(i, 1);
  break;
  }
  }
  delete this.shapes[shapeid];
  },
 
  getShapeAt: function (el, x, y) {
  this.targetX = x;
  this.targetY = y;
  this.render();
  return this.currentTargetShapeId;
  },
 
  render: function () {
  var shapeseq = this.shapeseq,
  shapes = this.shapes,
  shapeCount = shapeseq.length,
  context = this._getContext(),
  shapeid, shape, i;
  context.clearRect(0, 0, this.pixelWidth, this.pixelHeight);
  for (i = 0; i < shapeCount; i++) {
  shapeid = shapeseq[i];
  shape = shapes[shapeid];
  this['_draw' + shape.type].apply(this, shape.args);
  }
  if (!this.interact) {
  // not interactive so no need to keep the shapes array
  this.shapes = {};
  this.shapeseq = [];
  }
  }
 
  });
 
  VCanvas_vml = createClass(VCanvas_base, {
  init: function (width, height, target) {
  var groupel;
  VCanvas_vml._super.init.call(this, width, height, target);
  if (target[0]) {
  target = target[0];
  }
  $.data(target, '_jqs_vcanvas', this);
  this.canvas = document.createElement('span');
  $(this.canvas).css({ display: 'inline-block', position: 'relative', overflow: 'hidden', width: width, height: height, margin: '0px', padding: '0px', verticalAlign: 'top'});
  this._insert(this.canvas, target);
  this._calculatePixelDims(width, height, this.canvas);
  this.canvas.width = this.pixelWidth;
  this.canvas.height = this.pixelHeight;
  groupel = '<v:group coordorigin="0 0" coordsize="' + this.pixelWidth + ' ' + this.pixelHeight + '"' +
  ' style="position:absolute;top:0;left:0;width:' + this.pixelWidth + 'px;height=' + this.pixelHeight + 'px;"></v:group>';
  this.canvas.insertAdjacentHTML('beforeEnd', groupel);
  this.group = $(this.canvas).children()[0];
  this.rendered = false;
  this.prerender = '';
  },
 
  _drawShape: function (shapeid, path, lineColor, fillColor, lineWidth) {
  var vpath = [],
  initial, stroke, fill, closed, vel, plen, i;
  for (i = 0, plen = path.length; i < plen; i++) {
  vpath[i] = '' + (path[i][0]) + ',' + (path[i][1]);
  }
  initial = vpath.splice(0, 1);
  lineWidth = lineWidth === undefined ? 1 : lineWidth;
  stroke = lineColor === undefined ? ' stroked="false" ' : ' strokeWeight="' + lineWidth + 'px" strokeColor="' + lineColor + '" ';
  fill = fillColor === undefined ? ' filled="false"' : ' fillColor="' + fillColor + '" filled="true" ';
  closed = vpath[0] === vpath[vpath.length - 1] ? 'x ' : '';
  vel = '<v:shape coordorigin="0 0" coordsize="' + this.pixelWidth + ' ' + this.pixelHeight + '" ' +
  ' id="jqsshape' + shapeid + '" ' +
  stroke +
  fill +
  ' style="position:absolute;left:0px;top:0px;height:' + this.pixelHeight + 'px;width:' + this.pixelWidth + 'px;padding:0px;margin:0px;" ' +
  ' path="m ' + initial + ' l ' + vpath.join(', ') + ' ' + closed + 'e">' +
  ' </v:shape>';
  return vel;
  },
 
  _drawCircle: function (shapeid, x, y, radius, lineColor, fillColor, lineWidth) {
  var stroke, fill, vel;
  x -= radius;
  y -= radius;
  stroke = lineColor === undefined ? ' stroked="false" ' : ' strokeWeight="' + lineWidth + 'px" strokeColor="' + lineColor + '" ';
  fill = fillColor === undefined ? ' filled="false"' : ' fillColor="' + fillColor + '" filled="true" ';
  vel = '<v:oval ' +
  ' id="jqsshape' + shapeid + '" ' +
  stroke +
  fill +
  ' style="position:absolute;top:' + y + 'px; left:' + x + 'px; width:' + (radius * 2) + 'px; height:' + (radius * 2) + 'px"></v:oval>';
  return vel;
 
  },
 
  _drawPieSlice: function (shapeid, x, y, radius, startAngle, endAngle, lineColor, fillColor) {
  var vpath, startx, starty, endx, endy, stroke, fill, vel;
  if (startAngle === endAngle) {
  return; // VML seems to have problem when start angle equals end angle.
  }
  if ((endAngle - startAngle) === (2 * Math.PI)) {
  startAngle = 0.0; // VML seems to have a problem when drawing a full circle that doesn't start 0
  endAngle = (2 * Math.PI);
  }
 
  startx = x + Math.round(Math.cos(startAngle) * radius);
  starty = y + Math.round(Math.sin(startAngle) * radius);
  endx = x + Math.round(Math.cos(endAngle) * radius);
  endy = y + Math.round(Math.sin(endAngle) * radius);
 
  // Prevent very small slices from being mistaken as a whole pie
  if (startx === endx && starty === endy && (endAngle - startAngle) < Math.PI) {
  return;
  }
 
  vpath = [x - radius, y - radius, x + radius, y + radius, startx, starty, endx, endy];
  stroke = lineColor === undefined ? ' stroked="false" ' : ' strokeWeight="1px" strokeColor="' + lineColor + '" ';
  fill = fillColor === undefined ? ' filled="false"' : ' fillColor="' + fillColor + '" filled="true" ';
  vel = '<v:shape coordorigin="0 0" coordsize="' + this.pixelWidth + ' ' + this.pixelHeight + '" ' +
  ' id="jqsshape' + shapeid + '" ' +
  stroke +
  fill +
  ' style="position:absolute;left:0px;top:0px;height:' + this.pixelHeight + 'px;width:' + this.pixelWidth + 'px;padding:0px;margin:0px;" ' +
  ' path="m ' + x + ',' + y + ' wa ' + vpath.join(', ') + ' x e">' +
  ' </v:shape>';
  return vel;
  },
 
  _drawRect: function (shapeid, x, y, width, height, lineColor, fillColor) {
  return this._drawShape(shapeid, [[x, y], [x, y + height], [x + width, y + height], [x + width, y], [x, y]], lineColor, fillColor);
  },
 
  reset: function () {
  this.group.innerHTML = '';
  },
 
  appendShape: function (shape) {
  var vel = this['_draw' + shape.type].apply(this, shape.args);
  if (this.rendered) {
  this.group.insertAdjacentHTML('beforeEnd', vel);
  } else {
  this.prerender += vel;
  }
  this.lastShapeId = shape.id;
  return shape.id;
  },
 
  replaceWithShape: function (shapeid, shape) {
  var existing = $('#jqsshape' + shapeid),
  vel = this['_draw' + shape.type].apply(this, shape.args);
  existing[0].outerHTML = vel;
  },
 
  replaceWithShapes: function (shapeids, shapes) {
  // replace the first shapeid with all the new shapes then toast the remaining old shapes
  var existing = $('#jqsshape' + shapeids[0]),
  replace = '',
  slen = shapes.length,
  i;
  for (i = 0; i < slen; i++) {
  replace += this['_draw' + shapes[i].type].apply(this, shapes[i].args);
  }
  existing[0].outerHTML = replace;
  for (i = 1; i < shapeids.length; i++) {
  $('#jqsshape' + shapeids[i]).remove();
  }
  },
 
  insertAfterShape: function (shapeid, shape) {
  var existing = $('#jqsshape' + shapeid),
  vel = this['_draw' + shape.type].apply(this, shape.args);
  existing[0].insertAdjacentHTML('afterEnd', vel);
  },
 
  removeShapeId: function (shapeid) {
  var existing = $('#jqsshape' + shapeid);
  this.group.removeChild(existing[0]);
  },
 
  getShapeAt: function (el, x, y) {
  var shapeid = el.id.substr(8);
  return shapeid;
  },
 
  render: function () {
  if (!this.rendered) {
  // batch the intial render into a single repaint
  this.group.innerHTML = this.prerender;
  this.rendered = true;
  }
  }
  });
 
  }));
 
  .rickshaw_graph .detail{pointer-events:none;position:absolute;top:0;z-index:2;background:rgba(0,0,0,.1);bottom:0;width:1px;transition:opacity .25s linear;-moz-transition:opacity .25s linear;-o-transition:opacity .25s linear;-webkit-transition:opacity .25s linear}.rickshaw_graph .detail.inactive{opacity:0}.rickshaw_graph .detail .item.active{opacity:1}.rickshaw_graph .detail .x_label{font-family:Arial,sans-serif;border-radius:3px;padding:6px;opacity:.5;border:1px solid #e0e0e0;font-size:12px;position:absolute;background:#fff;white-space:nowrap}.rickshaw_graph .detail .item{position:absolute;z-index:2;border-radius:3px;padding:.25em;font-size:12px;font-family:Arial,sans-serif;opacity:0;background:rgba(0,0,0,.4);color:#fff;border:1px solid rgba(0,0,0,.4);margin-left:1em;margin-top:-1em;white-space:nowrap}.rickshaw_graph .detail .item.active{opacity:1;background:rgba(0,0,0,.8)}.rickshaw_graph .detail .item:before{content:"\25c2";position:absolute;left:-.5em;color:rgba(0,0,0,.7);width:0}.rickshaw_graph .detail .dot{width:4px;height:4px;margin-left:-4px;margin-top:-3px;border-radius:5px;position:absolute;box-shadow:0 0 2px rgba(0,0,0,.6);background:#fff;border-width:2px;border-style:solid;display:none;background-clip:padding-box}.rickshaw_graph .detail .dot.active{display:block}.rickshaw_graph{position:relative}.rickshaw_graph svg{display:block;overflow:hidden}.rickshaw_graph .x_tick{position:absolute;top:0;bottom:0;width:0;border-left:1px dotted rgba(0,0,0,.2);pointer-events:none}.rickshaw_graph .x_tick .title{position:absolute;font-size:12px;font-family:Arial,sans-serif;opacity:.5;white-space:nowrap;margin-left:3px;bottom:1px}.rickshaw_annotation_timeline{height:1px;border-top:1px solid #e0e0e0;margin-top:10px;position:relative}.rickshaw_annotation_timeline .annotation{position:absolute;height:6px;width:6px;margin-left:-2px;top:-3px;border-radius:5px;background-color:rgba(0,0,0,.25)}.rickshaw_graph .annotation_line{position:absolute;top:0;bottom:-6px;width:0;border-left:2px solid rgba(0,0,0,.3);display:none}.rickshaw_graph .annotation_line.active{display:block}.rickshaw_graph .annotation_range{background:rgba(0,0,0,.1);display:none;position:absolute;top:0;bottom:-6px;z-index:-10}.rickshaw_graph .annotation_range.active{display:block}.rickshaw_graph .annotation_range.active.offscreen{display:none}.rickshaw_annotation_timeline .annotation .content{background:#fff;color:#000;opacity:.9;padding:5px 5px;box-shadow:0 0 2px rgba(0,0,0,.8);border-radius:3px;position:relative;z-index:20;font-size:12px;padding:6px 8px 8px;top:18px;left:-11px;width:160px;display:none;cursor:pointer}.rickshaw_annotation_timeline .annotation .content:before{content:"\25b2";position:absolute;top:-11px;color:#fff;text-shadow:0 -1px 1px rgba(0,0,0,.8)}.rickshaw_annotation_timeline .annotation.active,.rickshaw_annotation_timeline .annotation:hover{background-color:rgba(0,0,0,.8);cursor:none}.rickshaw_annotation_timeline .annotation .content:hover{z-index:50}.rickshaw_annotation_timeline .annotation.active .content{display:block}.rickshaw_annotation_timeline .annotation:hover .content{display:block;z-index:50}.rickshaw_graph .y_axis{fill:none}.rickshaw_graph .y_ticks .tick{stroke:rgba(0,0,0,.16);stroke-width:2px;shape-rendering:crisp-edges;pointer-events:none}.rickshaw_graph .y_grid .tick{z-index:-1;stroke:rgba(0,0,0,.20);stroke-width:1px;stroke-dasharray:1 1}.rickshaw_graph .y_grid path{fill:none;stroke:none}.rickshaw_graph .y_ticks path{fill:none;stroke:#808080}.rickshaw_graph .y_ticks text{opacity:.5;font-size:12px;pointer-events:none}.rickshaw_graph .x_tick.glow .title,.rickshaw_graph .y_ticks.glow text{fill:black;color:#000;text-shadow:-1px 1px 0 rgba(255,255,255,.1),1px -1px 0 rgba(255,255,255,.1),1px 1px 0 rgba(255,255,255,.1),0px 1px 0 rgba(255,255,255,.1),0px -1px 0 rgba(255,255,255,.1),1px 0 0 rgba(255,255,255,.1),-1px 0 0 rgba(255,255,255,.1),-1px -1px 0 rgba(255,255,255,.1)}.rickshaw_graph .x_tick.inverse .title,.rickshaw_graph .y_ticks.inverse text{fill:white;color:#fff;text-shadow:-1px 1px 0 rgba(0,0,0,.8),1px -1px 0 rgba(0,0,0,.8),1px 1px 0 rgba(0,0,0,.8),0px 1px 0 rgba(0,0,0,.8),0px -1px 0 rgba(0,0,0,.8),1px 0 0 rgba(0,0,0,.8),-1px 0 0 rgba(0,0,0,.8),-1px -1px 0 rgba(0,0,0,.8)}.rickshaw_legend{font-family:Arial;font-size:12px;color:#fff;background:#404040;display:inline-block;padding:12px 5px;border-radius:2px;position:relative}.rickshaw_legend:hover{z-index:10}.rickshaw_legend .swatch{width:10px;height:10px;border:1px solid rgba(0,0,0,.2)}.rickshaw_legend .line{clear:both;line-height:140%;padding-right:15px}.rickshaw_legend .line .swatch{display:inline-block;margin-right:3px;border-radius:2px}.rickshaw_legend .label{white-space:nowrap;display:inline}.rickshaw_legend .action:hover{opacity:.6}.rickshaw_legend .action{margin-right:.2em;font-size:10px;opacity:.2;cursor:pointer;font-size:14px}.rickshaw_legend .line.disabled{opacity:.4}.rickshaw_legend ul{list-style-type:none;margin:0;padding:0;margin:2px;cursor:pointer}.rickshaw_legend li{padding:0 0 0 2px;min-width:80px;white-space:nowrap}.rickshaw_legend li:hover{background:rgba(255,255,255,.08);border-radius:3px}.rickshaw_legend li:active{background:rgba(255,255,255,.2);border-radius:3px}
  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.appendChild(e),e.addEventListener("click",function(a){e.classList.toggle("active"),b.line.classList.toggle("active"),b.boxes.forEach(function(a){a.rangeElement&&a.rangeElement.classList.toggle("active")})},!1)}b.element.style.left=d+"px",b.element.style.display="block",b.boxes.forEach(function(a){var e=a.element;e||(e=a.element=document.createElement("div"),e.classList.add("content"),e.innerHTML=a.content,b.element.appendChild(e),b.line=document.createElement("div"),b.line.classList.add("annotation_line"),c.graph.element.appendChild(b.line),a.end&&(a.rangeElement=document.createElement("div"),a.rangeElement.classList.add("annotation_range"),c.graph.element.appendChild(a.rangeElement)));if(a.end){var f=d,g=Math.min(c.graph.x(a.end),c.graph.x.range()[1]);f>g&&(g=d,f=Math.max(c.graph.x(a.end),c.graph.x.range()[0]));var h=g-f;a.rangeElement.style.left=f+"px",a.rangeElement.style.width=h+"px",a.rangeElement.classList.remove("offscreen")}b.line.classList.remove("offscreen"),b.line.style.left=d+"px"})},this)},this.graph.onUpdate(function(){c.update()})},Rickshaw.namespace("Rickshaw.Graph.Axis.Time"),Rickshaw.Graph.Axis.Time=function(a){var b=this;this.graph=a.graph,this.elements=[],this.ticksTreatment=a.ticksTreatment||"plain",this.fixedTimeUnit=a.timeUnit;var c=new Rickshaw.Fixtures.Time;this.appropriateTimeUnit=function(){var a,b=c.units,d=this.graph.x.domain(),e=d[1]-d[0];return b.forEach(function(b){Math.floor(e/b.seconds)>=2&&(a=a||b)}),a||c.units[c.units.length-1]},this.tickOffsets=function(){var a=this.graph.x.domain(),b=this.fixedTimeUnit||this.appropriateTimeUnit(),d=Math.ceil((a[1]-a[0])/b.seconds),e=a[0],f=[];for(var g=0;g<d;g++)tickValue=c.ceil(e,b),e=tickValue+b.seconds/2,f.push({value:tickValue,unit:b});return f},this.render=function(){this.elements.forEach(function(a){a.parentNode.removeChild(a)}),this.elements=[];var a=this.tickOffsets();a.forEach(function(a){if(b.graph.x(a.value)>b.graph.x.range()[1])return;var c=document.createElement("div");c.style.left=b.graph.x(a.value)+"px",c.classList.add("x_tick"),c.classList.add(b.ticksTreatment);var d=document.createElement("div");d.classList.add("title"),d.innerHTML=a.unit.formatter(new Date(a.value*1e3)),c.appendChild(d),b.graph.element.appendChild(c),b.elements.push(c)})},this.graph.onUpdate(function(){b.render()})},Rickshaw.namespace("Rickshaw.Graph.Axis.Y"),Rickshaw.Graph.Axis.Y=function(a){var b=this,c=.1;this.initialize=function(a){this.graph=a.graph,this.orientation=a.orientation||"right";var c=a.pixelsPerTick||75;this.ticks=a.ticks||Math.floor(this.graph.height/c),this.tickSize=a.tickSize||4,this.ticksTreatment=a.ticksTreatment||"plain",a.element?(this.element=a.element,this.vis=d3.select(a.element).append("svg:svg").attr("class","rickshaw_graph y_axis"),this.element=this.vis[0][0],this.element.style.position="relative",this.setSize({width:a.width,height:a.height})):this.vis=this.graph.vis,this.graph.onUpdate(function(){b.render()})},this.setSize=function(a){a=a||{};if(!this.element)return;if(typeof window!="undefined"){var b=window.getComputedStyle(this.element.parentNode,null),d=parseInt(b.getPropertyValue("width"));if(!a.auto)var e=parseInt(b.getPropertyValue("height"))}this.width=a.width||d||this.graph.width*c,this.height=a.height||e||this.graph.height,this.vis.attr("width",this.width).attr("height",this.height*(1+c));var f=this.height*c;this.element.style.top=-1*f+"px"},this.render=function(){this.graph.height!==this._renderHeight&&this.setSize({auto:!0});var b=d3.svg.axis().scale(this.graph.y).orient(this.orientation);b.tickFormat(a.tickFormat||function(a){return a});if(this.orientation=="left")var d=this.height*c,e="translate("+this.width+", "+d+")";this.element&&this.vis.selectAll("*").remove(),this.vis.append("svg:g").attr("class",["y_ticks",this.ticksTreatment].join(" ")).attr("transform",e).call(b.ticks(this.ticks).tickSubdivide(0).tickSize(this.tickSize));var f=(this.orientation=="right"?1:-1)*this.graph.width;this.graph.vis.append("svg:g").attr("class","y_grid").call(b.ticks(this.ticks).tickSubdivide(0).tickSize(f)),this._renderHeight=this.graph.height},this.initialize(a)},Rickshaw.namespace("Rickshaw.Graph.Behavior.Series.Highlight"),Rickshaw.Graph.Behavior.Series.Highlight=function(a){this.graph=a.graph,this.legend=a.legend;var b=this,c={};this.addHighlightEvents=function(a){a.element.addEventListener("mouseover",function(d){b.legend.lines.forEach(function(b){if(a===b)return;c[b.series.name]=c[b.series.name]||b.series.color,b.series.color=d3.interpolateRgb(b.series.color,d3.rgb("#d8d8d8"))(.8).toString()}),b.graph.update()},!1),a.element.addEventListener("mouseout",function(a){b.legend.lines.forEach(function(a){c[a.series.name]&&(a.series.color=c[a.series.name])}),b.graph.update()},!1)},this.legend&&this.legend.lines.forEach(function(a){b.addHighlightEvents(a)})},Rickshaw.namespace("Rickshaw.Graph.Behavior.Series.Order"),Rickshaw.Graph.Behavior.Series.Order=function(a){this.graph=a.graph,this.legend=a.legend;var b=this;$(function(){$(b.legend.list).sortable({containment:"parent",tolerance:"pointer",update:function(a,c){var d=[];$(b.legend.list).find("li").each(function(a,b){if(!b.series)return;d.push(b.series)});for(var e=b.graph.series.length-1;e>=0;e--)b.graph.series[e]=d.shift();b.graph.update()}}),$(b.legend.list).disableSelection()}),this.graph.onUpdate(function(){var a=window.getComputedStyle(b.legend.element).height;b.legend.element.style.height=a})},Rickshaw.namespace("Rickshaw.Graph.Behavior.Series.Toggle"),Rickshaw.Graph.Behavior.Series.Toggle=function(a){this.graph=a.graph,this.legend=a.legend;var b=this;this.addAnchor=function(a){var c=document.createElement("a");c.innerHTML="&#10004;",c.classList.add("action"),a.element.insertBefore(c,a.element.firstChild),c.onclick=function(b){a.series.disabled?(a.series.enable(),a.element.classList.remove("disabled")):(a.series.disable(),a.element.classList.add("disabled"))};var d=a.element.getElementsByTagName("span")[0];d.onclick=function(c){var d=a.series.disabled;if(!d)for(var e=0;e<b.legend.lines.length;e++){var f=b.legend.lines[e];if(a.series!==f.series&&!f.series.disabled){d=!0;break}}d?(a.series.enable(),a.element.classList.remove("disabled"),b.legend.lines.forEach(function(b){a.series!==b.series&&(b.series.disable(),b.element.classList.add("disabled"))})):b.legend.lines.forEach(function(a){a.series.enable(),a.element.classList.remove("disabled")})}},this.legend&&($(this.legend.list).sortable({start:function(a,b){b.item.bind("no.onclick",function(a){a.preventDefault()})},stop:function(a,b){setTimeout(function(){b.item.unbind("no.onclick")},250)}}),this.legend.lines.forEach(function(a){b.addAnchor(a)})),this._addBehavior=function(){this.graph.series.forEach(function(a){a.disable=function(){if(b.graph.series.length<=1)throw"only one series left";a.disabled=!0,b.graph.update()},a.enable=function(){a.disabled=!1,b.graph.update()}})},this._addBehavior(),this.updateBehaviour=function(){this._addBehavior()}},Rickshaw.namespace("Rickshaw.Graph.HoverDetail"),Rickshaw.Graph.HoverDetail=Rickshaw.Class.create({initialize:function(a){var b=this.graph=a.graph;this.xFormatter=a.xFormatter||function(a){return(new Date(a*1e3)).toUTCString()},this.yFormatter=a.yFormatter||function(a){return a.toFixed(2)};var c=this.element=document.createElement("div");c.className="detail",this.visible=!0,b.element.appendChild(c),this.lastEvent=null,this._addListeners(),this.onShow=a.onShow,this.onHide=a.onHide,this.onRender=a.onRender,this.formatter=a.formatter||this.formatter},formatter:function(a,b,c,d,e,f){return a.name+":&nbsp;"+e},update:function(a){a=a||this.lastEvent;if(!a)return;this.lastEvent=a;if(!a.target.nodeName.match(/^(path|svg|rect)$/))return;var b=this.graph,c=a.offsetX||a.layerX,d=a.offsetY||a.layerY,e=b.x.invert(c),f=b.stackedData,g=f.slice(-1).shift(),h=d3.scale.linear().domain([g[0].x,g.slice(-1).shift().x]).range([0,g.length]),i=Math.floor(h(e)),j=Math.min(i||0,f[0].length-1);for(var k=i;k<f[0].length-1;){if(!f[0][k]||!f[0][k+1])break;if(f[0][k].x<=e&&f[0][k+1].x>e){j=k;break}f[0][k+1]<=e?k++:k--}var e=f[0][j].x,l=this.xFormatter(e),m=b.x(e),n=0,o=b.series.active().map(function(a){return{order:n++,series:a,name:a.name,value:a.stack[j]}}),p,q=function(a,b){return a.value.y0+a.value.y-(b.value.y0+b.value.y)},r=b.y.magnitude.invert(b.element.offsetHeight-d);o.sort(q).forEach(function(a){a.formattedYValue=this.yFormatter.constructor==Array?this.yFormatter[o.indexOf(a)](a.value.y):this.yFormatter(a.value.y),a.graphX=m,a.graphY=b.y(a.value.y0+a.value.y),r>a.value.y0&&r<a.value.y0+a.value.y&&!p&&(p=a,a.active=!0)},this),this.element.innerHTML="",this.element.style.left=b.x(e)+"px",this.visible&&this.render({detail:o,domainX:e,formattedXValue:l,mouseX:c,mouseY:d})},hide:function(){this.visible=!1,this.element.classList.add("inactive"),typeof this.onHide=="function"&&this.onHide()},show:function(){this.visible=!0,this.element.classList.remove("inactive"),typeof this.onShow=="function"&&this.onShow()},render:function(a){var b=a.detail,c=a.domainX,d=a.mouseX,e=a.mouseY,f=a.formattedXValue,g=document.createElement("div");g.className="x_label",g.innerHTML=f,this.element.appendChild(g),b.forEach(function(a){var b=document.createElement("div");b.className="item",b.innerHTML=this.formatter(a.series,c,a.value.y,f,a.formattedYValue,a),b.style.top=this.graph.y(a.value.y0+a.value.y)+"px",this.element.appendChild(b);var d=document.createElement("div");d.className="dot",d.style.top=b.style.top,d.style.borderColor=a.series.color,this.element.appendChild(d),a.active&&(b.className="item active",d.className="dot active")},this),this.show(),typeof this.onRender=="function"&&this.onRender(a)},_addListeners:function(){this.graph.element.addEventListener("mousemove",function(a){this.visible=!0,this.update(a)}.bind(this),!1),this.graph.onUpdate(function(){this.update()}.bind(this)),this.graph.element.addEventListener("mouseout",function(a){a.relatedTarget&&!(a.relatedTarget.compareDocumentPosition(this.graph.element)&Node.DOCUMENT_POSITION_CONTAINS)&&this.hide()}.bind(this),!1)}}),Rickshaw.namespace("Rickshaw.Graph.JSONP"),Rickshaw.Graph.JSONP=Rickshaw.Class.create(Rickshaw.Graph.Ajax,{request:function(){$.ajax({url:this.dataURL,dataType:"jsonp",success:this.success.bind(this),error:this.error.bind(this)})}}),Rickshaw.namespace("Rickshaw.Graph.Legend"),Rickshaw.Graph.Legend=function(a){var b=this.element=a.element,c=this.graph=a.graph,d=this;b.classList.add("rickshaw_legend");var e=this.list=document.createElement("ul");b.appendChild(e);var f=c.series.map(function(a){return a}).reverse();this.lines=[],this.addLine=function(a){var b=document.createElement("li");b.className="line";var c=document.createElement("div");c.className="swatch",c.style.backgroundColor=a.color,b.appendChild(c);var f=document.createElement("span");f.className="label",f.innerHTML=a.name,b.appendChild(f),e.appendChild(b),b.series=a,a.noLegend&&(b.style.display="none");var g={element:b,series:a};d.shelving&&(d.shelving.addAnchor(g),d.shelving.updateBehaviour()),d.highlighter&&d.highlighter.addHighlightEvents(g),d.lines.push(g)},f.forEach(function(a){d.addLine(a)}),c.onUpdate(function(){})},Rickshaw.namespace("Rickshaw.Graph.RangeSlider"),Rickshaw.Graph.RangeSlider=function(a){var b=this.element=a.element,c=this.graph=a.graph;$(function(){$(b).slider({range:!0,min:c.dataDomain()[0],max:c.dataDomain()[1],values:[c.dataDomain()[0],c.dataDomain()[1]],slide:function(a,b){c.window.xMin=b.values[0],c.window.xMax=b.values[1],c.update(),c.dataDomain()[0]==b.values[0]&&(c.window.xMin=undefined),c.dataDomain()[1]==b.values[1]&&(c.window.xMax=undefined)}})}),b[0].style.width=c.width+"px",c.onUpdate(function(){var a=$(b).slider("option","values");$(b).slider("option","min",c.dataDomain()[0]),$(b).slider("option","max",c.dataDomain()[1]),c.window.xMin==undefined&&(a[0]=c.dataDomain()[0]),c.window.xMax==undefined&&(a[1]=c.dataDomain()[1]),$(b).slider("option","values",a)})},Rickshaw.namespace("Rickshaw.Graph.Renderer"),Rickshaw.Graph.Renderer=Rickshaw.Class.create({initialize:function(a){this.graph=a.graph,this.tension=a.tension||this.tension,this.graph.unstacker=this.graph.unstacker||new Rickshaw.Graph.Unstacker({graph:this.graph}),this.configure(a)},seriesPathFactory:function(){},seriesStrokeFactory:function(){},defaults:function(){return{tension:.8,strokeWidth:2,unstack:!0,padding:{top:.01,right:0,bottom:.01,left:0},stroke:!1,fill:!1}},domain:function(){var a=[],b=this.graph.stackedData||this.graph.stackData(),c=this.unstack?b:[b.slice(-1).shift()];c.forEach(function(b){b.forEach(function(b){a.push(b.y+b.y0)})});var d=b[0][0].x,e=b[0][b[0].length-1].x;d-=(e-d)*this.padding.left,e+=(e-d)*this.padding.right;var f=this.graph.min==="auto"?d3.min(a):this.graph.min||0,g=this.graph.max||d3.max(a);if(this.graph.min==="auto"||f<0)f-=(g-f)*this.padding.bottom;return this.graph.max===undefined&&(g+=(g-f)*this.padding.top),{x:[d,e],y:[f,g]}},render:function(){var a=this.graph;a.vis.selectAll("*").remove();var b=a.vis.selectAll("path").data(this.graph.stackedData).enter().append("svg:path").attr("d",this.seriesPathFactory()),c=0;a.series.forEach(function(a){if(a.disabled)return;a.path=b[0][c++],this._styleSeries(a)},this)},_styleSeries:function(a){var b=this.fill?a.color:"none",c=this.stroke?a.color:"none";a.path.setAttribute("fill",b),a.path.setAttribute("stroke",c),a.path.setAttribute("stroke-width",this.strokeWidth),a.path.setAttribute("class",a.className)},configure:function(a){a=a||{},Rickshaw.keys(this.defaults()).forEach(function(b){if(!a.hasOwnProperty(b)){this[b]=this[b]||this.graph[b]||this.defaults()[b];return}typeof this.defaults()[b]=="object"?Rickshaw.keys(this.defaults()[b]).forEach(function(c){this[b][c]=a[b][c]!==undefined?a[b][c]:this[b][c]!==undefined?this[b][c]:this.defaults()[b][c]},this):this[b]=a[b]!==undefined?a[b]:this[b]!==undefined?this[b]:this.graph[b]!==undefined?this.graph[b]:this.defaults()[b]},this)},setStrokeWidth:function(a){a!==undefined&&(this.strokeWidth=a)},setTension:function(a){a!==undefined&&(this.tension=a)}}),Rickshaw.namespace("Rickshaw.Graph.Renderer.Line"),Rickshaw.Graph.Renderer.Line=Rickshaw.Class.create(Rickshaw.Graph.Renderer,{name:"line",defaults:function($super){return Rickshaw.extend($super(),{unstack:!0,fill:!1,stroke:!0})},seriesPathFactory:function(){var a=this.graph;return d3.svg.line().x(function(b){return a.x(b.x)}).y(function(b){return a.y(b.y)}).interpolate(this.graph.interpolation).tension(this.tension)}}),Rickshaw.namespace("Rickshaw.Graph.Renderer.Stack"),Rickshaw.Graph.Renderer.Stack=Rickshaw.Class.create(Rickshaw.Graph.Renderer,{name:"stack",defaults:function($super){return Rickshaw.extend($super(),{fill:!0,stroke:!1,unstack:!1})},seriesPathFactory:function(){var a=this.graph;return d3.svg.area().x(function(b){return a.x(b.x)}).y0(function(b){return a.y(b.y0)}).y1(function(b){return a.y(b.y+b.y0)}).interpolate(this.graph.interpolation).tension(this.tension)}}),Rickshaw.namespace("Rickshaw.Graph.Renderer.Bar"),Rickshaw.Graph.Renderer.Bar=Rickshaw.Class.create(Rickshaw.Graph.Renderer,{name:"bar",defaults:function($super){var a=Rickshaw.extend($super(),{gapSize:.05,unstack:!1});return delete a.tension,a},initialize:function($super,a){a=a||{},this.gapSize=a.gapSize||this.gapSize,$super(a)},domain:function($super){var a=$super(),b=this._frequentInterval();return a.x[1]+=parseInt(b.magnitude),a},barWidth:function(){var a=this.graph.stackedData||this.graph.stackData(),b=a.slice(-1).shift(),c=this._frequentInterval(),d=this.graph.x(b[0].x+c.magnitude*(1-this.gapSize));return d},render:function(){var a=this.graph;a.vis.selectAll("*").remove();var b=this.barWidth(),c=0,d=a.series.filter(function(a){return!a.disabled}).length,e=this.unstack?b/d:b,f=function(b){var c=[1,0,0,b.y<0?-1:1,0,b.y<0?a.y.magnitude(Math.abs(b.y))*2:0];return"matrix("+c.join(",")+")"};a.series.forEach(function(b){if(b.disabled)return;var d=a.vis.selectAll("path").data(b.stack).enter().append("svg:rect").attr("x",function(b){return a.x(b.x)+c}).attr("y",function(b){return a.y(b.y0+Math.abs(b.y))*(b.y<0?-1:1)}).attr("width",e).attr("height",function(b){return a.y.magnitude(Math.abs(b.y))}).attr("transform",f);Array.prototype.forEach.call(d[0],function(a){a.setAttribute("fill",b.color)}),this.unstack&&(c+=e)},this)},_frequentInterval:function(){var a=this.graph.stackedData||this.graph.stackData(),b=a.slice(-1).shift(),c={};for(var d=0;d<b.length-1;d++){var e=b[d+1].x-b[d].x;c[e]=c[e]||0,c[e]++}var f={count:0};return Rickshaw.keys(c).forEach(function(a){f.count<c[a]&&(f={count:c[a],magnitude:a})}),this._frequentInterval=function(){return f},f}}),Rickshaw.namespace("Rickshaw.Graph.Renderer.Area"),Rickshaw.Graph.Renderer.Area=Rickshaw.Class.create(Rickshaw.Graph.Renderer,{name:"area",defaults:function($super){return Rickshaw.extend($super(),{unstack:!1,fill:!1,stroke:!1})},seriesPathFactory:function(){var a=this.graph;return d3.svg.area().x(function(b){return a.x(b.x)}).y0(function(b){return a.y(b.y0)}).y1(function(b){return a.y(b.y+b.y0)}).interpolate(a.interpolation).tension(this.tension)},seriesStrokeFactory:function(){var a=this.graph;return d3.svg.line().x(function(b){return a.x(b.x)}).y(function(b){return a.y(b.y+b.y0)}).interpolate(a.interpolation).tension(this.tension)},render:function(){var a=this.graph;a.vis.selectAll("*").remove();var b=a.vis.selectAll("path").data(this.graph.stackedData).enter().insert("svg:g","g");b.append("svg:path").attr("d",
  this.seriesPathFactory()).attr("class","area"),this.stroke&&b.append("svg:path").attr("d",this.seriesStrokeFactory()).attr("class","line");var c=0;a.series.forEach(function(a){if(a.disabled)return;a.path=b[0][c++],this._styleSeries(a)},this)},_styleSeries:function(a){if(!a.path)return;d3.select(a.path).select(".area").attr("fill",a.color),this.stroke&&d3.select(a.path).select(".line").attr("fill","none").attr("stroke",a.stroke||d3.interpolateRgb(a.color,"black")(.125)).attr("stroke-width",this.strokeWidth),a.className&&a.path.setAttribute("class",a.className)}}),Rickshaw.namespace("Rickshaw.Graph.Renderer.ScatterPlot"),Rickshaw.Graph.Renderer.ScatterPlot=Rickshaw.Class.create(Rickshaw.Graph.Renderer,{name:"scatterplot",defaults:function($super){return Rickshaw.extend($super(),{unstack:!0,fill:!0,stroke:!1,padding:{top:.01,right:.01,bottom:.01,left:.01},dotSize:4})},initialize:function($super,a){$super(a)},render:function(){var a=this.graph;a.vis.selectAll("*").remove(),a.series.forEach(function(b){if(b.disabled)return;var c=a.vis.selectAll("path").data(b.stack).enter().append("svg:circle").attr("cx",function(b){return a.x(b.x)}).attr("cy",function(b){return a.y(b.y)}).attr("r",function(b){return"r"in b?b.r:a.renderer.dotSize});Array.prototype.forEach.call(c[0],function(a){a.setAttribute("fill",b.color)})},this)}}),Rickshaw.namespace("Rickshaw.Graph.Smoother"),Rickshaw.Graph.Smoother=function(a){this.graph=a.graph,this.element=a.element;var b=this;this.aggregationScale=1,this.element&&$(function(){$(b.element).slider({min:1,max:100,slide:function(a,c){b.setScale(c.value),b.graph.update()}})}),b.graph.stackData.hooks.data.push({name:"smoother",orderPosition:50,f:function(a){var c=[];return a.forEach(function(a){var d=[];while(a.length){var e=0,f=0,g=a.splice(0,b.aggregationScale);g.forEach(function(a){e+=a.x/g.length,f+=a.y/g.length}),d.push({x:e,y:f})}c.push(d)}),c}}),this.setScale=function(a){if(a<1)throw"scale out of range: "+a;this.aggregationScale=a,this.graph.update()}},Rickshaw.namespace("Rickshaw.Graph.Unstacker"),Rickshaw.Graph.Unstacker=function(a){this.graph=a.graph;var b=this;this.graph.stackData.hooks.after.push({name:"unstacker",f:function(a){return b.graph.renderer.unstack?(a.forEach(function(a){a.forEach(function(a){a.y0=0})}),a):a}})},Rickshaw.namespace("Rickshaw.Series"),Rickshaw.Series=Rickshaw.Class.create(Array,{initialize:function(a,b,c){c=c||{},this.palette=new Rickshaw.Color.Palette(b),this.timeBase=typeof c.timeBase=="undefined"?Math.floor((new Date).getTime()/1e3):c.timeBase;var d=typeof c.timeInterval=="undefined"?1e3:c.timeInterval;this.setTimeInterval(d),a&&typeof a=="object"&&a instanceof Array&&a.forEach(function(a){this.addItem(a)},this)},addItem:function(a){if(typeof a.name=="undefined")throw"addItem() needs a name";a.color=a.color||this.palette.color(a.name),a.data=a.data||[],a.data.length==0&&this.length&&this.getIndex()>0?this[0].data.forEach(function(b){a.data.push({x:b.x,y:0})}):a.data.length==0&&a.data.push({x:this.timeBase-(this.timeInterval||0),y:0}),this.push(a),this.legend&&this.legend.addLine(this.itemByName(a.name))},addData:function(a){var b=this.getIndex();Rickshaw.keys(a).forEach(function(a){this.itemByName(a)||this.addItem({name:a})},this),this.forEach(function(c){c.data.push({x:(b*this.timeInterval||1)+this.timeBase,y:a[c.name]||0})},this)},getIndex:function(){return this[0]&&this[0].data&&this[0].data.length?this[0].data.length:0},itemByName:function(a){for(var b=0;b<this.length;b++)if(this[b].name==a)return this[b]},setTimeInterval:function(a){this.timeInterval=a/1e3},setTimeBase:function(a){this.timeBase=a},dump:function(){var a={timeBase:this.timeBase,timeInterval:this.timeInterval,items:[]};return this.forEach(function(b){var c={color:b.color,name:b.name,data:[]};b.data.forEach(function(a){c.data.push({x:a.x,y:a.y})}),a.items.push(c)}),a},load:function(a){a.timeInterval&&(this.timeInterval=a.timeInterval),a.timeBase&&(this.timeBase=a.timeBase),a.items&&a.items.forEach(function(a){this.push(a),this.legend&&this.legend.addLine(this.itemByName(a.name))},this)}}),Rickshaw.Series.zeroFill=function(a){var b,c=0,d=a.map(function(a){return a.data});while(c<Math.max.apply(null,d.map(function(a){return a.length})))b=Math.min.apply(null,d.filter(function(a){return a[c]}).map(function(a){return a[c].x})),d.forEach(function(a){(!a[c]||a[c].x!=b)&&a.splice(c,0,{x:b,y:0})}),c++},Rickshaw.namespace("Rickshaw.Series.FixedDuration"),Rickshaw.Series.FixedDuration=Rickshaw.Class.create(Rickshaw.Series,{initialize:function(a,b,c){var c=c||{};if(typeof c.timeInterval=="undefined")throw new Error("FixedDuration series requires timeInterval");if(typeof c.maxDataPoints=="undefined")throw new Error("FixedDuration series requires maxDataPoints");this.palette=new Rickshaw.Color.Palette(b),this.timeBase=typeof c.timeBase=="undefined"?Math.floor((new Date).getTime()/1e3):c.timeBase,this.setTimeInterval(c.timeInterval),this[0]&&this[0].data&&this[0].data.length?(this.currentSize=this[0].data.length,this.currentIndex=this[0].data.length):(this.currentSize=0,this.currentIndex=0),this.maxDataPoints=c.maxDataPoints,a&&typeof a=="object"&&a instanceof Array&&(a.forEach(function(a){this.addItem(a)},this),this.currentSize+=1,this.currentIndex+=1),this.timeBase-=(this.maxDataPoints-this.currentSize)*this.timeInterval;if(typeof this.maxDataPoints!="undefined"&&this.currentSize<this.maxDataPoints)for(var d=this.maxDataPoints-this.currentSize-1;d>0;d--)this.currentSize+=1,this.currentIndex+=1,this.forEach(function(a){a.data.unshift({x:((d-1)*this.timeInterval||1)+this.timeBase,y:0,i:d})},this)},addData:function($super,a){$super(a),this.currentSize+=1,this.currentIndex+=1;if(this.maxDataPoints!==undefined)while(this.currentSize>this.maxDataPoints)this.dropData()},dropData:function(){this.forEach(function(a){a.data.splice(0,1)}),this.currentSize-=1},getIndex:function(){return this.currentIndex}});
  <html xmlns:py="http://genshi.edgewall.org/"
  xmlns:i18n="http://genshi.edgewall.org/i18n"
  xmlns:xi="http://www.w3.org/2001/XInclude"
  py:strip="">
 
  <div class="popular_datasets">
  <div class="pull-right">
  <a href="${h.url_for(controller='ckanext.ga_report.controller:GaDatasetReport',action='read')}" class="btn btn-primary">More popular datasets »</a>
  </div>
  <h2>Popular datasets</h2>
  <h4>${publisher.title}</h4>
  <ul>
  <py:for each="dataset, _, _ in datasets">
  <li>
  <span>${h.link_to(dataset.title, h.url_for(controller='package', action='read', id=dataset.name))}</span>
  <div>${h.truncate(dataset.notes, length=80, whole_word=True)}</div>
  </li>
  </py:for>
  </ul>
 
  </div>
 
  </html>
 
 
 
 
  <html xmlns:py="http://genshi.edgewall.org/"
  xmlns:i18n="http://genshi.edgewall.org/i18n"
  xmlns:xi="http://www.w3.org/2001/XInclude"
  py:strip="">
 
 
  <div class="popular_datasets">
  <h2>Featured dataset</h2>
 
  <div class="dataset-summary boxed">
  <a class="dataset-header" href="${h.url_for(controller='package', action='read', id=dataset['name'])}">
  <h3>${dataset['title']}</h3>
  </a>
  <h4>
  <strong>Publisher</strong> :
  <a href="/publisher/${publisher['name']}">${publisher['title']}</a>
  </h4>
  <div>${h.truncate(dataset['notes_rendered'], length=200, whole_word=True)}</div>
  </div>
  <div>
  <a href="${h.url_for(controller='ckanext.ga_report.controller:GaDatasetReport',action='read')}" class="btn">Other popular datasets</a>
  <a href="${h.url_for(controller='ckanext.ga_report.controller:GaReport',action='index')}" class="btn">All usage data</a>
  </div>
  </div>
 
 
  </html>
 
 
 
 
  <html
  xmlns="http://www.w3.org/1999/xhtml"
  xmlns:i18n="http://genshi.edgewall.org/i18n"
  xmlns:py="http://genshi.edgewall.org/"
  xmlns:xi="http://www.w3.org/2001/XInclude"
  py:strip=""
  >
 
  <select name="month" py:def="month_selector(current_month, months, day)">
  <option value='' py:attrs="{'selected': 'selected' if not current_month else None}">All months</option>
  <py:for each="i, (val,desc) in enumerate(months)">
  <option value='${val}' py:attrs="{'selected': 'selected' if current_month == val else None}">${desc}<py:if test="i == 0 and day"> (up to ${day})</py:if></option>
  </py:for>
  </select>
 
 
  <table py:def="social_table(items, with_source=False)" class="table table-condensed table-bordered table-striped">
  <tr>
  <th>Name</th>
  <th py:if="with_source">Source</th>
  <th>Visits</th>
  </tr>
  <py:for each="name, url, source, count in items">
  <tr>
  <td><a href="${url}">${name}</a></td>
  <td py:if="with_source">${source}</td>
  <td>${count}</td>
  </tr>
  </py:for>
  </table>
 
 
  <div py:def="rickshaw_graph(items_json,id)">
  <div id="chart_container_$id" class="rickshaw_chart_container">
  <div id="y_axis_$id" class="rickshaw_y_axis"></div>
  <div id="chart_$id" class="rickshaw_chart"></div>
  <div id="legend_$id" class="rickshaw_legend"></div>
  <script type="text/javascript">
  $(function() {
  var all_series = ${items_json};
  var palette = new Rickshaw.Color.Palette( { scheme: 'munin' } );
  $.each(all_series, function(i, object) {
  object['color'] = palette.color();
  });
  var graph = new Rickshaw.Graph( {
  element: document.querySelector("#chart_$id"),
  renderer: 'line',
  series: all_series ,
  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_$id'),
  } );
  var legend = new Rickshaw.Graph.Legend( {
  element: document.querySelector('#legend_$id'),
  graph: graph
  } );
  graph.render();
  });
  </script>
  </div>
  </div>
 
 
  <table py:def="stat_table(items, title='Views')" class="table table-condensed table-bordered table-striped">
  <tr>
  <th>Name</th>
  <th>% ${title}</th>
  </tr>
  <py:for each="name, value in items">
  <tr>
  <td>${name}</td>
  <td>${value}</td>
  </tr>
  </py:for>
  </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">
  <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')}"><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')}"><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')}"><img src="/images/icons/page_white.png" height="16px" width="16px" alt="None" class="inline-icon "/> Datasets</a>
  </li>
  </ul>
  </div>
  </div>
  </div>
 
 
  </html>
 
  <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 since December 2012</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>
 
<html xmlns:py="http://genshi.edgewall.org/" <html xmlns:py="http://genshi.edgewall.org/"
xmlns:i18n="http://genshi.edgewall.org/i18n" xmlns:i18n="http://genshi.edgewall.org/i18n"
xmlns:xi="http://www.w3.org/2001/XInclude" xmlns:xi="http://www.w3.org/2001/XInclude"
py:strip=""> py:strip="">
   
<py:def function="page_title">Publisher Analytics for ${g.site_title}</py:def> <xi:include href="../ga_util.html" />
   
  <py:def function="page_title">Usage by Publisher</py:def>
   
<py:match path="primarysidebar"> <py:match path="primarysidebar">
<li class="widget-container boxed widget_text"> <li class="widget-container boxed widget_text">
<h4>Publishers</h4> <h4>Download</h4>
<p>The table shows the top 20 publishers as recorded by page views of datasets owned by that publisher, and the number of visits to each publisher's home page.</p> <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>
  <xi:include href="../notes.html" />
</py:match> </py:match>
   
  <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/vendor/jquery.sparkline.modified.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>
  </py:def>
   
<div py:match="content"> <div py:match="content">
<h1>Publisher Analytics</h1>  
<h2>The top 20 publishers</h2>  
   
<form class="form-inline" action="${h.url_for(controller='ckanext.ga_report.controller:GaPublisherReport',action='index')}" method="get"> <h1>Site Usage</h1>
   
  ${usage_nav('Publishers')}
   
   
  <form class="form-inline" action="${h.url_for(controller='ckanext.ga_report.controller:GaDatasetReport',action='publishers')}" method="get">
<div class="controls"> <div class="controls">
<select name="month">  
<py:for each="val,desc in c.months"> ${month_selector(c.month, c.months, c.day)}
<option value='${val}' py:attrs="{'selected': 'selected' if c.month == val else None}">${desc}</option>  
</py:for> <input class="btn button btn-primary" type='submit' value="Update"/>
</select>  
<input class="btn button" type='submit' value="Update"/>  
</div> </div>
</form> </form>
   
  ${rickshaw_graph(c.top_publishers_graph,'publishers')}
<table class="table table-condensed table-bordered table-striped"> <table class="table table-condensed table-bordered table-striped">
<tr> <tr>
<th>Publisher</th> <th>Publisher</th>
<th>Dataset Views</th> <th>Dataset Views</th>
<th>Visits</th>  
</tr> </tr>
<py:for each="publisher, views, visits in c.top_publishers"> <py:for each="publisher, views, visits in c.top_publishers">
<tr> <tr>
<td>${h.link_to(publisher.title, h.url_for(controller='ckanext.ga_report.controller:GaPublisherReport', action='read', id=publisher.name))} <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>
<td>${views}</td> <td>${views}</td>
<td>${visits}</td>  
</tr> </tr>
</py:for> </py:for>
</table> </table>
   
   
</div> </div>
   
<xi:include href="../../layout.html" /> <xi:include href="../../layout.html" />
   
<py:def function="optional_footer"> <py:def function="optional_footer">
<script type='text/javascript'> <script type='text/javascript'>
$('.nav-tabs li a').click(function (e) { $('.nav-tabs li a').click(function (e) {
e.preventDefault(); e.preventDefault();
$(this).tab('show'); $(this).tab('show');
}) })
</script> </script>
</py:def> </py:def>
   
</html> </html>
   
   
   
   
  <html xmlns:py="http://genshi.edgewall.org/"
  xmlns:i18n="http://genshi.edgewall.org/i18n"
  xmlns:xi="http://www.w3.org/2001/XInclude"
  py:strip="">
  <py:if test="dataset_count == 0">
  <p>We do not currently have usage data for ${publisher.title}</p>
  </py:if>
 
  <py:if test="dataset_count > 0">
  <div class="popular_datasets">
  <ul>
  <py:for each="dataset,views,visits in datasets">
  <li>
  ${h.link_to(dataset.title, h.url_for(controller='package', action='read', id=dataset.name))}
  </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>
  </div>
  </py:if>
  </html>
 
 
 
 
<html xmlns:py="http://genshi.edgewall.org/" <html xmlns:py="http://genshi.edgewall.org/"
xmlns:i18n="http://genshi.edgewall.org/i18n" xmlns:i18n="http://genshi.edgewall.org/i18n"
xmlns:xi="http://www.w3.org/2001/XInclude" xmlns:xi="http://www.w3.org/2001/XInclude"
py:strip=""> py:strip="">
   
<py:def function="page_title">Analytics for ${g.site_title}</py:def> <xi:include href="../ga_util.html" />
   
<py:match path="primarysidebar"> <py:def function="page_title">Usage by Dataset</py:def>
   
  <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/vendor/jquery.sparkline.modified.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>
  </py:def>
   
  <py:match path="primarysidebar">
<li class="widget-container boxed widget_text"> <li class="widget-container boxed widget_text">
<h4>${c.publisher.title}</h4> <h4>Download</h4>
<p> <p><center>
The table shows the top 20 most viewed datasets belonging to ${c.publisher.title}. <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> </p>
<p>  
As well as showing the number of views within ${c.month_desc}, it will also show the  
number of visitors that viewed each dataset.  
</p>  
<p>The dataset list page for <a href="${h.url_for(controller='ckanext.dgu.controllers.publisher:PublisherController', action='read', id=c.publisher.name)}">${c.publisher.title}</a> was viewed ${c.publisher_page_views} times during ${c.month_desc}</p>  
<p>View the <a href="${h.url_for(controller='ckanext.ga_report.controller:GaPublisherReport', action='index')}">publishers</a> leaderboard</p>  
</li> </li>
  <xi:include href="../notes.html" />
</py:match> </py:match>
   
   
<div py:match="content"> <div py:match="content">
<h1>Analytics for ${c.publisher.title}</h1> <h1>Site Usage</h1>
   
<h2>Top 20 most viewed datasets</h2> ${usage_nav('Datasets')}
<p><em>Note: this data does not include API calls</em></p>  
   
<form class="form-inline" action="${h.url_for(controller='ckanext.ga_report.controller:GaPublisherReport',action='read',id=c.publisher.name)}" method="get"> <form class="form-inline" action="${h.url_for(controller='ckanext.ga_report.controller:GaDatasetReport',action='read')}" method="get">
<div class="controls"> <div class="controls">
<select name="month">  
<py:for each="val,desc in c.months"> ${month_selector(c.month, c.months, c.day)}
<option value='${val}' py:attrs="{'selected': 'selected' if c.month == val else None}">${desc}</option>  
  <select name="publisher">
  <option value='' py:attrs="{'selected': 'selected' if not c.publisher else None}">All publishers</option>
  <py:for each="val,desc in c.publishers">
  <option value='${val}' py:attrs="{'selected': 'selected' if c.publisher_name == val else None}">${desc}</option>
</py:for> </py:for>
</select> </select>
<input class="btn button" type='submit' value="Update"/> <input class="btn button btn-primary" type='submit' value="Update"/>
</div> </div>
</form> </form>
   
  <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>
   
  <py:if test="c.top_packages">
  ${rickshaw_graph(c.graph_data,'dataset-downloads',debug=True)}
<table class="table table-condensed table-bordered table-striped"> <table class="table table-condensed table-bordered table-striped">
<tr> <tr>
<th>Dataset</th> <th>Dataset</th>
<th>Views</th> <th>Views</th>
<th>Visits</th> <th>Downloads</th>
</tr> </tr>
<py:for each="package, views, visits in c.top_packages"> <py:for each="package, views, visits,downloads in c.top_packages">
<tr> <tr>
<td>${h.link_to(package.title or package.name, h.url_for(controller='package', action='read', id=package.name))} <td>
</td> ${h.link_to(package.title or package.name, h.url_for(controller='package', action='read', id=package.name))}
<td>${views}</td> </td>
<td>${visits}</td> <td>${views}</td>
</tr> <td>${downloads}</td>
  </tr>
</py:for> </py:for>
</table> </table>
  </py:if>
   
   
</div> </div>
   
<xi:include href="../../layout.html" /> <xi:include href="../../layout.html" />
</html> </html>
   
   
   
   
  <html xmlns:py="http://genshi.edgewall.org/"
  xmlns:i18n="http://genshi.edgewall.org/i18n"
  xmlns:xi="http://www.w3.org/2001/XInclude"
  py:strip="">
 
  <xi:include href="../ga_util.html" />
 
  <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" />
 
  </py:match>
 
  <div py:match="content">
  <h1>Downloads</h1>
  ${usage_nav('Downloads')}
 
  <form class="form-inline" action="${h.url_for(controller='ckanext.ga_report.controller:GaReport',action='downloads')}" method="get">
  <div class="controls">
 
  ${month_selector(c.month, c.months, c.day)}
 
  <input class="btn button btn-primary" type='submit' value="Update"/>
  </div>
  </form>
 
  <py:if test="c.downloads">
  ${downloads_table(c.downloads)}
  </py:if>
  <py:if test="not c.downloads">
  <h4>No data</h4>
  <p>There is no download data available for this month</p>
  </py:if>
  </div>
 
  <xi:include href="../../layout.html" />
 
  <py:def function="optional_footer">
  <script type='text/javascript'>
  $('.dropdown-toggle').dropdown();
  $('.nav-tabs li a').click(function (e) {
  e.preventDefault();
  $(this).tab('show');
  })
  alert(window.location.hash);
  </script>
  </py:def>
  </html>
 
 
 
 
<html xmlns:py="http://genshi.edgewall.org/" <html xmlns:py="http://genshi.edgewall.org/"
xmlns:i18n="http://genshi.edgewall.org/i18n" xmlns:i18n="http://genshi.edgewall.org/i18n"
xmlns:xi="http://www.w3.org/2001/XInclude" xmlns:xi="http://www.w3.org/2001/XInclude"
py:strip=""> py:strip="">
   
<py:def function="page_title">Site analytics</py:def> <xi:include href="../ga_util.html" />
   
  <py:def function="page_title">Site usage</py:def>
   
  <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/vendor/jquery.sparkline.modified.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>
  </py:def>
   
<py:match path="primarysidebar"> <py:match path="primarysidebar">
<li class="widget-container boxed widget_text"> <li class="widget-container boxed widget_text">
<h4>Statistics</h4> <h4>Download</h4>
<p>It is possible to <a href="${h.url_for(controller='ckanext.ga_report.controller:GaReport',action='csv',month=c.month)}">export the analytics data</a> as a CSV file, which contains all of the information for ${c.month_desc}</p> <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>
  <xi:include href="../notes.html" />
   
</li>  
</py:match> </py:match>
   
<div py:match="content"> <div py:match="content">
<h1>Site statistics</h1> <h1>Site Usage</h1>
  ${usage_nav('Site-wide')}
   
<form class="form-inline" action="${h.url_for(controller='ckanext.ga_report.controller:GaReport',action='index')}" method="get"> <form class="form-inline" action="${h.url_for(controller='ckanext.ga_report.controller:GaReport',action='index')}" method="get">
<div class="controls"> <div class="controls">
<select name="month">  
<py:for each="val,desc in c.months"> ${month_selector(c.month, c.months, c.day)}
<option value='${val}' py:attrs="{'selected': 'selected' if c.month == val else None}">${desc}</option>  
</py:for> <input class="btn button btn-primary" type='submit' value="Update"/>
</select>  
<input class="btn button" type='submit' value="Update"/>  
</div> </div>
</form> </form>
   
<div class="tabbable"> <div class="tabbable">
<ul class="nav nav-tabs"> <ul class="nav nav-tabs">
<li class="active"><a href="#totals" data-toggle="tab">Totals</a></li> <li class="active"><a href="#totals" data-toggle="tab">Totals</a></li>
<li><a href="#browsers" data-toggle="tab">Browsers</a></li> <li class="dropdown">
<li><a href="#os" data-toggle="tab">Operating Systems</a></li> <a href="#browsers" class="dropdown-toggle" data-toggle="dropdown">Browsers
<li><a href="#social_networks" data-toggle="tab">Social Networks</a></li> <b class="caret"></b></a>
  <ul class="dropdown-menu">
  <li><a href="#browsers_names" data-toggle="tab">Browsers</a></li>
  <li><a href="#browsers_versions" data-toggle="tab">Versions</a></li>
  </ul>
  </li>
  <li class="dropdown">
  <a href="#browsers" class="dropdown-toggle" data-toggle="dropdown">Operating<br/>Systems
  <b class="caret"></b></a>
  <ul class="dropdown-menu">
  <li><a href="#os" data-toggle="tab">Operating Systems</a></li>
  <li><a href="#os_versions" data-toggle="tab">Versions</a></li>
  </ul>
  </li>
  <li class="dropdown">
  <a href="#browsers" class="dropdown-toggle" data-toggle="dropdown">Social Networks
  <b class="caret"></b></a>
  <ul class="dropdown-menu">
  <li><a href="#social_networks" data-toggle="tab">All networks</a></li>
  <li><a href="#social_referrals_totals" data-toggle="tab">Referral links</a></li>
  </ul>
  </li>
   
  <li><a href="#social_networks" data-toggle="tab"></a></li>
<li><a href="#languages" data-toggle="tab">Languages</a></li> <li><a href="#languages" data-toggle="tab">Languages</a></li>
<li><a href="#country" data-toggle="tab">Country</a></li> <li><a href="#country" data-toggle="tab">Country</a></li>
</ul> </ul>
<div class="tab-content"> <div class="tab-content">
<div class="tab-pane active" id="totals"> <div class="tab-pane active" id="totals">
<table class="table table-condensed table-bordered table-striped"> <table class="table table-condensed table-bordered table-striped">
<tr> <tr>
<th>Name</th> <th>Name</th>
<th>Value</th> <th>Value</th>
  <th>History</th>
</tr> </tr>
<py:for each="name, value in c.global_totals"> <py:for each="name, value, graph in c.global_totals">
<tr> <tr>
<td>${name}</td> <td>${name}</td>
<td>${value}</td> <td>${value}</td>
</tr> <td class="sparkline-cell">
</py:for> <span class="sparkline" sparkTooltips="${','.join([x for x,y in graph])}">
</table> ${','.join([y for x,y in graph])}
  </span>
<p>* Values are rounded up to 2 decimal places.</p> </td>
</div>  
<div class="tab-pane" id="browsers">  
<table class="table table-condensed table-bordered table-striped">  
<tr>  
<th>Name</th>  
<th>Value</th>  
</tr>  
<py:for each="name, value in c.browsers">  
<tr>  
<td>${name}</td>  
<td>${value}</td>  
</tr> </tr>
</py:for> </py:for>
</table> </table>
</div> </div>
<div class="tab-pane" id="os"> <div class="tab-pane" id="browsers_versions">
<table class="table table-condensed table-bordered table-striped"> ${rickshaw_graph(c.browser_versions_graph,'browser-versions')}
<tr> <p>Note: Where a browser has a large number of versions, these have been grouped together.</p>
<th>Name</th> ${stat_table(c.browser_versions)}
<th>Value</th> </div>
</tr> <div class="tab-pane" id="browsers_names">
<py:for each="name, value in c.os"> ${rickshaw_graph(c.browsers_graph,'browsers')}
<tr> ${stat_table(c.browsers)}
<td>${name}</td> </div>
<td>${value}</td> <div class="tab-pane" id="os">
</tr> ${rickshaw_graph(c.os_graph,'os')}
</py:for> ${stat_table(c.os)}
</table> </div>
  <div class="tab-pane" id="os_versions">
  ${rickshaw_graph(c.os_versions_graph,'os_versions')}
  ${stat_table(c.os_versions)}
  </div>
  <div class="tab-pane" id="social_referrals_totals">
  <p>Number of visits that were referred from social networks</p>
  ${social_table(c.social_referrer_totals)}
</div> </div>
<div class="tab-pane" id="social_networks"> <div class="tab-pane" id="social_networks">
<table class="table table-condensed table-bordered table-striped"> ${rickshaw_graph(c.social_networks_graph, 'social_networks')}
<tr> <p>Percentage of visits that were referred from these social networks</p>
<th>Name</th> ${stat_table(c.social_networks, 'Visits')}
<th>Value</th>  
</tr>  
<py:for each="name, value in c.social_networks">  
<tr>  
<td>${name}</td>  
<td>${value}</td>  
</tr>  
</py:for>  
</table>  
</div> </div>
<div class="tab-pane" id="languages"> <div class="tab-pane" id="languages">
<table class="table table-condensed table-bordered table-striped"> ${rickshaw_graph(c.languages_graph,'languages')}
<tr> ${stat_table(c.languages)}
<th>Name</th>  
<th>Value</th>  
</tr>  
<py:for each="name, value in c.languages">  
<tr>  
<td>${name}</td>  
<td>${value}</td>  
</tr>  
</py:for>  
</table>  
</div> </div>
<div class="tab-pane" id="country"> <div class="tab-pane" id="country">
<table class="table table-condensed table-bordered table-striped"> ${rickshaw_graph(c.country_graph,'country')}
<tr> ${stat_table(c.country)}
<th>Name</th>  
<th>Value</th>  
</tr>  
<py:for each="name, value in c.country">  
<tr>  
<td>${name}</td>  
<td>${value}</td>  
</tr>  
</py:for>  
</table>  
</div> </div>
  </div>
  </div>
  </div>
   
   
</div> <py:def function="optional_footer">
</div> <script type="text/javascript">
  $(function() {
  var sparkOptions = {
  enableTagOptions: true,
</div> type: 'line',
  width: 100,
  height: 26,
  chartRangeMin: 0,
  spotColor: '',
  maxSpotColor: '',
  minSpotColor: '',
  highlightSpotColor: '000000',
  lineColor: '3F8E6D',
  fillColor: 'B7E66B'
  };
  $('.sparkline').sparkline('html',sparkOptions);
  });
  </script>
  </py:def>
   
<xi:include href="../../layout.html" /> <xi:include href="../../layout.html" />
   
<py:def function="optional_footer">  
<script type='text/javascript'>  
$('.nav-tabs li a').click(function (e) {  
e.preventDefault();  
$(this).tab('show');  
})  
</script>  
</py:def>  
</html> </html>
   
   
   
   
  from nose.tools import assert_equal
 
  from ckanext.ga_report.download_analytics import DownloadAnalytics
 
  _filter_browser_version = DownloadAnalytics._filter_browser_version
 
  class TestBrowserVersionFilter:
  def test_chrome(self):
  assert_equal(_filter_browser_version('Chrome', u'6.0.472.0'), '6')
  def test_firefox(self):
  assert_equal(_filter_browser_version('Firefox', u'16.1'), '16')
  def test_safari(self):
  assert_equal(_filter_browser_version('Safari', u'534.55.3'), '53X')
  assert_equal(_filter_browser_version('Safari', u'1534.55.3'), '15XX')
  def test_ie(self):
  assert_equal(_filter_browser_version('Internet Explorer', u'8.0'), '8')
  def test_opera_mini(self):
  assert_equal(_filter_browser_version('Opera Mini', u'6.5.27431'), '6')
  def test_opera(self):
  assert_equal(_filter_browser_version('Opera', u'11.60'), '11')
 
  class TestDownloadAnalytics:
  def test_filter_out_long_tail(self):
  data = {'Firefox': 100,
  'Obscure Browser': 5,
  'Chrome': 150}
  DownloadAnalytics._filter_out_long_tail(data, 10)
  assert_equal(data, {'Firefox': 100,
  'Chrome': 150})
 
  from nose.tools import assert_equal
 
  from ckanext.ga_report.ga_model import _normalize_url
 
  class TestNormalizeUrl:
  def test_normal(self):
  assert_equal(_normalize_url('http://data.gov.uk/dataset/weekly_fuel_prices'),
  '/dataset/weekly_fuel_prices')
 
  def test_www_dot(self):
  assert_equal(_normalize_url('http://www.data.gov.uk/dataset/weekly_fuel_prices'),
  '/dataset/weekly_fuel_prices')
 
  def test_https(self):
  assert_equal(_normalize_url('https://data.gov.uk/dataset/weekly_fuel_prices'),
  '/dataset/weekly_fuel_prices')
 
 
file:a/setup.py -> file:b/setup.py
from setuptools import setup, find_packages from setuptools import setup, find_packages
import sys, os import sys, os
   
version = '0.1' version = '0.1'
   
setup( setup(
name='ckanext-ga-report', name='ckanext-ga-report',
version=version, version=version,
description="GA reporting for CKAN", description="GA reporting for CKAN",
long_description="""\ long_description="""\
""", """,
classifiers=[], # Get strings from http://pypi.python.org/pypi?%3Aaction=list_classifiers classifiers=[], # Get strings from http://pypi.python.org/pypi?%3Aaction=list_classifiers
keywords='', keywords='',
author='David Read', author='David Read',
author_email='david.read@hackneyworkshop.com', author_email='david.read@hackneyworkshop.com',
url='', url='',
license='', license='',
packages=find_packages(exclude=['ez_setup', 'examples', 'tests']), packages=find_packages(exclude=['ez_setup', 'examples', 'tests']),
namespace_packages=['ckanext', 'ckanext.ga_report'], namespace_packages=['ckanext', 'ckanext.ga_report'],
include_package_data=True, include_package_data=True,
zip_safe=False, zip_safe=False,
install_requires=[ install_requires=[
'gdata', 'gdata',
'google-api-python-client' 'google-api-python-client'
], ],
entry_points=\ entry_points=\
""" """
[ckan.plugins] [ckan.plugins]
# Add plugins here, eg # Add plugins here
ga-report=ckanext.ga_report.plugin:GAReportPlugin ga-report=ckanext.ga_report.plugin:GAReportPlugin
   
[paste.paster_command] [paste.paster_command]
loadanalytics = ckanext.ga_report.command:LoadAnalytics loadanalytics = ckanext.ga_report.command:LoadAnalytics
initdb = ckanext.ga_report.command:InitDB initdb = ckanext.ga_report.command:InitDB
getauthtoken = ckanext.ga_report.command:GetAuthToken getauthtoken = ckanext.ga_report.command:GetAuthToken
  fixtimeperiods = ckanext.ga_report.command:FixTimePeriods
""", """,
) )