diff --git a/.gitignore b/.gitignore
index ac451899a..a7b6ed7d1 100644
--- a/.gitignore
+++ b/.gitignore
@@ -8,3 +8,4 @@ secrets.json
.sass-cache/
.coverage
.tox
+djangoproject/static/js/lib/jquery-flot/examples
diff --git a/Makefile b/Makefile
index 4e0d10c83..195fd957c 100644
--- a/Makefile
+++ b/Makefile
@@ -1,4 +1,5 @@
STATIC = djangoproject/static
+JQUERY_FLOT=djangoproject/static/js/lib/jquery-flot
.PHONY: collectstatics compile-scss compile-scss-debug watch-scss run install test ci
@@ -21,7 +22,14 @@ install:
pip install -r requirements/dev.txt
test:
- @coverage run manage.py test -v2 aggregator contact docs fundraising legacy releases svntogit
+ @coverage run manage.py test -v2 aggregator contact docs fundraising legacy releases svntogit dashboard
ci: test
@coverage report
+
+$(JQUERY_FLOT)/jquery.flot.min.js: $(JQUERY_FLOT)
+ cat $(JQUERY_FLOT)/jquery.flot.js $(JQUERY_FLOT)/jquery.flot.time.js > $(JQUERY_FLOT)/jquery.flot.concat.js
+ yuicompressor $(JQUERY_FLOT)/jquery.flot.concat.js -o $(JQUERY_FLOT)/jquery.flot.min.js
+
+$(JQUERY_FLOT)/:
+ bower install
diff --git a/README.rst b/README.rst
index 40f37632b..e7953eebf 100644
--- a/README.rst
+++ b/README.rst
@@ -51,11 +51,16 @@ To run locally, do the usual:
./manage.py loaddata doc_releases
./manage.py update_docs
-#. Point the ``www.djangoproject.dev`` and ``docs.djangoproject.dev``
+#. For dashboard::
+
+ ./manage.py loaddata dashboard_example_data
+ ./manage.py update_metrics
+
+#. Point the ``www.djangoproject.dev``, ``docs.djangoproject.dev`` and ``dashboard.djangoproject.dev``
hostnames with your ``/etc/hosts`` file to ``localhost``/``127.0.0.1``.
Here's how it could look like::
- 127.0.0.1 docs.djangoproject.dev, www.djangoproject.dev
+ 127.0.0.1 docs.djangoproject.dev, www.djangoproject.dev, dashboard.djangoproject.dev
If you're on Mac OS and don't feel like editing the ``/etc/hosts`` file
manually, there is a great preference pane called `Hosts.prefpane`_. On
@@ -71,8 +76,9 @@ To run locally, do the usual:
make run
This runs both the main site ("www") as well as the
- docs site in the same process. Open http://www.djangoproject.dev:8000/
- or http://docs.djangoproject.dev:8000/.
+ docs and dashboard site in the same process.
+ Open http://www.djangoproject.dev:8000/, http://docs.djangoproject.dev:8000/
+ or http://dashboard.djangoproject.dev:8000/.
Running the tests
-----------------
diff --git a/bower.json b/bower.json
index be3e488a8..bfaf7e040 100644
--- a/bower.json
+++ b/bower.json
@@ -6,6 +6,7 @@
"jquery.inview": "1.0.0",
"webfontloader": "~1.5.10",
"jquery.payment": "~1.1.4",
- "modernizr-retina-test": "~1.0.0"
+ "modernizr-retina-test": "~1.0.0",
+ "jquery-flot": "~0.8.3"
}
}
diff --git a/dashboard/__init__.py b/dashboard/__init__.py
new file mode 100644
index 000000000..e69de29bb
diff --git a/dashboard/admin.py b/dashboard/admin.py
new file mode 100644
index 000000000..52ef89c79
--- /dev/null
+++ b/dashboard/admin.py
@@ -0,0 +1,27 @@
+from __future__ import absolute_import
+
+from django.contrib import admin
+from .models import Category, Metric, Datum
+
+
+@admin.register(Category)
+class CategoryAdmin(admin.ModelAdmin):
+ list_display = ('name', 'position')
+ list_editable = ('position',)
+ ordering = ('position',)
+
+
+class MetricAdmin(admin.ModelAdmin):
+ list_display = ('name', 'category', 'position', 'show_on_dashboard', 'show_sparkline', 'period')
+ list_editable = ('show_on_dashboard', 'category', 'position', 'show_sparkline', 'period')
+ ordering = ('category__position', 'position')
+ prepopulated_fields = {'slug': ['name']}
+
+
+for MC in Metric.__subclasses__():
+ admin.site.register(MC, MetricAdmin)
+
+
+@admin.register(Datum)
+class DatumAdmin(admin.ModelAdmin):
+ list_display = ('timestamp', 'metric', 'measurement')
diff --git a/dashboard/fixtures/dashboard_example_data.json.gz b/dashboard/fixtures/dashboard_example_data.json.gz
new file mode 100644
index 000000000..d56f9aabc
Binary files /dev/null and b/dashboard/fixtures/dashboard_example_data.json.gz differ
diff --git a/dashboard/fixtures/dashboard_test_data.json b/dashboard/fixtures/dashboard_test_data.json
new file mode 100644
index 000000000..43fa4a8e5
--- /dev/null
+++ b/dashboard/fixtures/dashboard_test_data.json
@@ -0,0 +1,246 @@
+[
+ {
+ "fields": {
+ "position": 1,
+ "name": "Activity"
+ },
+ "model": "dashboard.category",
+ "pk": 1
+ },
+ {
+ "fields": {
+ "position": 2,
+ "name": "Patches"
+ },
+ "model": "dashboard.category",
+ "pk": 2
+ },
+ {
+ "fields": {
+ "position": 3,
+ "name": "Tickets by triage stage"
+ },
+ "model": "dashboard.category",
+ "pk": 3
+ },
+ {
+ "fields": {
+ "position": 4,
+ "name": "Accepted tickets by type"
+ },
+ "model": "dashboard.category",
+ "pk": 4
+ },
+ {
+ "fields": {
+ "category": 3,
+ "show_on_dashboard": true,
+ "name": "Unreviewed tickets",
+ "period": "instant",
+ "show_sparkline": true,
+ "query": "status=!closed&stage=Unreviewed",
+ "unit_plural": "tickets",
+ "position": 1,
+ "slug": "unreviewed",
+ "unit": "ticket"
+ },
+ "model": "dashboard.tracticketmetric",
+ "pk": 1
+ },
+ {
+ "fields": {
+ "category": 2,
+ "show_on_dashboard": true,
+ "name": "Patches needing review",
+ "period": "instant",
+ "show_sparkline": true,
+ "query": "status=!closed&needs_better_patch=0&needs_tests=0&needs_docs=0&has_patch=1&stage=Accepted",
+ "unit_plural": "tickets",
+ "position": 1,
+ "slug": "patches",
+ "unit": "ticket"
+ },
+ "model": "dashboard.tracticketmetric",
+ "pk": 2
+ },
+ {
+ "fields": {
+ "category": 2,
+ "show_on_dashboard": true,
+ "name": "Doc. patches needing review",
+ "period": "instant",
+ "show_sparkline": true,
+ "query": "status=!closed&needs_better_patch=0&component=Documentation&needs_tests=0&needs_docs=0&has_patch=1&stage=Accepted",
+ "unit_plural": "tickets",
+ "position": 1,
+ "slug": "doc-patches",
+ "unit": "ticket"
+ },
+ "model": "dashboard.tracticketmetric",
+ "pk": 3
+ },
+ {
+ "fields": {
+ "category": 3,
+ "show_on_dashboard": true,
+ "name": "Tickets needing design decision",
+ "period": "instant",
+ "show_sparkline": true,
+ "query": "status=!closed&stage=Design decision needed",
+ "unit_plural": "tickets",
+ "position": 1,
+ "slug": "ddn",
+ "unit": "ticket"
+ },
+ "model": "dashboard.tracticketmetric",
+ "pk": 4
+ },
+ {
+ "fields": {
+ "category": 3,
+ "show_on_dashboard": true,
+ "name": "Tickets ready for commit",
+ "period": "instant",
+ "show_sparkline": true,
+ "query": "status=!closed&stage=Ready for checkin",
+ "unit_plural": "tickets",
+ "position": 1,
+ "slug": "rfc",
+ "unit": "ticket"
+ },
+ "model": "dashboard.tracticketmetric",
+ "pk": 5
+ },
+ {
+ "fields": {
+ "category": 1,
+ "show_on_dashboard": true,
+ "name": "Release blockers",
+ "period": "instant",
+ "show_sparkline": true,
+ "query": "status=!closed&severity=Release blocker",
+ "unit_plural": "tickets",
+ "position": 1,
+ "slug": "blockers",
+ "unit": "ticket"
+ },
+ "model": "dashboard.tracticketmetric",
+ "pk": 6
+ },
+ {
+ "fields": {
+ "category": 1,
+ "show_on_dashboard": true,
+ "name": "New tickets this week",
+ "period": "weekly",
+ "show_sparkline": true,
+ "query": "time=thisweek..",
+ "unit_plural": "tickets",
+ "position": 1,
+ "slug": "new-tickets-week",
+ "unit": "ticket"
+ },
+ "model": "dashboard.tracticketmetric",
+ "pk": 7
+ },
+ {
+ "fields": {
+ "category": 1,
+ "show_on_dashboard": true,
+ "name": "New tickets today",
+ "period": "daily",
+ "show_sparkline": true,
+ "query": "time=today..",
+ "unit_plural": "tickets",
+ "position": 1,
+ "slug": "new-tickets-today",
+ "unit": "ticket"
+ },
+ "model": "dashboard.tracticketmetric",
+ "pk": 8
+ },
+ {
+ "fields": {
+ "category": 2,
+ "show_on_dashboard": true,
+ "name": "\"Easy\" tickets",
+ "period": "instant",
+ "show_sparkline": true,
+ "query": "status=!closed&easy=1&stage=Accepted",
+ "unit_plural": "tickets",
+ "position": 1,
+ "slug": "easy-tickets",
+ "unit": "ticket"
+ },
+ "model": "dashboard.tracticketmetric",
+ "pk": 9
+ },
+ {
+ "fields": {
+ "category": 1,
+ "show_on_dashboard": true,
+ "name": "Commits today",
+ "feed_url": "http://code.djangoproject.com/timeline?changeset=on&max=0&daysback=0&format=rss",
+ "link_url": "http://code.djangoproject.com/timeline?changeset=on&max=0&daysback=0",
+ "period": "daily",
+ "show_sparkline": true,
+ "unit_plural": "commits",
+ "position": 1,
+ "slug": "commits-today",
+ "unit": "commit"
+ },
+ "model": "dashboard.rssfeedmetric",
+ "pk": 1
+ },
+ {
+ "fields": {
+ "category": 1,
+ "show_on_dashboard": true,
+ "name": "Commits in the last week",
+ "feed_url": "http://code.djangoproject.com/timeline?changeset=on&max=0&daysback=7&format=rss",
+ "link_url": "http://code.djangoproject.com/timeline?changeset=on&max=0&daysback=7",
+ "period": "weekly",
+ "show_sparkline": true,
+ "unit_plural": "commits",
+ "position": 1,
+ "slug": "commits-week",
+ "unit": "commit"
+ },
+ "model": "dashboard.rssfeedmetric",
+ "pk": 2
+ },
+ {
+ "fields": {
+ "category": 2,
+ "show_on_dashboard": true,
+ "name": "Pull Requests (Open)",
+ "link_url": "https://github.com/django/django/pulls",
+ "period": "instant",
+ "show_sparkline": true,
+ "unit_plural": "pull requests",
+ "position": 1,
+ "slug": "pull-requests-open",
+ "unit": "pull request",
+ "api_url": "https://api.github.com/repos/django/django/pulls?state=open"
+ },
+ "model": "dashboard.githubitemcountmetric",
+ "pk": 1
+ },
+ {
+ "fields": {
+ "category": 2,
+ "show_on_dashboard": true,
+ "name": "Pull Requests (Closed)",
+ "link_url": "https://github.com/django/django/pulls",
+ "period": "instant",
+ "show_sparkline": true,
+ "unit_plural": "pull requests",
+ "position": 1,
+ "slug": "pull-requests-closed",
+ "unit": "pull request",
+ "api_url": "https://api.github.com/repos/django/django/pulls?state=closed"
+ },
+ "model": "dashboard.githubitemcountmetric",
+ "pk": 2
+ }
+]
diff --git a/dashboard/fixtures/rss_feed_metric.xml b/dashboard/fixtures/rss_feed_metric.xml
new file mode 100644
index 000000000..66137844b
--- /dev/null
+++ b/dashboard/fixtures/rss_feed_metric.xml
@@ -0,0 +1,2626 @@
+
+
+
+ Django
+ https://code.djangoproject.com/timeline
+ Trac Timeline
+ en-US
+ Trac 1.0.2
+
+ Django
+ https://www.djangoproject.com/s/img/site/hdr_logo.gif
+ https://code.djangoproject.com/timeline
+
+ -
+
Changeset [c3336e7e]: Removed dumpdata --natural option and serializers use_natural_keys ...
+
+ Tim Graham <timograham@…>
+
+ Sun, 18 Jan 2015 01:18:34 GMT
+ https://code.djangoproject.com/changeset/c3336e7e4f146fc62272d462288a00f8d78c1f83
+ https://code.djangoproject.com/changeset/c3336e7e4f146fc62272d462288a00f8d78c1f83/1421543914000000
+ <p>
+Removed dumpdata --natural option and serializers use_natural_keys parameter.
+</p>
+<p>
+Per deprecation timeline; refs <a class="closed ticket" href="https://code.djangoproject.com/ticket/13252" title="New feature: Use the natural key instead of the primary key when serializing (closed: fixed)">#13252</a>.
+</p>
+
+ changeset
+ -
+
Changeset [1d975ff]: Removed usage of deprecated dumpdata options in docs.
+
+ Tim Graham <timograham@…>
+
+ Sun, 18 Jan 2015 01:14:45 GMT
+ https://code.djangoproject.com/changeset/1d975ff44bc23efaf0ebf3e96cc35539d80bd244
+ https://code.djangoproject.com/changeset/1d975ff44bc23efaf0ebf3e96cc35539d80bd244/1421543685000000
+ <p>
+Removed usage of deprecated dumpdata options in docs.
+</p>
+
+ changeset
+ -
+
Changeset [8d959f73]: Removed obsolete deprecation warning in runtests.py
+
+ Tim Graham <timograham@…>
+
+ Sun, 18 Jan 2015 00:41:46 GMT
+ https://code.djangoproject.com/changeset/8d959f73bed135c1253c0d957355b60fce4091e9
+ https://code.djangoproject.com/changeset/8d959f73bed135c1253c0d957355b60fce4091e9/1421541706000000
+ <p>
+Removed obsolete deprecation warning in runtests.py
+</p>
+
+ changeset
+ -
+
Changeset [40d6b37]: Removed BaseMemcachedCacheMethods._get_memcache_timeout backwards ...
+
+ Tim Graham <timograham@…>
+
+ Sun, 18 Jan 2015 00:32:54 GMT
+ https://code.djangoproject.com/changeset/40d6b376d4ac29c1f271bb827cd9ca24860ce4b5
+ https://code.djangoproject.com/changeset/40d6b376d4ac29c1f271bb827cd9ca24860ce4b5/1421541174000000
+ <p>
+Removed BaseMemcachedCacheMethods._get_memcache_timeout backwards compatibility shim.
+</p>
+<p>
+Per deprecation timeline; refs <a class="closed ticket" href="https://code.djangoproject.com/ticket/21147" title="Bug: cache.tests.FileBasedCacheTests.test_zero_timeout fails with sqlite on ... (closed: fixed)">#21147</a>.
+</p>
+
+ changeset
+ -
+
Changeset [eea66a6e]: [1.7.x] Added removal of check management command to deprecation ...
+
+ Tim Graham <timograham@…>
+
+ Sun, 18 Jan 2015 00:16:41 GMT
+ https://code.djangoproject.com/changeset/eea66a6e97f9c3392cf24b971c15a6a7ea634202
+ https://code.djangoproject.com/changeset/eea66a6e97f9c3392cf24b971c15a6a7ea634202/1421540201000000
+ <p>
+[1.7.x] Added removal of check management command to deprecation timeline.
+</p>
+<p>
+Backport of <a class="changeset" href="https://code.djangoproject.com/changeset/20e4e8fc79af9a87fb48951d03d3747f1f550551/" title="Added removal of check management command to deprecation timeline.">20e4e8fc79af9a87fb48951d03d3747f1f550551</a> from master
+</p>
+
+ changeset
+ -
+
Changeset [a41d410]: [1.8.x] Added removal of check management command to deprecation ...
+
+ Tim Graham <timograham@…>
+
+ Sun, 18 Jan 2015 00:15:59 GMT
+ https://code.djangoproject.com/changeset/a41d41046af60e585713b33ff2e6e039446ce389
+ https://code.djangoproject.com/changeset/a41d41046af60e585713b33ff2e6e039446ce389/1421540159000000
+ <p>
+[1.8.x] Added removal of check management command to deprecation timeline.
+</p>
+<p>
+Backport of <a class="changeset" href="https://code.djangoproject.com/changeset/20e4e8fc79af9a87fb48951d03d3747f1f550551/" title="Added removal of check management command to deprecation timeline.">20e4e8fc79af9a87fb48951d03d3747f1f550551</a> from master
+</p>
+
+ changeset
+ -
+
Changeset [20e4e8f]: Added removal of check management command to deprecation timeline.
+
+ Tim Graham <timograham@…>
+
+ Sun, 18 Jan 2015 00:14:44 GMT
+ https://code.djangoproject.com/changeset/20e4e8fc79af9a87fb48951d03d3747f1f550551
+ https://code.djangoproject.com/changeset/20e4e8fc79af9a87fb48951d03d3747f1f550551/1421540084000000
+ <p>
+Added removal of check management command to deprecation timeline.
+</p>
+
+ changeset
+ -
+
Changeset [0622bca5]: Removed the validate management command per deprecation timeline.
+
+ Tim Graham <timograham@…>
+
+ Sun, 18 Jan 2015 00:12:03 GMT
+ https://code.djangoproject.com/changeset/0622bca5d1b25877167b7beda96edcd3ba58db8d
+ https://code.djangoproject.com/changeset/0622bca5d1b25877167b7beda96edcd3ba58db8d/1421539923000000
+ <p>
+Removed the validate management command per deprecation timeline.
+</p>
+
+ changeset
+ -
+
Changeset [714277c]: Removed support for SplitDateTimeWidget with DateTimeField per ...
+
+ Tim Graham <timograham@…>
+
+ Sat, 17 Jan 2015 23:49:44 GMT
+ https://code.djangoproject.com/changeset/714277cb4cedd8290101f9c6b3e6382f192ae177
+ https://code.djangoproject.com/changeset/714277cb4cedd8290101f9c6b3e6382f192ae177/1421538584000000
+ <p>
+Removed support for SplitDateTimeWidget with DateTimeField per deprecation timeline.
+</p>
+<p>
+refs <a class="closed ticket" href="https://code.djangoproject.com/ticket/8898" title="Bug: `required` validation bypassed when using `DateTimeField` with ... (closed: fixed)">#8898</a>
+</p>
+
+ changeset
+ -
+
Changeset [ecbe20f]: [1.7.x] Added versionadded to ModelAdmin.get_formsets_with_inlines(); ...
+
+ Tim Graham <timograham@…>
+
+ Sat, 17 Jan 2015 23:18:06 GMT
+ https://code.djangoproject.com/changeset/ecbe20fe20ddc54aa034530b38a73195ee3f598d
+ https://code.djangoproject.com/changeset/ecbe20fe20ddc54aa034530b38a73195ee3f598d/1421536686000000
+ <p>
+[1.7.x] Added versionadded to ModelAdmin.get_formsets_with_inlines(); refs <a class="closed ticket" href="https://code.djangoproject.com/ticket/20702" title="Bug: Using ModelAdmin.get_formsets() to filter inlines is broken. (closed: fixed)">#20702</a>.
+</p>
+<p>
+Backport of <a class="changeset" href="https://code.djangoproject.com/changeset/7cfcdd98dcf0dbbde7cfd656e450c52342dbe6f3/" title="[1.8.x] Added versionadded to ModelAdmin.get_formsets_with_inlines(); ...">7cfcdd98dcf0dbbde7cfd656e450c52342dbe6f3</a> from stable/1.8.x
+</p>
+
+ changeset
+ -
+
Changeset [7cfcdd9]: [1.8.x] Added versionadded to ModelAdmin.get_formsets_with_inlines(); ...
+
+ Tim Graham <timograham@…>
+
+ Sat, 17 Jan 2015 23:12:47 GMT
+ https://code.djangoproject.com/changeset/7cfcdd98dcf0dbbde7cfd656e450c52342dbe6f3
+ https://code.djangoproject.com/changeset/7cfcdd98dcf0dbbde7cfd656e450c52342dbe6f3/1421536367000000
+ <p>
+[1.8.x] Added versionadded to ModelAdmin.get_formsets_with_inlines(); refs <a class="closed ticket" href="https://code.djangoproject.com/ticket/20702" title="Bug: Using ModelAdmin.get_formsets() to filter inlines is broken. (closed: fixed)">#20702</a>.
+</p>
+
+ changeset
+ -
+
Changeset [2c9e9563]: Removed ModelAdmin.get_formsets() per deprecation timeline; refs #20702.
+
+ Tim Graham <timograham@…>
+
+ Sat, 17 Jan 2015 23:02:35 GMT
+ https://code.djangoproject.com/changeset/2c9e95639e5a353f9fe1b81ecd3fdc5e2212781e
+ https://code.djangoproject.com/changeset/2c9e95639e5a353f9fe1b81ecd3fdc5e2212781e/1421535755000000
+ <p>
+Removed ModelAdmin.get_formsets() per deprecation timeline; refs <a class="closed ticket" href="https://code.djangoproject.com/ticket/20702" title="Bug: Using ModelAdmin.get_formsets() to filter inlines is broken. (closed: fixed)">#20702</a>.
+</p>
+
+ changeset
+ -
+
Changeset [3b570db]: Removed BaseDatabaseValidation.validate_field() per deprecation timeline.
+
+ Tim Graham <timograham@…>
+
+ Sat, 17 Jan 2015 19:58:09 GMT
+ https://code.djangoproject.com/changeset/3b570dbcdb605cc6ee7e8796e1533fdd40c92362
+ https://code.djangoproject.com/changeset/3b570dbcdb605cc6ee7e8796e1533fdd40c92362/1421524689000000
+ <p>
+Removed BaseDatabaseValidation.validate_field() per deprecation timeline.
+</p>
+
+ changeset
+ -
+
Changeset [e1b93db]: Removed deprecated aliases in django.db.models.
+
+ Tim Graham <timograham@…>
+
+ Sat, 17 Jan 2015 19:57:34 GMT
+ https://code.djangoproject.com/changeset/e1b93dbbef6afb86cf17be7f260f7b05ab7579e2
+ https://code.djangoproject.com/changeset/e1b93dbbef6afb86cf17be7f260f7b05ab7579e2/1421524654000000
+ <p>
+Removed deprecated aliases in django.db.models.
+</p>
+
+ changeset
+ -
+
Changeset [29737a2]: [1.7.x] Cleaned up migration writer tests Backport of ...
+
+ Markus Holtermann <info@…>
+
+ Sat, 17 Jan 2015 19:45:41 GMT
+ https://code.djangoproject.com/changeset/29737a2949e9b56df03dae3f84fccd8fe0443358
+ https://code.djangoproject.com/changeset/29737a2949e9b56df03dae3f84fccd8fe0443358/1421523941000000
+ <p>
+[1.7.x] Cleaned up migration writer tests
+</p>
+<p>
+Backport of <a class="changeset" href="https://code.djangoproject.com/changeset/65d55c409343aab7c2ae771c459720ef797b4cdb/" title="Cleaned up migration writer tests">65d55c409343aab7c2ae771c459720ef797b4cdb</a> from master
+</p>
+
+ changeset
+ -
+
Changeset [5512338]: [1.8.x] Cleaned up migration writer tests Backport of ...
+
+ Markus Holtermann <info@…>
+
+ Sat, 17 Jan 2015 19:42:58 GMT
+ https://code.djangoproject.com/changeset/5512338d4f2d4193cf4d9fa3dd707b149b8b9450
+ https://code.djangoproject.com/changeset/5512338d4f2d4193cf4d9fa3dd707b149b8b9450/1421523778000000
+ <p>
+[1.8.x] Cleaned up migration writer tests
+</p>
+<p>
+Backport of <a class="changeset" href="https://code.djangoproject.com/changeset/65d55c409343aab7c2ae771c459720ef797b4cdb/" title="Cleaned up migration writer tests">65d55c409343aab7c2ae771c459720ef797b4cdb</a> from master
+</p>
+
+ changeset
+ -
+
Changeset [2b4ca2e]: Removed unused imports.
+
+ Tim Graham <timograham@…>
+
+ Sat, 17 Jan 2015 19:42:51 GMT
+ https://code.djangoproject.com/changeset/2b4ca2ec295503472d488bb75548f0a576eb6aaf
+ https://code.djangoproject.com/changeset/2b4ca2ec295503472d488bb75548f0a576eb6aaf/1421523771000000
+ <p>
+Removed unused imports.
+</p>
+
+ changeset
+ -
+
Changeset [c2d5f290]: Removed contrib.flatpages.FlatPageSitemap per deprecation timeline; ...
+
+ Tim Graham <timograham@…>
+
+ Sat, 17 Jan 2015 19:40:22 GMT
+ https://code.djangoproject.com/changeset/c2d5f2903cfaef4f8b17d86f4db706b61073a471
+ https://code.djangoproject.com/changeset/c2d5f2903cfaef4f8b17d86f4db706b61073a471/1421523622000000
+ <p>
+Removed contrib.flatpages.FlatPageSitemap per deprecation timeline; refs <a class="closed ticket" href="https://code.djangoproject.com/ticket/23884" title="Cleanup/optimization: Consider moving FlatPageSitemap to contrib.flatpages (closed: fixed)">#23884</a>.
+</p>
+
+ changeset
+ -
+
Changeset [65d55c40]: Cleaned up migration writer tests
+
+ Markus Holtermann <info@…>
+
+ Sat, 17 Jan 2015 19:29:50 GMT
+ https://code.djangoproject.com/changeset/65d55c409343aab7c2ae771c459720ef797b4cdb
+ https://code.djangoproject.com/changeset/65d55c409343aab7c2ae771c459720ef797b4cdb/1421522990000000
+ <p>
+Cleaned up migration writer tests
+</p>
+
+ changeset
+ -
+
Changeset [2788c46]: Removed Multiple/ModelChoiceField cache_choices option; refs #22838.
+
+ Tim Graham <timograham@…>
+
+ Sat, 17 Jan 2015 18:48:46 GMT
+ https://code.djangoproject.com/changeset/2788c46d46bc4d7afb906765ce1f484faef180c5
+ https://code.djangoproject.com/changeset/2788c46d46bc4d7afb906765ce1f484faef180c5/1421520526000000
+ <p>
+Removed Multiple/ModelChoiceField cache_choices option; refs <a class="closed ticket" href="https://code.djangoproject.com/ticket/22838" title="Cleanup/optimization: Deprecate ModelChoiceField.cache_choices (closed: fixed)">#22838</a>.
+</p>
+
+ changeset
+ -
+
Changeset [4b8d3bb]: Standardized indentation in docs/howto/custom-management-commands.txt.
+
+ Tim Graham <timograham@…>
+
+ Sat, 17 Jan 2015 18:38:01 GMT
+ https://code.djangoproject.com/changeset/4b8d3bbab58ee99404fc84091fcca566552322ea
+ https://code.djangoproject.com/changeset/4b8d3bbab58ee99404fc84091fcca566552322ea/1421519881000000
+ <p>
+Standardized indentation in docs/howto/custom-management-commands.txt.
+</p>
+
+ changeset
+ -
+
Changeset [bd93032]: Removed ssi/url tags from future per deprecation timeline; refs #21939.
+
+ Tim Graham <timograham@…>
+
+ Sat, 17 Jan 2015 18:29:52 GMT
+ https://code.djangoproject.com/changeset/bd93032191f96bc7086aa46b91b4177601c6fce9
+ https://code.djangoproject.com/changeset/bd93032191f96bc7086aa46b91b4177601c6fce9/1421519392000000
+ <p>
+Removed ssi/url tags from future per deprecation timeline; refs <a class="closed ticket" href="https://code.djangoproject.com/ticket/21939" title="Cleanup/optimization: Deprecate loading ssi/url tags from future (closed: fixed)">#21939</a>.
+</p>
+
+ changeset
+ -
+
Changeset [a58a120]: [1.8.x] Standardized indentation in ...
+
+ Tim Graham <timograham@…>
+
+ Sat, 17 Jan 2015 18:27:59 GMT
+ https://code.djangoproject.com/changeset/a58a120021e4d63fbb176ecd88a7fefae54ff19c
+ https://code.djangoproject.com/changeset/a58a120021e4d63fbb176ecd88a7fefae54ff19c/1421519279000000
+ <p>
+[1.8.x] Standardized indentation in docs/howto/custom-management-commands.txt.
+</p>
+
+ changeset
+ -
+
Changeset [e278407]: Removed unused imports from previous commit.
+
+ Tim Graham <timograham@…>
+
+ Sat, 17 Jan 2015 18:22:52 GMT
+ https://code.djangoproject.com/changeset/e278407b20af0fd957fc3f39244a991c7520f2ba
+ https://code.djangoproject.com/changeset/e278407b20af0fd957fc3f39244a991c7520f2ba/1421518972000000
+ <p>
+Removed unused imports from previous commit.
+</p>
+
+ changeset
+ -
+
Changeset [4a03d348]: Removed BaseCommand.requires_model_validation per deprecation timeline.
+
+ Tim Graham <timograham@…>
+
+ Sat, 17 Jan 2015 17:59:07 GMT
+ https://code.djangoproject.com/changeset/4a03d348c70caa3e21393e08e6e665ef752202ac
+ https://code.djangoproject.com/changeset/4a03d348c70caa3e21393e08e6e665ef752202ac/1421517547000000
+ <p>
+Removed BaseCommand.requires_model_validation per deprecation timeline.
+</p>
+
+ changeset
+ -
+
Changeset [85c0eb1]: [1.7.x] Replaced deprecated requires_model_validation in docs. ...
+
+ Tim Graham <timograham@…>
+
+ Sat, 17 Jan 2015 17:52:34 GMT
+ https://code.djangoproject.com/changeset/85c0eb1e3baf09d79d463a20fedc3fbded3093f9
+ https://code.djangoproject.com/changeset/85c0eb1e3baf09d79d463a20fedc3fbded3093f9/1421517154000000
+ <p>
+[1.7.x] Replaced deprecated requires_model_validation in docs.
+</p>
+<p>
+Backport of <a class="changeset" href="https://code.djangoproject.com/changeset/18192b9fa4387d5e6c677a7929d91ce04f92cda7/" title="Replaced deprecated requires_model_validation in docs.">18192b9fa4387d5e6c677a7929d91ce04f92cda7</a> from master
+</p>
+
+ changeset
+ -
+
Changeset [bfa34788]: [1.8.x] Replaced deprecated requires_model_validation in docs. ...
+
+ Tim Graham <timograham@…>
+
+ Sat, 17 Jan 2015 17:52:30 GMT
+ https://code.djangoproject.com/changeset/bfa34788507505584a9885c6856c6a6083670879
+ https://code.djangoproject.com/changeset/bfa34788507505584a9885c6856c6a6083670879/1421517150000000
+ <p>
+[1.8.x] Replaced deprecated requires_model_validation in docs.
+</p>
+<p>
+Backport of <a class="changeset" href="https://code.djangoproject.com/changeset/18192b9fa4387d5e6c677a7929d91ce04f92cda7/" title="Replaced deprecated requires_model_validation in docs.">18192b9fa4387d5e6c677a7929d91ce04f92cda7</a> from master
+</p>
+
+ changeset
+ -
+
Changeset [18192b9f]: Replaced deprecated requires_model_validation in docs.
+
+ Tim Graham <timograham@…>
+
+ Sat, 17 Jan 2015 17:51:50 GMT
+ https://code.djangoproject.com/changeset/18192b9fa4387d5e6c677a7929d91ce04f92cda7
+ https://code.djangoproject.com/changeset/18192b9fa4387d5e6c677a7929d91ce04f92cda7/1421517110000000
+ <p>
+Replaced deprecated requires_model_validation in docs.
+</p>
+
+ changeset
+ -
+
Changeset [df3f3bb]: Removed utils.text.javascript_quote() per deprecation timeline; refs ...
+
+ Tim Graham <timograham@…>
+
+ Sat, 17 Jan 2015 17:41:49 GMT
+ https://code.djangoproject.com/changeset/df3f3bbe2927b9bad80088c6adbf5e8c5ba778c9
+ https://code.djangoproject.com/changeset/df3f3bbe2927b9bad80088c6adbf5e8c5ba778c9/1421516509000000
+ <p>
+Removed utils.text.javascript_quote() per deprecation timeline; refs <a class="closed ticket" href="https://code.djangoproject.com/ticket/21725" title="Bug: Javascript translations fail with non-BMP characters (closed: fixed)">#21725</a>.
+</p>
+
+ changeset
+ -
+
Changeset [00a1199]: Removed support for AppCommand.handle_app() per deprecation timeline.
+
+ Tim Graham <timograham@…>
+
+ Sat, 17 Jan 2015 17:34:34 GMT
+ https://code.djangoproject.com/changeset/00a11994a57312687b9e104d2de370b139a28358
+ https://code.djangoproject.com/changeset/00a11994a57312687b9e104d2de370b139a28358/1421516074000000
+ <p>
+Removed support for AppCommand.handle_app() per deprecation timeline.
+</p>
+
+ changeset
+ -
+
Changeset [f0a1df0b]: Removed deprecated Chinese language codes; refs #18149.
+
+ Tim Graham <timograham@…>
+
+ Sat, 17 Jan 2015 16:23:43 GMT
+ https://code.djangoproject.com/changeset/f0a1df0b0139a7f5a576dff966210b5d52247650
+ https://code.djangoproject.com/changeset/f0a1df0b0139a7f5a576dff966210b5d52247650/1421511823000000
+ <p>
+Removed deprecated Chinese language codes; refs <a class="closed ticket" href="https://code.djangoproject.com/ticket/18149" title="Bug: Form.prefix example gives wrong separator between prefix and field name (closed: fixed)">#18149</a>.
+</p>
+
+ changeset
+ -
+
Changeset [467fd7e]: [1.7.x] Updated tutorial 1 with actual migrate output.
+
+ Tim Graham <timograham@…>
+
+ Sat, 17 Jan 2015 15:54:34 GMT
+ https://code.djangoproject.com/changeset/467fd7ea1afe6d80735af73ece45eaa61d9ff3c5
+ https://code.djangoproject.com/changeset/467fd7ea1afe6d80735af73ece45eaa61d9ff3c5/1421510074000000
+ <p>
+[1.7.x] Updated tutorial 1 with actual migrate output.
+</p>
+
+ changeset
+ -
+
Changeset [9f86d86]: [1.8.x] Updated tutorial 1 with actual migrate output.
+
+ Tim Graham <timograham@…>
+
+ Sat, 17 Jan 2015 15:46:27 GMT
+ https://code.djangoproject.com/changeset/9f86d86c62e5e805ba0191ccd546e174301998ea
+ https://code.djangoproject.com/changeset/9f86d86c62e5e805ba0191ccd546e174301998ea/1421509587000000
+ <p>
+[1.8.x] Updated tutorial 1 with actual migrate output.
+</p>
+
+ changeset
+ -
+
Changeset [b845951]: Required sqlparse for SQL splitting per deprecation timeline.
+
+ Tim Graham <timograham@…>
+
+ Sat, 17 Jan 2015 15:20:30 GMT
+ https://code.djangoproject.com/changeset/b845951fd41cf6a380b3e09eeb30f7d105c37061
+ https://code.djangoproject.com/changeset/b845951fd41cf6a380b3e09eeb30f7d105c37061/1421508030000000
+ <p>
+Required sqlparse for SQL splitting per deprecation timeline.
+</p>
+
+ changeset
+ -
+
Changeset [4aa089a9]: Removed support for custom SQL per deprecation timeline.
+
+ Tim Graham <timograham@…>
+
+ Sat, 17 Jan 2015 15:16:06 GMT
+ https://code.djangoproject.com/changeset/4aa089a9a9504c4a833eee8161be013206da5d15
+ https://code.djangoproject.com/changeset/4aa089a9a9504c4a833eee8161be013206da5d15/1421507766000000
+ <p>
+Removed support for custom SQL per deprecation timeline.
+</p>
+
+ changeset
+ -
+
Changeset [a420f83]: Fixed #24055 -- Keep reference to view class for resolve()
+
+ Loic Bistuer <loic.bistuer@…>
+
+ Sat, 17 Jan 2015 15:09:10 GMT
+ https://code.djangoproject.com/changeset/a420f83e7d2e446ca01ef7c13d30c2ef3e975e5c
+ https://code.djangoproject.com/changeset/a420f83e7d2e446ca01ef7c13d30c2ef3e975e5c/1421507350000000
+ <p>
+Fixed <a class="closed ticket" href="https://code.djangoproject.com/ticket/24055" title="New feature: Keep reference to view class for resolve() (closed: fixed)">#24055</a> -- Keep reference to view class for resolve()
+</p>
+
+ changeset
+ -
+
Changeset [67235fd]: Removed support for initial_data fixtures per deprecation timeline.
+
+ Tim Graham <timograham@…>
+
+ Sat, 17 Jan 2015 14:59:25 GMT
+ https://code.djangoproject.com/changeset/67235fd4ef1b006fc9cdb2fa20e7bb93b0edff4b
+ https://code.djangoproject.com/changeset/67235fd4ef1b006fc9cdb2fa20e7bb93b0edff4b/1421506765000000
+ <p>
+Removed support for initial_data fixtures per deprecation timeline.
+</p>
+
+ changeset
+ -
+
Changeset [f635d759]: Removed support for old-style test database settings per deprecation ...
+
+ Tim Graham <timograham@…>
+
+ Sat, 17 Jan 2015 14:55:18 GMT
+ https://code.djangoproject.com/changeset/f635d759354842e46901ed1ae1be5f5a0b81e567
+ https://code.djangoproject.com/changeset/f635d759354842e46901ed1ae1be5f5a0b81e567/1421506518000000
+ <p>
+Removed support for old-style test database settings per deprecation timeline.
+</p>
+
+ changeset
+ -
+
Changeset [d038c54]: Removed django.core.cache.get_cache() per deprecation timeline; refs ...
+
+ Tim Graham <timograham@…>
+
+ Sat, 17 Jan 2015 14:55:18 GMT
+ https://code.djangoproject.com/changeset/d038c547b5ce585cbf9ef5bb7e5298f52e4a243b
+ https://code.djangoproject.com/changeset/d038c547b5ce585cbf9ef5bb7e5298f52e4a243b/1421506518000000
+ <p>
+Removed django.core.cache.get_cache() per deprecation timeline; refs <a class="closed ticket" href="https://code.djangoproject.com/ticket/21012" title="Cleanup/optimization: Provide shared "caches" dict to avoid creating multiple cache class ... (closed: fixed)">#21012</a>.
+</p>
+
+ changeset
+ -
+
Changeset [6b1b726]: [1.8.x] Fixed PostGIS crosses lookup and added crosses test Backport ...
+
+ Claude Paroz <claude@…>
+
+ Sat, 17 Jan 2015 14:46:50 GMT
+ https://code.djangoproject.com/changeset/6b1b7263f4b0065a8a514abfd7e74e17e742dbf6
+ https://code.djangoproject.com/changeset/6b1b7263f4b0065a8a514abfd7e74e17e742dbf6/1421506010000000
+ <p>
+[1.8.x] Fixed PostGIS crosses lookup and added crosses test
+</p>
+<p>
+Backport of aff0e54d5 from master.
+</p>
+
+ changeset
+ -
+
Changeset [aff0e54d]: Fixed PostGIS crosses lookup and added crosses test
+
+ Claude Paroz <claude@…>
+
+ Sat, 17 Jan 2015 14:44:49 GMT
+ https://code.djangoproject.com/changeset/aff0e54d511f55dbcdbae6a79c237fbb3edc9973
+ https://code.djangoproject.com/changeset/aff0e54d511f55dbcdbae6a79c237fbb3edc9973/1421505889000000
+ <p>
+Fixed PostGIS crosses lookup and added crosses test
+</p>
+
+ changeset
+ -
+
Changeset [1b0365ad]: Removed django.utils.tzinfo per deprecation timeline; refs #17262.
+
+ Tim Graham <timograham@…>
+
+ Sat, 17 Jan 2015 14:32:33 GMT
+ https://code.djangoproject.com/changeset/1b0365ad34151f266fe3faecb4edf31a51a8642c
+ https://code.djangoproject.com/changeset/1b0365ad34151f266fe3faecb4edf31a51a8642c/1421505153000000
+ <p>
+Removed django.utils.tzinfo per deprecation timeline; refs <a class="closed ticket" href="https://code.djangoproject.com/ticket/17262" title="Cleanup/optimization: Refactor the implementations of tzinfo classes (closed: fixed)">#17262</a>.
+</p>
+
+ changeset
+ -
+
Changeset [5f600db3]: [1.7.x] Documented django.utils.timezone.FixedOffset; thanks Aymeric. ...
+
+ Tim Graham <timograham@…>
+
+ Sat, 17 Jan 2015 14:31:59 GMT
+ https://code.djangoproject.com/changeset/5f600db37a61be24cfe75bb2f327bb7bb57a468c
+ https://code.djangoproject.com/changeset/5f600db37a61be24cfe75bb2f327bb7bb57a468c/1421505119000000
+ <p>
+[1.7.x] Documented django.utils.timezone.FixedOffset; thanks Aymeric.
+</p>
+<p>
+Backport of <a class="changeset" href="https://code.djangoproject.com/changeset/25264d4e2a4b3fd6a25e6b617388ea24f3d48d63/" title="Documented django.utils.timezone.FixedOffset; thanks Aymeric.">25264d4e2a4b3fd6a25e6b617388ea24f3d48d63</a> from master
+</p>
+
+ changeset
+ -
+
Changeset [b714316]: [1.8.x] Documented django.utils.timezone.FixedOffset; thanks Aymeric. ...
+
+ Tim Graham <timograham@…>
+
+ Sat, 17 Jan 2015 14:31:56 GMT
+ https://code.djangoproject.com/changeset/b714316c0606110cd508863b6d12650004366aaf
+ https://code.djangoproject.com/changeset/b714316c0606110cd508863b6d12650004366aaf/1421505116000000
+ <p>
+[1.8.x] Documented django.utils.timezone.FixedOffset; thanks Aymeric.
+</p>
+<p>
+Backport of <a class="changeset" href="https://code.djangoproject.com/changeset/25264d4e2a4b3fd6a25e6b617388ea24f3d48d63/" title="Documented django.utils.timezone.FixedOffset; thanks Aymeric.">25264d4e2a4b3fd6a25e6b617388ea24f3d48d63</a> from master
+</p>
+
+ changeset
+ -
+
Changeset [25264d4e]: Documented django.utils.timezone.FixedOffset; thanks Aymeric.
+
+ Tim Graham <timograham@…>
+
+ Sat, 17 Jan 2015 14:30:52 GMT
+ https://code.djangoproject.com/changeset/25264d4e2a4b3fd6a25e6b617388ea24f3d48d63
+ https://code.djangoproject.com/changeset/25264d4e2a4b3fd6a25e6b617388ea24f3d48d63/1421505052000000
+ <p>
+Documented django.utils.timezone.FixedOffset; thanks Aymeric.
+</p>
+
+ changeset
+ -
+
Changeset [f6463bb3]: Removed the syncdb command per deprecation timeline.
+
+ Tim Graham <timograham@…>
+
+ Sat, 17 Jan 2015 14:20:12 GMT
+ https://code.djangoproject.com/changeset/f6463bb38047bac7a62826ca901e3c5add40cb10
+ https://code.djangoproject.com/changeset/f6463bb38047bac7a62826ca901e3c5add40cb10/1421504412000000
+ <p>
+Removed the syncdb command per deprecation timeline.
+</p>
+
+ changeset
+ -
+
Changeset [f4f24d3]: Removed pre_syncdb and post_syncdb signals per deprecation timeline.
+
+ Tim Graham <timograham@…>
+
+ Sat, 17 Jan 2015 14:07:00 GMT
+ https://code.djangoproject.com/changeset/f4f24d30e044b5bc2014b8356058099808c22c1a
+ https://code.djangoproject.com/changeset/f4f24d30e044b5bc2014b8356058099808c22c1a/1421503620000000
+ <p>
+Removed pre_syncdb and post_syncdb signals per deprecation timeline.
+</p>
+
+ changeset
+ -
+
Changeset [fed25f11]: Removed compatibility with Python 3.2.
+
+ Tim Graham <timograham@…>
+
+ Sat, 17 Jan 2015 14:00:17 GMT
+ https://code.djangoproject.com/changeset/fed25f1105ff0d5fe46cd217b29371ee7f1907f2
+ https://code.djangoproject.com/changeset/fed25f1105ff0d5fe46cd217b29371ee7f1907f2/1421503217000000
+ <p>
+Removed compatibility with Python 3.2.
+</p>
+
+ changeset
+ -
+
Changeset [4e65f195]: Removed django.db.models.loading per deprecation timeline.
+
+ Tim Graham <timograham@…>
+
+ Sat, 17 Jan 2015 13:40:59 GMT
+ https://code.djangoproject.com/changeset/4e65f195e1b10d83bb7edc38c908747c4fd537b8
+ https://code.djangoproject.com/changeset/4e65f195e1b10d83bb7edc38c908747c4fd537b8/1421502059000000
+ <p>
+Removed django.db.models.loading per deprecation timeline.
+</p>
+
+ changeset
+ -
+
Changeset [d4ee6cd]: Removed ModelAdmin.declared_fieldsets per deprecation timeline; refs ...
+
+ Tim Graham <timograham@…>
+
+ Sat, 17 Jan 2015 13:40:33 GMT
+ https://code.djangoproject.com/changeset/d4ee6cda5802adc5a38d266ccebe78fb67066179
+ https://code.djangoproject.com/changeset/d4ee6cda5802adc5a38d266ccebe78fb67066179/1421502033000000
+ <p>
+Removed ModelAdmin.declared_fieldsets per deprecation timeline; refs <a class="closed ticket" href="https://code.djangoproject.com/ticket/18681" title="Bug: get_fieldsets not hooked in properly (closed: fixed)">#18681</a>.
+</p>
+
+ changeset
+ -
+
Changeset [c820892]: Removed django.utils.datastructures.SortedDict per deprecation timeline.
+
+ Tim Graham <timograham@…>
+
+ Sat, 17 Jan 2015 13:40:23 GMT
+ https://code.djangoproject.com/changeset/c820892eed9ea10879270e64e8109dc44829756a
+ https://code.djangoproject.com/changeset/c820892eed9ea10879270e64e8109dc44829756a/1421502023000000
+ <p>
+Removed django.utils.datastructures.<a class="wiki" href="https://code.djangoproject.com/wiki/SortedDict">SortedDict</a> per deprecation timeline.
+</p>
+
+ changeset
+ -
+
Changeset [41f0d3d3]: Removed FastCGI support per deprecation timeline; refs #20766.
+
+ Tim Graham <timograham@…>
+
+ Sat, 17 Jan 2015 13:32:31 GMT
+ https://code.djangoproject.com/changeset/41f0d3d3bc8b0a6831530e1176c6415f9ba45b0b
+ https://code.djangoproject.com/changeset/41f0d3d3bc8b0a6831530e1176c6415f9ba45b0b/1421501551000000
+ <p>
+Removed FastCGI support per deprecation timeline; refs <a class="closed ticket" href="https://code.djangoproject.com/ticket/20766" title="Cleanup/optimization: Deprecate fastcgi support (closed: fixed)">#20766</a>.
+</p>
+
+ changeset
+ -
+
Changeset [37b7776a]: Removed django.utils.datastructures.MergeDict per deprecation ...
+
+ Tim Graham <timograham@…>
+
+ Sat, 17 Jan 2015 13:13:36 GMT
+ https://code.djangoproject.com/changeset/37b7776a015102e97b9bdd64d88c732883ff9989
+ https://code.djangoproject.com/changeset/37b7776a015102e97b9bdd64d88c732883ff9989/1421500416000000
+ <p>
+Removed django.utils.datastructures.MergeDict per deprecation timeline; refs <a class="closed ticket" href="https://code.djangoproject.com/ticket/18659" title="Cleanup/optimization: Remove `request.REQUEST` (closed: fixed)">#18659</a>.
+</p>
+
+ changeset
+ -
+
Changeset [75f107b8]: Removed request.REQUEST per deprecation timeline; refs #18659.
+
+ Tim Graham <timograham@…>
+
+ Sat, 17 Jan 2015 13:05:18 GMT
+ https://code.djangoproject.com/changeset/75f107b8842dfc890ddd65262bd09ca87c3a15be
+ https://code.djangoproject.com/changeset/75f107b8842dfc890ddd65262bd09ca87c3a15be/1421499918000000
+ <p>
+Removed request.REQUEST per deprecation timeline; refs <a class="closed ticket" href="https://code.djangoproject.com/ticket/18659" title="Cleanup/optimization: Remove `request.REQUEST` (closed: fixed)">#18659</a>.
+</p>
+
+ changeset
+ -
+
Changeset [61ad1ea]: Removed django.utils.functional.memoize per deprecation timeline. ...
+
+ Tim Graham <timograham@…>
+
+ Sat, 17 Jan 2015 12:55:32 GMT
+ https://code.djangoproject.com/changeset/61ad1ea92b1f4df992b0ef1dcc7c781da2413815
+ https://code.djangoproject.com/changeset/61ad1ea92b1f4df992b0ef1dcc7c781da2413815/1421499332000000
+ <p>
+Removed django.utils.functional.memoize per deprecation timeline.
+</p>
+<p>
+refs <a class="closed ticket" href="https://code.djangoproject.com/ticket/21351" title="Cleanup/optimization: memoize function needs tests and improvements (closed: fixed)">#21351</a>.
+</p>
+
+ changeset
+ -
+
Changeset [9ce36512]: Removed backwards compatibility shims for "util" modules per ...
+
+ Tim Graham <timograham@…>
+
+ Sat, 17 Jan 2015 12:40:12 GMT
+ https://code.djangoproject.com/changeset/9ce36512fa925231b0496cc10f7d114c069c7c06
+ https://code.djangoproject.com/changeset/9ce36512fa925231b0496cc10f7d114c069c7c06/1421498412000000
+ <p>
+Removed backwards compatibility shims for "util" modules per deprecation timeline.
+</p>
+<p>
+refs <a class="closed ticket" href="https://code.djangoproject.com/ticket/17627" title="Cleanup/optimization: contrib.admin.util is misnamed (closed: fixed)">#17627</a>.
+</p>
+
+ changeset
+ -
+
Changeset [d79a30b]: Removed fallback suport for allow_syncdb() in database routers per ...
+
+ Tim Graham <timograham@…>
+
+ Sat, 17 Jan 2015 12:39:46 GMT
+ https://code.djangoproject.com/changeset/d79a30ba3f991bc3150fa1260fab1c10f5d3450a
+ https://code.djangoproject.com/changeset/d79a30ba3f991bc3150fa1260fab1c10f5d3450a/1421498386000000
+ <p>
+Removed fallback suport for allow_syncdb() in database routers per deprecation timeline.
+</p>
+
+ changeset
+ -
+
Changeset [b952c3fc]: Removed django.utils.unittest per deprecation timeline.
+
+ Tim Graham <timograham@…>
+
+ Sat, 17 Jan 2015 12:32:22 GMT
+ https://code.djangoproject.com/changeset/b952c3fc606ed01c6d0ff26451b6e2acf1b91ca5
+ https://code.djangoproject.com/changeset/b952c3fc606ed01c6d0ff26451b6e2acf1b91ca5/1421497942000000
+ <p>
+Removed django.utils.unittest per deprecation timeline.
+</p>
+
+ changeset
+ -
+
Changeset [ce78b954]: Removed django.utils.importlib per deprecation timeline.
+
+ Tim Graham <timograham@…>
+
+ Sat, 17 Jan 2015 12:14:21 GMT
+ https://code.djangoproject.com/changeset/ce78b954cf2747add06dadd6aef8bbd70d70c667
+ https://code.djangoproject.com/changeset/ce78b954cf2747add06dadd6aef8bbd70d70c667/1421496861000000
+ <p>
+Removed django.utils.importlib per deprecation timeline.
+</p>
+
+ changeset
+ -
+
Changeset [1c8b637e]: Removed django.utils.dictconfig per deprecation timeline.
+
+ Tim Graham <timograham@…>
+
+ Sat, 17 Jan 2015 12:13:51 GMT
+ https://code.djangoproject.com/changeset/1c8b637ed97927c90a0f0056aa7b30bfe361c16b
+ https://code.djangoproject.com/changeset/1c8b637ed97927c90a0f0056aa7b30bfe361c16b/1421496831000000
+ <p>
+Removed django.utils.dictconfig per deprecation timeline.
+</p>
+
+ changeset
+ -
+
Changeset [428c8840]: Used features in GIS sitemap view conditionals
+
+ Claude Paroz <claude@…>
+
+ Sat, 17 Jan 2015 11:41:18 GMT
+ https://code.djangoproject.com/changeset/428c884083d8e60025ba1d2fa97de438c57c9d48
+ https://code.djangoproject.com/changeset/428c884083d8e60025ba1d2fa97de438c57c9d48/1421494878000000
+ <p>
+Used features in GIS sitemap view conditionals
+</p>
+
+ changeset
+ -
+
Changeset [53e1423]: Updated en translation catalogs Forward port of 666c12e52 from ...
+
+ Claude Paroz <claude@…>
+
+ Sat, 17 Jan 2015 10:19:37 GMT
+ https://code.djangoproject.com/changeset/53e1423eda369acb1882d6939a7f9e11617e5591
+ https://code.djangoproject.com/changeset/53e1423eda369acb1882d6939a7f9e11617e5591/1421489977000000
+ <p>
+Updated en translation catalogs
+</p>
+<p>
+Forward port of 666c12e52 from stable/1.8.x
+</p>
+
+ changeset
+ -
+
Changeset [666c12e5]: [1.8.x] Updated en translation catalogs
+
+ Claude Paroz <claude@…>
+
+ Sat, 17 Jan 2015 10:18:45 GMT
+ https://code.djangoproject.com/changeset/666c12e5295d021c8d791d4410e5753d4a2cc98e
+ https://code.djangoproject.com/changeset/666c12e5295d021c8d791d4410e5753d4a2cc98e/1421489925000000
+ <p>
+[1.8.x] Updated en translation catalogs
+</p>
+
+ changeset
+ -
+
Changeset [dec5157a]: [1.8.x] Complemented test about non-supported aggregation exception ...
+
+ Claude Paroz <claude@…>
+
+ Sat, 17 Jan 2015 09:04:38 GMT
+ https://code.djangoproject.com/changeset/dec5157a7256b91534eaa64afb91c6e569ce17e3
+ https://code.djangoproject.com/changeset/dec5157a7256b91534eaa64afb91c6e569ce17e3/1421485478000000
+ <p>
+[1.8.x] Complemented test about non-supported aggregation exception
+</p>
+<p>
+Backport of d69ecf922dd from master.
+</p>
+
+ changeset
+ -
+
Changeset [d69ecf9]: Complemented test about non-supported aggregation exception
+
+ Claude Paroz <claude@…>
+
+ Sat, 17 Jan 2015 09:01:55 GMT
+ https://code.djangoproject.com/changeset/d69ecf922ddf6d4e3698e0dfd42d9ae387df182c
+ https://code.djangoproject.com/changeset/d69ecf922ddf6d4e3698e0dfd42d9ae387df182c/1421485315000000
+ <p>
+Complemented test about non-supported aggregation exception
+</p>
+
+ changeset
+ -
+
Changeset [eb6a07e0]: [1.8.x] Fixed typo in 'Django Template Language' Backport of ...
+
+ Markus Holtermann <info@…>
+
+ Sat, 17 Jan 2015 02:06:26 GMT
+ https://code.djangoproject.com/changeset/eb6a07e0697cf4dbaf33915e60fce56d7705ec22
+ https://code.djangoproject.com/changeset/eb6a07e0697cf4dbaf33915e60fce56d7705ec22/1421460386000000
+ <p>
+[1.8.x] Fixed typo in 'Django Template Language'
+</p>
+<p>
+Backport of <a class="changeset" href="https://code.djangoproject.com/changeset/d60b96d98881b47c845125e82269ea6a9b268fbb/" title="Fixed typo in 'Django Template Language'">d60b96d98881b47c845125e82269ea6a9b268fbb</a> from master
+</p>
+
+ changeset
+ -
+
Changeset [d60b96d]: Fixed typo in 'Django Template Language'
+
+ Markus Holtermann <info@…>
+
+ Sat, 17 Jan 2015 02:05:28 GMT
+ https://code.djangoproject.com/changeset/d60b96d98881b47c845125e82269ea6a9b268fbb
+ https://code.djangoproject.com/changeset/d60b96d98881b47c845125e82269ea6a9b268fbb/1421460328000000
+ <p>
+Fixed typo in 'Django Template Language'
+</p>
+
+ changeset
+ -
+
Changeset [c5125888]: Increased the default PBKDF2 iterations.
+
+ Tim Graham <timograham@…>
+
+ Sat, 17 Jan 2015 00:27:10 GMT
+ https://code.djangoproject.com/changeset/c51258882bbf388f5c4cfc379340097ebe9beda9
+ https://code.djangoproject.com/changeset/c51258882bbf388f5c4cfc379340097ebe9beda9/1421454430000000
+ <p>
+Increased the default PBKDF2 iterations.
+</p>
+
+ changeset
+ -
+
Changeset [8be1b8b4]: [1.8.x] Bumped django_next_version in docs config.
+
+ Tim Graham <timograham@…>
+
+ Fri, 16 Jan 2015 23:30:49 GMT
+ https://code.djangoproject.com/changeset/8be1b8b488760959410be93e460944970b8199d6
+ https://code.djangoproject.com/changeset/8be1b8b488760959410be93e460944970b8199d6/1421451049000000
+ <p>
+[1.8.x] Bumped django_next_version in docs config.
+</p>
+
+ changeset
+ -
+
Changeset [d468903]: Bumped version to 1.9 in docs config.
+
+ Tim Graham <timograham@…>
+
+ Fri, 16 Jan 2015 23:30:28 GMT
+ https://code.djangoproject.com/changeset/d4689034becf3bc519dbddd22f702406c5cbe5b1
+ https://code.djangoproject.com/changeset/d4689034becf3bc519dbddd22f702406c5cbe5b1/1421451028000000
+ <p>
+Bumped version to 1.9 in docs config.
+</p>
+
+ changeset
+ -
+
Changeset [cba3d8a]: [1.6.x] Fixed a typo in the test responses docs. Backport of ...
+
+ Tim Graham <timograham@…>
+
+ Fri, 16 Jan 2015 23:24:51 GMT
+ https://code.djangoproject.com/changeset/cba3d8ab076f3a34c23ca51ea9c31e9e7688e833
+ https://code.djangoproject.com/changeset/cba3d8ab076f3a34c23ca51ea9c31e9e7688e833/1421450691000000
+ <p>
+[1.6.x] Fixed a typo in the test responses docs.
+</p>
+<p>
+Backport of <a class="changeset" href="https://code.djangoproject.com/changeset/996292d6498d25c6b3e84435e82edeff5aaa0257/" title="Fixed a typo in the test responses docs.">996292d6498d25c6b3e84435e82edeff5aaa0257</a> from master
+</p>
+
+ changeset
+ -
+
Changeset [be0bc9a9]: [1.7.x] Fixed a typo in the test responses docs. Backport of ...
+
+ Tim Graham <timograham@…>
+
+ Fri, 16 Jan 2015 23:24:48 GMT
+ https://code.djangoproject.com/changeset/be0bc9a9e7ad4421cea4aeebf274278e256d1a62
+ https://code.djangoproject.com/changeset/be0bc9a9e7ad4421cea4aeebf274278e256d1a62/1421450688000000
+ <p>
+[1.7.x] Fixed a typo in the test responses docs.
+</p>
+<p>
+Backport of <a class="changeset" href="https://code.djangoproject.com/changeset/996292d6498d25c6b3e84435e82edeff5aaa0257/" title="Fixed a typo in the test responses docs.">996292d6498d25c6b3e84435e82edeff5aaa0257</a> from master
+</p>
+
+ changeset
+ -
+
Changeset [801287b]: [1.8.x] Fixed a typo in the test responses docs. Backport of ...
+
+ Tim Graham <timograham@…>
+
+ Fri, 16 Jan 2015 23:24:44 GMT
+ https://code.djangoproject.com/changeset/801287bff262e3beb52d4b4fd21ec529416e079e
+ https://code.djangoproject.com/changeset/801287bff262e3beb52d4b4fd21ec529416e079e/1421450684000000
+ <p>
+[1.8.x] Fixed a typo in the test responses docs.
+</p>
+<p>
+Backport of <a class="changeset" href="https://code.djangoproject.com/changeset/996292d6498d25c6b3e84435e82edeff5aaa0257/" title="Fixed a typo in the test responses docs.">996292d6498d25c6b3e84435e82edeff5aaa0257</a> from master
+</p>
+
+ changeset
+ -
+
Changeset [d9edd2f]: Fixed #21108 -- Updated how to release docs: prereleases now go to PyPI.
+
+ Tim Graham <timograham@…>
+
+ Fri, 16 Jan 2015 23:24:10 GMT
+ https://code.djangoproject.com/changeset/d9edd2f68f6edaa5e634a7d93b29fe97cb7312fe
+ https://code.djangoproject.com/changeset/d9edd2f68f6edaa5e634a7d93b29fe97cb7312fe/1421450650000000
+ <p>
+Fixed <a class="closed ticket" href="https://code.djangoproject.com/ticket/21108" title="New feature: pip install --pre Django==1.xb not found (closed: fixed)">#21108</a> -- Updated how to release docs: prereleases now go to PyPI.
+</p>
+
+ changeset
+ -
+
Changeset [996292d6]: Fixed a typo in the test responses docs.
+
+ Simon Charette <charette.s@…>
+
+ Fri, 16 Jan 2015 23:17:00 GMT
+ https://code.djangoproject.com/changeset/996292d6498d25c6b3e84435e82edeff5aaa0257
+ https://code.djangoproject.com/changeset/996292d6498d25c6b3e84435e82edeff5aaa0257/1421450220000000
+ <p>
+Fixed a typo in the test responses docs.
+</p>
+
+ changeset
+ -
+
Changeset [3fe3bdd]: Added stub release notes for Django 1.9.
+
+ Tim Graham <timograham@…>
+
+ Fri, 16 Jan 2015 23:00:45 GMT
+ https://code.djangoproject.com/changeset/3fe3bddc2895e4bd1f968a962da36748160f4dae
+ https://code.djangoproject.com/changeset/3fe3bddc2895e4bd1f968a962da36748160f4dae/1421449245000000
+ <p>
+Added stub release notes for Django 1.9.
+</p>
+
+ changeset
+ -
+
Changeset [c72448b5]: Bumped version to 1.8 alpha 1.
+
+ Tim Graham <timograham@…>
+
+ Fri, 16 Jan 2015 22:06:32 GMT
+ https://code.djangoproject.com/changeset/c72448b59725c619f4f9d6e38264484c12c4c3b9
+ https://code.djangoproject.com/changeset/c72448b59725c619f4f9d6e38264484c12c4c3b9/1421445992000000
+ <p>
+Bumped version to 1.8 alpha 1.
+</p>
+
+ changeset
+ -
+
Changeset [3f23f1c]: Bumped version; master is now 1.9 pre-alpha.
+
+ Tim Graham <timograham@…>
+
+ Fri, 16 Jan 2015 22:04:35 GMT
+ https://code.djangoproject.com/changeset/3f23f1cfb472e77bb1f9260a71b40f895438658d
+ https://code.djangoproject.com/changeset/3f23f1cfb472e77bb1f9260a71b40f895438658d/1421445875000000
+ <p>
+Bumped version; master is now 1.9 pre-alpha.
+</p>
+
+ changeset
+ -
+
Changeset [39d95fb]: Fixed #24092 -- Widened base field support for ArrayField. Several ...
+
+ Tim Graham <timograham@…>
+
+ Fri, 16 Jan 2015 21:15:16 GMT
+ https://code.djangoproject.com/changeset/39d95fb6ada99c59d47fa0eae6d3128abafe2d58
+ https://code.djangoproject.com/changeset/39d95fb6ada99c59d47fa0eae6d3128abafe2d58/1421442916000000
+ <p>
+Fixed <a class="closed ticket" href="https://code.djangoproject.com/ticket/24092" title="Bug: New ArrayField from django.contrib.postgres.fields does not correctly ... (closed: fixed)">#24092</a> -- Widened base field support for ArrayField.
+</p>
+<p>
+Several issues resolved here, following from a report that a base_field
+of GenericIpAddressField was failing.
+</p>
+<p>
+We were using get_prep_value instead of get_db_prep_value in ArrayField
+which was bypassing any extra modifications to the value being made in
+the base field's get_db_prep_value. Changing this broke datetime
+support, so the postgres backend has gained the relevant operation
+methods to send dates/times/datetimes directly to the db backend instead
+of casting them to strings. Similarly, a new database feature has been
+added allowing the uuid to be passed directly to the backend, as we do
+with timedeltas.
+</p>
+<p>
+On the other side, psycopg2 expects an Inet() instance for IP address
+fields, so we add a value_to_db_ipaddress method to wrap the strings on
+postgres. We also have to manually add a database adapter to psycopg2,
+as we do not wish to use the built in adapter which would turn
+everything into Inet() instances.
+</p>
+<p>
+Thanks to smclenithan for the report.
+</p>
+
+ changeset
+ -
+
Changeset [a17724b7]: Fixed the length of a headline in the 1.8 release notes. This broke ...
+
+ Jannis Leidel <jannis@…>
+
+ Fri, 16 Jan 2015 20:29:28 GMT
+ https://code.djangoproject.com/changeset/a17724b791275578334bcdc66b3a8113eb86605e
+ https://code.djangoproject.com/changeset/a17724b791275578334bcdc66b3a8113eb86605e/1421440168000000
+ <p>
+Fixed the length of a headline in the 1.8 release notes.
+</p>
+<p>
+This broke the website design in the sidebar because the line could not be wrapped.
+</p>
+
+ changeset
+ -
+
Changeset [e8171da]: Fixed #24146 -- Fixed a missing fields regression in admin checks. ...
+
+ Tim Graham <timograham@…>
+
+ Fri, 16 Jan 2015 19:47:09 GMT
+ https://code.djangoproject.com/changeset/e8171daf0cd7f0e070395cb4c850c17fea32f11d
+ https://code.djangoproject.com/changeset/e8171daf0cd7f0e070395cb4c850c17fea32f11d/1421437629000000
+ <p>
+Fixed <a class="closed ticket" href="https://code.djangoproject.com/ticket/24146" title="Bug: (admin.E116) The value of 'list_filter[0]' refers to 'through__field', ... (closed: fixed)">#24146</a> -- Fixed a missing fields regression in admin checks.
+</p>
+<p>
+This allows using get_field() early in the app loading process.
+</p>
+<p>
+Thanks to PirosB3 and Tim Graham.
+</p>
+
+ changeset
+ -
+
Changeset [8e8daf7c]: Removed empty sections in 1.8 minor features.
+
+ Tim Graham <timograham@…>
+
+ Fri, 16 Jan 2015 19:41:05 GMT
+ https://code.djangoproject.com/changeset/8e8daf7c9b0f951ed53c322eada0e61e55a57a9f
+ https://code.djangoproject.com/changeset/8e8daf7c9b0f951ed53c322eada0e61e55a57a9f/1421437265000000
+ <p>
+Removed empty sections in 1.8 minor features.
+</p>
+
+ changeset
+ -
+
Changeset [b4ac232]: Fixed #24099 -- Removed contenttype.name deprecated field This ...
+
+ Markus Holtermann <info@…>
+
+ Fri, 16 Jan 2015 19:21:34 GMT
+ https://code.djangoproject.com/changeset/b4ac23290772e0c11379eb2dfb81c750b7052b66
+ https://code.djangoproject.com/changeset/b4ac23290772e0c11379eb2dfb81c750b7052b66/1421436094000000
+ <p>
+Fixed <a class="closed ticket" href="https://code.djangoproject.com/ticket/24099" title="Cleanup/optimization: Remove ContentType.name (closed: fixed)">#24099</a> -- Removed contenttype.name deprecated field
+</p>
+<p>
+This finsishes the work started on <a class="closed ticket" href="https://code.djangoproject.com/ticket/16803" title="Bug: Unicode representation of ContentType instances is not translated (closed: fixed)">#16803</a>.
+Thanks Simon Charette, Tim Graham and Collin Anderson for the
+reviews.
+</p>
+
+ changeset
+ -
+
Changeset [374c2419]: Tested that geo aggregates support slicing Refs #15101. Patch ...
+
+ Claude Paroz <claude@…>
+
+ Fri, 16 Jan 2015 19:10:25 GMT
+ https://code.djangoproject.com/changeset/374c2419e5adef53a643bf69c4753a6bf0c78a98
+ https://code.djangoproject.com/changeset/374c2419e5adef53a643bf69c4753a6bf0c78a98/1421435425000000
+ <p>
+Tested that geo aggregates support slicing
+</p>
+<p>
+Refs <a class="closed ticket" href="https://code.djangoproject.com/ticket/15101" title="Bug: GeoQuerySet extent() method fails to limit (closed: fixed)">#15101</a>. Patch slightly reworked by Claude Paroz.
+</p>
+
+ changeset
+ -
+
Changeset [a79e6b67]: Fixed #24152 -- Deprecated GeoQuerySet aggregate methods Thanks Josh ...
+
+ Claude Paroz <claude@…>
+
+ Fri, 16 Jan 2015 18:53:02 GMT
+ https://code.djangoproject.com/changeset/a79e6b67175f532049967268c10609af6d31d140
+ https://code.djangoproject.com/changeset/a79e6b67175f532049967268c10609af6d31d140/1421434382000000
+ <p>
+Fixed <a class="closed ticket" href="https://code.djangoproject.com/ticket/24152" title="Cleanup/optimization: Deprecate GeoQuerySet aggregate methods (closed: fixed)">#24152</a> -- Deprecated GeoQuerySet aggregate methods
+</p>
+<p>
+Thanks Josh Smeaton and Tim Graham for the reviews.
+</p>
+
+ changeset
+ -
+
Changeset [cef3f805]: [1.7.x] Fixed #24160 -- Fixed model_regress test on Windows; refs ...
+
+ Tim Graham <timograham@…>
+
+ Fri, 16 Jan 2015 17:03:15 GMT
+ https://code.djangoproject.com/changeset/cef3f805c21c029d017e6704565f9f2f5746a1e5
+ https://code.djangoproject.com/changeset/cef3f805c21c029d017e6704565f9f2f5746a1e5/1421427795000000
+ <p>
+[1.7.x] Fixed <a class="closed ticket" href="https://code.djangoproject.com/ticket/24160" title="Bug: Fatal Python error: Failed to initialize Windows random API (CryptoGen) (closed: fixed)">#24160</a> -- Fixed model_regress test on Windows; refs <a class="closed ticket" href="https://code.djangoproject.com/ticket/24007" title="Bug: Unable to unpickle models from an external script (closed: fixed)">#24007</a>.
+</p>
+<p>
+Backport of <a class="changeset" href="https://code.djangoproject.com/changeset/5338ff4808c822a8b00e90154b884b7be3011e60/" title="Fixed #24160 -- Fixed model_regress test on Windows; refs #24007.">5338ff4808c822a8b00e90154b884b7be3011e60</a> from master
+</p>
+
+ changeset
+ -
+
Changeset [5338ff4]: Fixed #24160 -- Fixed model_regress test on Windows; refs #24007.
+
+ Tim Graham <timograham@…>
+
+ Fri, 16 Jan 2015 16:59:08 GMT
+ https://code.djangoproject.com/changeset/5338ff4808c822a8b00e90154b884b7be3011e60
+ https://code.djangoproject.com/changeset/5338ff4808c822a8b00e90154b884b7be3011e60/1421427548000000
+ <p>
+Fixed <a class="closed ticket" href="https://code.djangoproject.com/ticket/24160" title="Bug: Fatal Python error: Failed to initialize Windows random API (CryptoGen) (closed: fixed)">#24160</a> -- Fixed model_regress test on Windows; refs <a class="closed ticket" href="https://code.djangoproject.com/ticket/24007" title="Bug: Unable to unpickle models from an external script (closed: fixed)">#24007</a>.
+</p>
+
+ changeset
+ -
+
Changeset [fb614ff4]: [1.6.x] Fixed #23312 -- Marked an i18n test as expectedFailure on ...
+
+ Tim Graham <timograham@…>
+
+ Fri, 16 Jan 2015 15:34:46 GMT
+ https://code.djangoproject.com/changeset/fb614ff4a712cf7d221ed9ddeb7e4164e882ba81
+ https://code.djangoproject.com/changeset/fb614ff4a712cf7d221ed9ddeb7e4164e882ba81/1421422486000000
+ <p>
+[1.6.x] Fixed <a class="closed ticket" href="https://code.djangoproject.com/ticket/23312" title="Bug: Windows/Python 3 test failure: test_unicode_decode_error (closed: fixed)">#23312</a> -- Marked an i18n test as expectedFailure on Windows/Python 3.
+</p>
+<p>
+Backport of <a class="changeset" href="https://code.djangoproject.com/changeset/433e7dd5076e492290a90130c9b3e2b1319b2f95/" title="[1.7.x] Fixed #23312 -- Marked an i18n test as expectedFailure on ...">433e7dd5076e492290a90130c9b3e2b1319b2f95</a> from stable/1.7.x
+</p>
+
+ changeset
+ -
+
Changeset [433e7dd]: [1.7.x] Fixed #23312 -- Marked an i18n test as expectedFailure on ...
+
+ Tim Graham <timograham@…>
+
+ Fri, 16 Jan 2015 15:31:49 GMT
+ https://code.djangoproject.com/changeset/433e7dd5076e492290a90130c9b3e2b1319b2f95
+ https://code.djangoproject.com/changeset/433e7dd5076e492290a90130c9b3e2b1319b2f95/1421422309000000
+ <p>
+[1.7.x] Fixed <a class="closed ticket" href="https://code.djangoproject.com/ticket/23312" title="Bug: Windows/Python 3 test failure: test_unicode_decode_error (closed: fixed)">#23312</a> -- Marked an i18n test as expectedFailure on Windows/Python 3.
+</p>
+
+ changeset
+ -
+
Changeset [bd08cfca]: [1.7.x] Fixed #24143 -- Encouraged use of Http404 messages for ...
+
+ Tim Graham <timograham@…>
+
+ Fri, 16 Jan 2015 14:42:03 GMT
+ https://code.djangoproject.com/changeset/bd08cfca6ff04e7cec940f5b59e97cdcceddcc69
+ https://code.djangoproject.com/changeset/bd08cfca6ff04e7cec940f5b59e97cdcceddcc69/1421419323000000
+ <p>
+[1.7.x] Fixed <a class="closed ticket" href="https://code.djangoproject.com/ticket/24143" title="Cleanup/optimization: Documentation uses uninstantiated Http404s (closed: fixed)">#24143</a> -- Encouraged use of Http404 messages for debugging.
+</p>
+<p>
+Backport of <a class="changeset" href="https://code.djangoproject.com/changeset/726a9550db5129badc1c44809b0bed728fa1ad90/" title="Fixed #24143 -- Encouraged use of Http404 messages for debugging.">726a9550db5129badc1c44809b0bed728fa1ad90</a> from master
+</p>
+
+ changeset
+ -
+
Changeset [726a9550]: Fixed #24143 -- Encouraged use of Http404 messages for debugging.
+
+ Tim Graham <timograham@…>
+
+ Fri, 16 Jan 2015 14:41:01 GMT
+ https://code.djangoproject.com/changeset/726a9550db5129badc1c44809b0bed728fa1ad90
+ https://code.djangoproject.com/changeset/726a9550db5129badc1c44809b0bed728fa1ad90/1421419261000000
+ <p>
+Fixed <a class="closed ticket" href="https://code.djangoproject.com/ticket/24143" title="Cleanup/optimization: Documentation uses uninstantiated Http404s (closed: fixed)">#24143</a> -- Encouraged use of Http404 messages for debugging.
+</p>
+
+ changeset
+ -
+
Changeset [a34fba5]: Simplified a bit GeoAggregate classes Thanks Josh Smeaton for the ...
+
+ Claude Paroz <claude@…>
+
+ Fri, 16 Jan 2015 09:40:45 GMT
+ https://code.djangoproject.com/changeset/a34fba5e596a3ec95bf284fd77b1609e71a65019
+ https://code.djangoproject.com/changeset/a34fba5e596a3ec95bf284fd77b1609e71a65019/1421401245000000
+ <p>
+Simplified a bit GeoAggregate classes
+</p>
+<p>
+Thanks Josh Smeaton for the review. Refs <a class="closed ticket" href="https://code.djangoproject.com/ticket/24152" title="Cleanup/optimization: Deprecate GeoQuerySet aggregate methods (closed: fixed)">#24152</a>.
+</p>
+
+ changeset
+ -
+
Changeset [065b2a82]: [1.7.x] Fixed #24135 -- Made RenameModel rename many-to-many tables. ...
+
+ Tim Graham <timograham@…>
+
+ Fri, 16 Jan 2015 01:43:49 GMT
+ https://code.djangoproject.com/changeset/065b2a82f6d7539032e15308351fa5eee95c0cb9
+ https://code.djangoproject.com/changeset/065b2a82f6d7539032e15308351fa5eee95c0cb9/1421372629000000
+ <p>
+[1.7.x] Fixed <a class="closed ticket" href="https://code.djangoproject.com/ticket/24135" title="Bug: migrations.RenameModel does not rename m2m tables (closed: fixed)">#24135</a> -- Made RenameModel rename many-to-many tables.
+</p>
+<p>
+Thanks Simon and Markus for reviews.
+</p>
+<p>
+Backport of <a class="changeset" href="https://code.djangoproject.com/changeset/28db4af80a319485c0da724d692e2f8396aa57e3/" title="Fixed #24135 -- Made RenameModel rename many-to-many tables.
+Thanks ...">28db4af80a319485c0da724d692e2f8396aa57e3</a> from master
+</p>
+
+ changeset
+ -
+
Changeset [28db4af]: Fixed #24135 -- Made RenameModel rename many-to-many tables. Thanks ...
+
+ Tim Graham <timograham@…>
+
+ Fri, 16 Jan 2015 01:34:33 GMT
+ https://code.djangoproject.com/changeset/28db4af80a319485c0da724d692e2f8396aa57e3
+ https://code.djangoproject.com/changeset/28db4af80a319485c0da724d692e2f8396aa57e3/1421372073000000
+ <p>
+Fixed <a class="closed ticket" href="https://code.djangoproject.com/ticket/24135" title="Bug: migrations.RenameModel does not rename m2m tables (closed: fixed)">#24135</a> -- Made RenameModel rename many-to-many tables.
+</p>
+<p>
+Thanks Simon and Markus for reviews.
+</p>
+
+ changeset
+ -
+
Changeset [3f9ec12d]: Fixed #23712 -- Fixed KeyError with BaseForm._html_output()
+
+ Tim Graham <timograham@…>
+
+ Fri, 16 Jan 2015 01:19:53 GMT
+ https://code.djangoproject.com/changeset/3f9ec12d9c9eff9a3b1a205d87c7e66587cf9967
+ https://code.djangoproject.com/changeset/3f9ec12d9c9eff9a3b1a205d87c7e66587cf9967/1421371193000000
+ <p>
+Fixed <a class="closed ticket" href="https://code.djangoproject.com/ticket/23712" title="Bug: BaseForm._html_output() uses inconsistent formatting for normal row (closed: fixed)">#23712</a> -- Fixed KeyError with BaseForm._html_output()
+</p>
+
+ changeset
+ -
+
Changeset [faf0d66a]: Fixed #23850 -- Fixed a migrations test failure on Mac OS X & Python 3
+
+ Tim Graham <timograham@…>
+
+ Fri, 16 Jan 2015 00:45:43 GMT
+ https://code.djangoproject.com/changeset/faf0d66a80e09be3656a337c33a8e70c7fbab7e3
+ https://code.djangoproject.com/changeset/faf0d66a80e09be3656a337c33a8e70c7fbab7e3/1421369143000000
+ <p>
+Fixed <a class="closed ticket" href="https://code.djangoproject.com/ticket/23850" title="Bug: Test failure in migrations tests on OS X (closed: fixed)">#23850</a> -- Fixed a migrations test failure on Mac OS X & Python 3
+</p>
+
+ changeset
+ -
+
Changeset [39b58ad]: Fixed #24148 -- Documented a bug with case expressions in SQLite < 3.7.0
+
+ Tim Graham <timograham@…>
+
+ Fri, 16 Jan 2015 00:42:05 GMT
+ https://code.djangoproject.com/changeset/39b58ad95ade8109de0f0ae3930e333b7f689c0f
+ https://code.djangoproject.com/changeset/39b58ad95ade8109de0f0ae3930e333b7f689c0f/1421368925000000
+ <p>
+Fixed <a class="closed ticket" href="https://code.djangoproject.com/ticket/24148" title="Bug: test_combined_expression test failure on Windows/Python 2 (closed: fixed)">#24148</a> -- Documented a bug with case expressions in SQLite < 3.7.0
+</p>
+
+ changeset
+ -
+
Changeset [b419bd3]: [1.7.x] Refs #24075 -- Silenced needless call_command output while ...
+
+ Markus Holtermann <info@…>
+
+ Thu, 15 Jan 2015 20:13:27 GMT
+ https://code.djangoproject.com/changeset/b419bd38431b83eec93376cd911e2b17eb8e7342
+ https://code.djangoproject.com/changeset/b419bd38431b83eec93376cd911e2b17eb8e7342/1421352807000000
+ <p>
+[1.7.x] Refs <a class="closed ticket" href="https://code.djangoproject.com/ticket/24075" title="Bug: Can't migrate contenttypes and auth to zero (closed: fixed)">#24075</a> -- Silenced needless call_command output while running tests
+</p>
+<p>
+Thanks Tim Graham for the report
+</p>
+<p>
+Backport of <a class="changeset" href="https://code.djangoproject.com/changeset/51dc617b21e67636d96cf645905797a4d6ff4bf0/" title="Refs #24075 -- Silenced needless call_command output while running ...">51dc617b21e67636d96cf645905797a4d6ff4bf0</a> from master
+</p>
+
+ changeset
+ -
+
Changeset [51dc617b]: Refs #24075 -- Silenced needless call_command output while running ...
+
+ Markus Holtermann <info@…>
+
+ Thu, 15 Jan 2015 20:07:39 GMT
+ https://code.djangoproject.com/changeset/51dc617b21e67636d96cf645905797a4d6ff4bf0
+ https://code.djangoproject.com/changeset/51dc617b21e67636d96cf645905797a4d6ff4bf0/1421352459000000
+ <p>
+Refs <a class="closed ticket" href="https://code.djangoproject.com/ticket/24075" title="Bug: Can't migrate contenttypes and auth to zero (closed: fixed)">#24075</a> -- Silenced needless call_command output while running tests
+</p>
+<p>
+Thanks Tim Graham for the report
+</p>
+
+ changeset
+ -
+
Changeset [67dbc56]: Made an expressions_case test work without Pillow.
+
+ Tim Graham <timograham@…>
+
+ Thu, 15 Jan 2015 19:02:58 GMT
+ https://code.djangoproject.com/changeset/67dbc56ec8657e02247c97ec06f7cde8d4808f0d
+ https://code.djangoproject.com/changeset/67dbc56ec8657e02247c97ec06f7cde8d4808f0d/1421348578000000
+ <p>
+Made an expressions_case test work without Pillow.
+</p>
+
+ changeset
+ -
+
Changeset [47bdad4e]: Replaced inner functions by class methods. refs #24031 Thanks to Tim ...
+
+ Simon Charette <charette.s@…>
+
+ Thu, 15 Jan 2015 18:03:34 GMT
+ https://code.djangoproject.com/changeset/47bdad4e6b3bf70aec0d19d65eeb6a7319ad045a
+ https://code.djangoproject.com/changeset/47bdad4e6b3bf70aec0d19d65eeb6a7319ad045a/1421345014000000
+ <p>
+Replaced inner functions by class methods.
+</p>
+<p>
+refs <a class="closed ticket" href="https://code.djangoproject.com/ticket/24031" title="New feature: Implement a Case/When expression (closed: fixed)">#24031</a>
+</p>
+<p>
+Thanks to Tim Graham and Michał Modzelewski for the review.
+</p>
+
+ changeset
+ -
+
Changeset [57671dd0]: [1.5.x] Direct readers to format_html() in mark_safe() docs. Backport ...
+
+ Luke Plant <L.Plant.98@…>
+
+ Thu, 15 Jan 2015 08:32:32 GMT
+ https://code.djangoproject.com/changeset/57671dd0b91ed4def9c7a49c486944f4e98fed9d
+ https://code.djangoproject.com/changeset/57671dd0b91ed4def9c7a49c486944f4e98fed9d/1421310752000000
+ <p>
+[1.5.x] Direct readers to format_html() in mark_safe() docs.
+</p>
+<p>
+Backport of <a class="changeset" href="https://code.djangoproject.com/changeset/4832c004e88a68b98b976b4f68a1c9fdb4ea3530/" title="Direct readers to format_html() in mark_safe() docs.">4832c004e88a68b98b976b4f68a1c9fdb4ea3530</a> from master
+</p>
+
+ changeset
+ -
+
Changeset [b9101fa7]: [1.6.x] Direct readers to format_html() in mark_safe() docs. Backport ...
+
+ Luke Plant <L.Plant.98@…>
+
+ Thu, 15 Jan 2015 08:30:38 GMT
+ https://code.djangoproject.com/changeset/b9101fa7a2180f121fc828174149b61e9151e69d
+ https://code.djangoproject.com/changeset/b9101fa7a2180f121fc828174149b61e9151e69d/1421310638000000
+ <p>
+[1.6.x] Direct readers to format_html() in mark_safe() docs.
+</p>
+<p>
+Backport of <a class="changeset" href="https://code.djangoproject.com/changeset/4832c004e88a68b98b976b4f68a1c9fdb4ea3530/" title="Direct readers to format_html() in mark_safe() docs.">4832c004e88a68b98b976b4f68a1c9fdb4ea3530</a> from master
+</p>
+
+ changeset
+ -
+
Changeset [327703c]: [1.7.x] Direct readers to format_html() in mark_safe() docs. Backport ...
+
+ Luke Plant <L.Plant.98@…>
+
+ Thu, 15 Jan 2015 08:29:00 GMT
+ https://code.djangoproject.com/changeset/327703c0672b705f264d2611aacbe2ce9795749b
+ https://code.djangoproject.com/changeset/327703c0672b705f264d2611aacbe2ce9795749b/1421310540000000
+ <p>
+[1.7.x] Direct readers to format_html() in mark_safe() docs.
+</p>
+<p>
+Backport of <a class="changeset" href="https://code.djangoproject.com/changeset/4832c004e88a68b98b976b4f68a1c9fdb4ea3530/" title="Direct readers to format_html() in mark_safe() docs.">4832c004e88a68b98b976b4f68a1c9fdb4ea3530</a> from master
+</p>
+
+ changeset
+ -
+
Changeset [4832c004]: Direct readers to format_html() in mark_safe() docs.
+
+ Luke Plant <L.Plant.98@…>
+
+ Thu, 15 Jan 2015 08:24:54 GMT
+ https://code.djangoproject.com/changeset/4832c004e88a68b98b976b4f68a1c9fdb4ea3530
+ https://code.djangoproject.com/changeset/4832c004e88a68b98b976b4f68a1c9fdb4ea3530/1421310294000000
+ <p>
+Direct readers to format_html() in mark_safe() docs.
+</p>
+
+ changeset
+ -
+
Changeset [67bcae1]: Moved check_aggregate_support to BaseSpatialOperations
+
+ Claude Paroz <claude@…>
+
+ Wed, 14 Jan 2015 21:03:41 GMT
+ https://code.djangoproject.com/changeset/67bcae1e5890c77d789d10cfe8644b5ab00ffb81
+ https://code.djangoproject.com/changeset/67bcae1e5890c77d789d10cfe8644b5ab00ffb81/1421269421000000
+ <p>
+Moved check_aggregate_support to BaseSpatialOperations
+</p>
+
+ changeset
+ -
+
Changeset [9801d41]: Skipped a problematic file_storage test on Windows.
+
+ Tim Graham <timograham@…>
+
+ Wed, 14 Jan 2015 20:00:27 GMT
+ https://code.djangoproject.com/changeset/9801d419b9e3d3c716cb857e4337f403d1c50706
+ https://code.djangoproject.com/changeset/9801d419b9e3d3c716cb857e4337f403d1c50706/1421265627000000
+ <p>
+Skipped a problematic file_storage test on Windows.
+</p>
+
+ changeset
+ -
+
Changeset [2d5da57f]: Fixed incorrect error message in Options.get_fields()
+
+ Tim Graham <timograham@…>
+
+ Wed, 14 Jan 2015 19:51:05 GMT
+ https://code.djangoproject.com/changeset/2d5da57f4856656677d82d4a56f5914f3cc431d8
+ https://code.djangoproject.com/changeset/2d5da57f4856656677d82d4a56f5914f3cc431d8/1421265065000000
+ <p>
+Fixed incorrect error message in Options.get_fields()
+</p>
+
+ changeset
+ -
+
Changeset [478546f]: [1.7.x] Fixed #24075 -- Prevented running post_migrate signals when ...
+
+ Markus Holtermann <info@…>
+
+ Wed, 14 Jan 2015 19:37:56 GMT
+ https://code.djangoproject.com/changeset/478546fcef38d95866a92bc44d10e15b26c7254c
+ https://code.djangoproject.com/changeset/478546fcef38d95866a92bc44d10e15b26c7254c/1421264276000000
+ <p>
+[1.7.x] Fixed <a class="closed ticket" href="https://code.djangoproject.com/ticket/24075" title="Bug: Can't migrate contenttypes and auth to zero (closed: fixed)">#24075</a> -- Prevented running post_migrate signals when unapplying initial migrations of contenttypes and auth
+</p>
+<p>
+Thanks Florian Apolloner for the report and Claude Paroz and Tim Graham for the review and help on the patch.
+</p>
+<p>
+Backport of <a class="changeset" href="https://code.djangoproject.com/changeset/737d24923ac69bb8b89af1bb2f3f4c4c744349e8/" title="Fixed #24075 -- Prevented running post_migrate signals when unapplying ...">737d24923ac69bb8b89af1bb2f3f4c4c744349e8</a> from master.
+</p>
+
+ changeset
+ -
+
Changeset [28308078]: Fixed #22603 -- Reorganized classes in django.db.backends.
+
+ Tim Graham <timograham@…>
+
+ Wed, 14 Jan 2015 19:16:20 GMT
+ https://code.djangoproject.com/changeset/28308078f397d1de36fd0da417ac7da2544ba12d
+ https://code.djangoproject.com/changeset/28308078f397d1de36fd0da417ac7da2544ba12d/1421262980000000
+ <p>
+Fixed <a class="closed ticket" href="https://code.djangoproject.com/ticket/22603" title="Cleanup/optimization: Reorganize code in django.db.backends (closed: fixed)">#22603</a> -- Reorganized classes in django.db.backends.
+</p>
+
+ changeset
+ -
+
Changeset [737d249]: Fixed #24075 -- Prevented running post_migrate signals when unapplying ...
+
+ Markus Holtermann <info@…>
+
+ Wed, 14 Jan 2015 18:59:39 GMT
+ https://code.djangoproject.com/changeset/737d24923ac69bb8b89af1bb2f3f4c4c744349e8
+ https://code.djangoproject.com/changeset/737d24923ac69bb8b89af1bb2f3f4c4c744349e8/1421261979000000
+ <p>
+Fixed <a class="closed ticket" href="https://code.djangoproject.com/ticket/24075" title="Bug: Can't migrate contenttypes and auth to zero (closed: fixed)">#24075</a> -- Prevented running post_migrate signals when unapplying initial migrations of contenttypes and auth
+</p>
+<p>
+Thanks Florian Apolloner for the report and Claude Paroz and Tim Graham for the review and help on the patch.
+</p>
+
+ changeset
+ -
+
Changeset [99e6ac7]: [1.4.x] Fixed a static view test on Windows. Backport of ...
+
+ Tim Graham <timograham@…>
+
+ Wed, 14 Jan 2015 18:57:59 GMT
+ https://code.djangoproject.com/changeset/99e6ac77f2b5c701896dd83ce99ff12278cdbeef
+ https://code.djangoproject.com/changeset/99e6ac77f2b5c701896dd83ce99ff12278cdbeef/1421261879000000
+ <p>
+[1.4.x] Fixed a static view test on Windows.
+</p>
+<p>
+Backport of <a class="changeset" href="https://code.djangoproject.com/changeset/a6f144fd4fee0090de3a99b1f50a4142722e7946/" title="Fixed a static view test on Windows.">a6f144fd4fee0090de3a99b1f50a4142722e7946</a> from master
+</p>
+
+ changeset
+ -
+
Changeset [79df62f]: [1.6.x] Fixed a static view test on Windows. Backport of ...
+
+ Tim Graham <timograham@…>
+
+ Wed, 14 Jan 2015 18:57:14 GMT
+ https://code.djangoproject.com/changeset/79df62f4d2254a1158f5fda4bfddbfcea570c735
+ https://code.djangoproject.com/changeset/79df62f4d2254a1158f5fda4bfddbfcea570c735/1421261834000000
+ <p>
+[1.6.x] Fixed a static view test on Windows.
+</p>
+<p>
+Backport of <a class="changeset" href="https://code.djangoproject.com/changeset/a6f144fd4fee0090de3a99b1f50a4142722e7946/" title="Fixed a static view test on Windows.">a6f144fd4fee0090de3a99b1f50a4142722e7946</a> from master
+</p>
+
+ changeset
+ -
+
Changeset [02c059f]: [1.7.x] Fixed a static view test on Windows. Backport of ...
+
+ Tim Graham <timograham@…>
+
+ Wed, 14 Jan 2015 18:57:10 GMT
+ https://code.djangoproject.com/changeset/02c059ff7f282e541629a67e25a70a292cc01d87
+ https://code.djangoproject.com/changeset/02c059ff7f282e541629a67e25a70a292cc01d87/1421261830000000
+ <p>
+[1.7.x] Fixed a static view test on Windows.
+</p>
+<p>
+Backport of <a class="changeset" href="https://code.djangoproject.com/changeset/a6f144fd4fee0090de3a99b1f50a4142722e7946/" title="Fixed a static view test on Windows.">a6f144fd4fee0090de3a99b1f50a4142722e7946</a> from master
+</p>
+
+ changeset
+ -
+
Changeset [a6f144f]: Fixed a static view test on Windows.
+
+ Tim Graham <timograham@…>
+
+ Wed, 14 Jan 2015 18:56:49 GMT
+ https://code.djangoproject.com/changeset/a6f144fd4fee0090de3a99b1f50a4142722e7946
+ https://code.djangoproject.com/changeset/a6f144fd4fee0090de3a99b1f50a4142722e7946/1421261809000000
+ <p>
+Fixed a static view test on Windows.
+</p>
+
+ changeset
+ -
+
Changeset [88786af]: Fixed #24147 -- Prevented managers leaking model during migrations ...
+
+ Markus Holtermann <info@…>
+
+ Wed, 14 Jan 2015 15:09:33 GMT
+ https://code.djangoproject.com/changeset/88786afbffc5c095f6491e080afea394f63bb44a
+ https://code.djangoproject.com/changeset/88786afbffc5c095f6491e080afea394f63bb44a/1421248173000000
+ <p>
+Fixed <a class="closed ticket" href="https://code.djangoproject.com/ticket/24147" title="Bug: Model managers in migration leak referenced model across project states (closed: fixed)">#24147</a> -- Prevented managers leaking model during migrations
+</p>
+<p>
+Thanks Tim Graham for the review.
+</p>
+
+ changeset
+ -
+
Changeset [2e261799]: [1.7.x] Added stub release notes for 1.7.4. Backport of ...
+
+ Tim Graham <timograham@…>
+
+ Wed, 14 Jan 2015 14:48:06 GMT
+ https://code.djangoproject.com/changeset/2e2617991a6f446defc755c6b73e6e9db865f197
+ https://code.djangoproject.com/changeset/2e2617991a6f446defc755c6b73e6e9db865f197/1421246886000000
+ <p>
+[1.7.x] Added stub release notes for 1.7.4.
+</p>
+<p>
+Backport of <a class="changeset" href="https://code.djangoproject.com/changeset/ec7ef5afbbd12abe74314d557aabb3d85d667749/" title="Added stub release notes for 1.7.4.">ec7ef5afbbd12abe74314d557aabb3d85d667749</a> from master
+</p>
+
+ changeset
+ -
+
Changeset [ec7ef5af]: Added stub release notes for 1.7.4.
+
+ Tim Graham <timograham@…>
+
+ Wed, 14 Jan 2015 14:47:29 GMT
+ https://code.djangoproject.com/changeset/ec7ef5afbbd12abe74314d557aabb3d85d667749
+ https://code.djangoproject.com/changeset/ec7ef5afbbd12abe74314d557aabb3d85d667749/1421246849000000
+ <p>
+Added stub release notes for 1.7.4.
+</p>
+
+ changeset
+ -
+
Changeset [dc90bf2]: Removed unused import
+
+ Claude Paroz <claude@…>
+
+ Wed, 14 Jan 2015 08:31:24 GMT
+ https://code.djangoproject.com/changeset/dc90bf2ac823ed9841cc8ca4035fe0b62a18c958
+ https://code.djangoproject.com/changeset/dc90bf2ac823ed9841cc8ca4035fe0b62a18c958/1421224284000000
+ <p>
+Removed unused import
+</p>
+
+ changeset
+ -
+
Changeset [1913c1a]: Added today's security issues to the archive.
+
+ Tim Graham <timograham@…>
+
+ Tue, 13 Jan 2015 19:44:08 GMT
+ https://code.djangoproject.com/changeset/1913c1ac2190cdc31de9dcd81687f5dad057e2f0
+ https://code.djangoproject.com/changeset/1913c1ac2190cdc31de9dcd81687f5dad057e2f0/1421178248000000
+ <p>
+Added today's security issues to the archive.
+</p>
+
+ changeset
+ -
+
Changeset [7ecd6544]: Removed blank lines from docs/releases/security.txt
+
+ Tim Graham <timograham@…>
+
+ Tue, 13 Jan 2015 19:37:30 GMT
+ https://code.djangoproject.com/changeset/7ecd654497e778550735b77794eae62a9d014272
+ https://code.djangoproject.com/changeset/7ecd654497e778550735b77794eae62a9d014272/1421177850000000
+ <p>
+Removed blank lines from docs/releases/security.txt
+</p>
+
+ changeset
+ -
+
Changeset [4296a1da]: [1.4.x] Post-release version bump.
+
+ Tim Graham <timograham@…>
+
+ Tue, 13 Jan 2015 19:16:07 GMT
+ https://code.djangoproject.com/changeset/4296a1da8b2acf118814669fa045a3c353c8892e
+ https://code.djangoproject.com/changeset/4296a1da8b2acf118814669fa045a3c353c8892e/1421176567000000
+ <p>
+[1.4.x] Post-release version bump.
+</p>
+
+ changeset
+ -
+
Changeset [4c6bd574]: [1.6.x] Post-release version bump.
+
+ Tim Graham <timograham@…>
+
+ Tue, 13 Jan 2015 19:15:55 GMT
+ https://code.djangoproject.com/changeset/4c6bd574ad5fd8c6e9988b571ad41123ed348992
+ https://code.djangoproject.com/changeset/4c6bd574ad5fd8c6e9988b571ad41123ed348992/1421176555000000
+ <p>
+[1.6.x] Post-release version bump.
+</p>
+
+ changeset
+ -
+
Changeset [f7ce66b]: [1.7.x] Post-release version bump.
+
+ Tim Graham <timograham@…>
+
+ Tue, 13 Jan 2015 19:14:05 GMT
+ https://code.djangoproject.com/changeset/f7ce66bc466e04249fbdc66048c3de6f605fddb1
+ https://code.djangoproject.com/changeset/f7ce66bc466e04249fbdc66048c3de6f605fddb1/1421176445000000
+ <p>
+[1.7.x] Post-release version bump.
+</p>
+
+ changeset
+ -
+
Changeset [bd9dcd2]: [1.4.x] Bumped version for 1.4.18 release.
+
+ Tim Graham <timograham@…>
+
+ Tue, 13 Jan 2015 18:14:08 GMT
+ https://code.djangoproject.com/changeset/bd9dcd226b5b11fa40668553d26ce06000b3be75
+ https://code.djangoproject.com/changeset/bd9dcd226b5b11fa40668553d26ce06000b3be75/1421172848000000
+ <p>
+[1.4.x] Bumped version for 1.4.18 release.
+</p>
+
+ changeset
+ -
+
Changeset [e2fea7c7]: [1.6.x] Bumped version for 1.6.10 release.
+
+ Tim Graham <timograham@…>
+
+ Tue, 13 Jan 2015 18:12:22 GMT
+ https://code.djangoproject.com/changeset/e2fea7c7145e6d26a17126c60bf090bab2932e7e
+ https://code.djangoproject.com/changeset/e2fea7c7145e6d26a17126c60bf090bab2932e7e/1421172742000000
+ <p>
+[1.6.x] Bumped version for 1.6.10 release.
+</p>
+
+ changeset
+ -
+
Changeset [6bf1930f]: [1.7.x] Bumped version for 1.7.3 release.
+
+ Tim Graham <timograham@…>
+
+ Tue, 13 Jan 2015 18:11:37 GMT
+ https://code.djangoproject.com/changeset/6bf1930fb5c7c6a47992ff368e21c58f4f14b402
+ https://code.djangoproject.com/changeset/6bf1930fb5c7c6a47992ff368e21c58f4f14b402/1421172697000000
+ <p>
+[1.7.x] Bumped version for 1.7.3 release.
+</p>
+
+ changeset
+ -
+
Changeset [88b7957]: [1.4.x] Added dates to release notes.
+
+ Tim Graham <timograham@…>
+
+ Tue, 13 Jan 2015 18:10:54 GMT
+ https://code.djangoproject.com/changeset/88b7957b34b2305ece54a3aab57a87701279a1d8
+ https://code.djangoproject.com/changeset/88b7957b34b2305ece54a3aab57a87701279a1d8/1421172654000000
+ <p>
+[1.4.x] Added dates to release notes.
+</p>
+
+ changeset
+ -
+
Changeset [f9de1998]: [1.6.x] Added dates to release notes.
+
+ Tim Graham <timograham@…>
+
+ Tue, 13 Jan 2015 18:10:25 GMT
+ https://code.djangoproject.com/changeset/f9de1998f9aac2387b6896653b3aa51ad372da81
+ https://code.djangoproject.com/changeset/f9de1998f9aac2387b6896653b3aa51ad372da81/1421172625000000
+ <p>
+[1.6.x] Added dates to release notes.
+</p>
+
+ changeset
+ -
+
Changeset [d7a06ee7]: [1.6.x] Fixed DoS possibility in ModelMultipleChoiceField. This is a ...
+
+ Tim Graham <timograham@…>
+
+ Tue, 13 Jan 2015 18:10:11 GMT
+ https://code.djangoproject.com/changeset/d7a06ee7e571b6dad07c0f5b519b1db02e2a476c
+ https://code.djangoproject.com/changeset/d7a06ee7e571b6dad07c0f5b519b1db02e2a476c/1421172611000000
+ <p>
+[1.6.x] Fixed DoS possibility in ModelMultipleChoiceField.
+</p>
+<p>
+This is a security fix. Disclosure following shortly.
+</p>
+<p>
+Thanks Keryn Knight for the report and initial patch.
+</p>
+
+ changeset
+ -
+
Changeset [553779c]: [1.6.x] Prevented views.static.serve() from using large memory on ...
+
+ Tim Graham <timograham@…>
+
+ Tue, 13 Jan 2015 18:10:11 GMT
+ https://code.djangoproject.com/changeset/553779c4055e8742cc832ed525b9ee34b174934f
+ https://code.djangoproject.com/changeset/553779c4055e8742cc832ed525b9ee34b174934f/1421172611000000
+ <p>
+[1.6.x] Prevented views.static.serve() from using large memory on large files.
+</p>
+<p>
+This is a security fix. Disclosure following shortly.
+</p>
+
+ changeset
+ -
+
Changeset [72e0b03]: [1.6.x] Fixed is_safe_url() to handle leading whitespace. This is a ...
+
+ Tim Graham <timograham@…>
+
+ Tue, 13 Jan 2015 18:10:11 GMT
+ https://code.djangoproject.com/changeset/72e0b033662faa11bb7f516f18a132728aa0ae28
+ https://code.djangoproject.com/changeset/72e0b033662faa11bb7f516f18a132728aa0ae28/1421172611000000
+ <p>
+[1.6.x] Fixed is_safe_url() to handle leading whitespace.
+</p>
+<p>
+This is a security fix. Disclosure following shortly.
+</p>
+
+ changeset
+ -
+
Changeset [d7597b31]: [1.6.x] Stripped headers containing underscores to prevent spoofing in ...
+
+ Tim Graham <timograham@…>
+
+ Tue, 13 Jan 2015 18:10:11 GMT
+ https://code.djangoproject.com/changeset/d7597b31d5c03106eeba4be14a33b32a5e25f4ee
+ https://code.djangoproject.com/changeset/d7597b31d5c03106eeba4be14a33b32a5e25f4ee/1421172611000000
+ <p>
+[1.6.x] Stripped headers containing underscores to prevent spoofing in WSGI environ.
+</p>
+<p>
+This is a security fix. Disclosure following shortly.
+</p>
+<p>
+Thanks to Jedediah Smith for the report.
+</p>
+
+ changeset
+ -
+
Changeset [f143e25]: [1.6.x] Added stub release notes for security releases.
+
+ Tim Graham <timograham@…>
+
+ Tue, 13 Jan 2015 18:10:11 GMT
+ https://code.djangoproject.com/changeset/f143e25883bb420fd8ec140a09dc88a899e9607e
+ https://code.djangoproject.com/changeset/f143e25883bb420fd8ec140a09dc88a899e9607e/1421172611000000
+ <p>
+[1.6.x] Added stub release notes for security releases.
+</p>
+
+ changeset
+ -
+
Changeset [9b403a1]: [1.7.x] Added dates to release notes.
+
+ Tim Graham <timograham@…>
+
+ Tue, 13 Jan 2015 18:09:34 GMT
+ https://code.djangoproject.com/changeset/9b403a108cfd1e63b5a99174c65c65fc41116d6e
+ https://code.djangoproject.com/changeset/9b403a108cfd1e63b5a99174c65c65fc41116d6e/1421172574000000
+ <p>
+[1.7.x] Added dates to release notes.
+</p>
+
+ changeset
+ -
+
Changeset [cbbe6a6a]: Added dates to release notes.
+
+ Tim Graham <timograham@…>
+
+ Tue, 13 Jan 2015 18:08:57 GMT
+ https://code.djangoproject.com/changeset/cbbe6a6abba6510716e25b7ee9364274334ffcfe
+ https://code.djangoproject.com/changeset/cbbe6a6abba6510716e25b7ee9364274334ffcfe/1421172537000000
+ <p>
+Added dates to release notes.
+</p>
+
+ changeset
+ -
+
Changeset [baf2542]: Fixed DoS possibility in ModelMultipleChoiceField. This is a security ...
+
+ Tim Graham <timograham@…>
+
+ Tue, 13 Jan 2015 18:03:06 GMT
+ https://code.djangoproject.com/changeset/baf2542c4f502d8f5adc3704eb22ca237d50aee1
+ https://code.djangoproject.com/changeset/baf2542c4f502d8f5adc3704eb22ca237d50aee1/1421172186000000
+ <p>
+Fixed DoS possibility in ModelMultipleChoiceField.
+</p>
+<p>
+This is a security fix. Disclosure following shortly.
+</p>
+<p>
+Thanks Keryn Knight for the report and initial patch.
+</p>
+
+ changeset
+ -
+
Changeset [a3bebfd]: Ensured views.static.serve() doesn't use large memory on large files. ...
+
+ Tim Graham <timograham@…>
+
+ Tue, 13 Jan 2015 18:03:06 GMT
+ https://code.djangoproject.com/changeset/a3bebfdc34686622b91e7f3154390ca2058957f9
+ https://code.djangoproject.com/changeset/a3bebfdc34686622b91e7f3154390ca2058957f9/1421172186000000
+ <p>
+Ensured views.static.serve() doesn't use large memory on large files.
+</p>
+<p>
+This issue was fixed in master by refs <a class="closed ticket" href="https://code.djangoproject.com/ticket/24072" title="New feature: Add wsgi.file_wrapper support to responses (closed: fixed)">#24072</a>.
+</p>
+
+ changeset
+ -
+
Changeset [69b5e667]: Fixed is_safe_url() to handle leading whitespace. This is a security ...
+
+ Tim Graham <timograham@…>
+
+ Tue, 13 Jan 2015 18:03:06 GMT
+ https://code.djangoproject.com/changeset/69b5e667385db9ed5e61917a58a75f97b6a97e68
+ https://code.djangoproject.com/changeset/69b5e667385db9ed5e61917a58a75f97b6a97e68/1421172186000000
+ <p>
+Fixed is_safe_url() to handle leading whitespace.
+</p>
+<p>
+This is a security fix. Disclosure following shortly.
+</p>
+
+ changeset
+ -
+
Changeset [316b8d4]: Stripped headers containing underscores to prevent spoofing in WSGI ...
+
+ Tim Graham <timograham@…>
+
+ Tue, 13 Jan 2015 18:03:05 GMT
+ https://code.djangoproject.com/changeset/316b8d49746933d1845d600314b002d9b64d3e3d
+ https://code.djangoproject.com/changeset/316b8d49746933d1845d600314b002d9b64d3e3d/1421172185000000
+ <p>
+Stripped headers containing underscores to prevent spoofing in WSGI environ.
+</p>
+<p>
+This is a security fix. Disclosure following shortly.
+</p>
+<p>
+Thanks to Jedediah Smith for the report.
+</p>
+
+ changeset
+ -
+
Changeset [958aeda]: Added stub release notes for security releases.
+
+ Tim Graham <timograham@…>
+
+ Tue, 13 Jan 2015 18:03:05 GMT
+ https://code.djangoproject.com/changeset/958aeda4b5efcde30438979d93fc585a2f12ce02
+ https://code.djangoproject.com/changeset/958aeda4b5efcde30438979d93fc585a2f12ce02/1421172185000000
+ <p>
+Added stub release notes for security releases.
+</p>
+
+ changeset
+ -
+
Changeset [bcfb477]: [1.7.x] Fixed DoS possibility in ModelMultipleChoiceField. This is a ...
+
+ Tim Graham <timograham@…>
+
+ Tue, 13 Jan 2015 18:02:56 GMT
+ https://code.djangoproject.com/changeset/bcfb47780ce7caecb409a9e9c1c314266e41d392
+ https://code.djangoproject.com/changeset/bcfb47780ce7caecb409a9e9c1c314266e41d392/1421172176000000
+ <p>
+[1.7.x] Fixed DoS possibility in ModelMultipleChoiceField.
+</p>
+<p>
+This is a security fix. Disclosure following shortly.
+</p>
+<p>
+Thanks Keryn Knight for the report and initial patch.
+</p>
+
+ changeset
+ -
+
Changeset [818e59a3]: [1.7.x] Prevented views.static.serve() from using large memory on ...
+
+ Tim Graham <timograham@…>
+
+ Tue, 13 Jan 2015 18:02:56 GMT
+ https://code.djangoproject.com/changeset/818e59a3f0fbadf6c447754d202d88df025f8f2a
+ https://code.djangoproject.com/changeset/818e59a3f0fbadf6c447754d202d88df025f8f2a/1421172176000000
+ <p>
+[1.7.x] Prevented views.static.serve() from using large memory on large files.
+</p>
+<p>
+This is a security fix. Disclosure following shortly.
+</p>
+
+ changeset
+ -
+
Changeset [de67dedc]: [1.7.x] Fixed is_safe_url() to handle leading whitespace. This is a ...
+
+ Tim Graham <timograham@…>
+
+ Tue, 13 Jan 2015 18:02:56 GMT
+ https://code.djangoproject.com/changeset/de67dedc771ad2edec15c1d00c083a1a084e1e89
+ https://code.djangoproject.com/changeset/de67dedc771ad2edec15c1d00c083a1a084e1e89/1421172176000000
+ <p>
+[1.7.x] Fixed is_safe_url() to handle leading whitespace.
+</p>
+<p>
+This is a security fix. Disclosure following shortly.
+</p>
+
+ changeset
+ -
+
Changeset [41b4bc7]: [1.7.x] Stripped headers containing underscores to prevent spoofing in ...
+
+ Tim Graham <timograham@…>
+
+ Tue, 13 Jan 2015 18:02:56 GMT
+ https://code.djangoproject.com/changeset/41b4bc73ee0da7b2e09f4af47fc1fd21144c710f
+ https://code.djangoproject.com/changeset/41b4bc73ee0da7b2e09f4af47fc1fd21144c710f/1421172176000000
+ <p>
+[1.7.x] Stripped headers containing underscores to prevent spoofing in WSGI environ.
+</p>
+<p>
+This is a security fix. Disclosure following shortly.
+</p>
+<p>
+Thanks to Jedediah Smith for the report.
+</p>
+
+ changeset
+ -
+
Changeset [33f1ccf5]: [1.7.x] Added stub release notes for security releases.
+
+ Tim Graham <timograham@…>
+
+ Tue, 13 Jan 2015 18:02:55 GMT
+ https://code.djangoproject.com/changeset/33f1ccf5b1a928b8680e25b3e419834d139e04e8
+ https://code.djangoproject.com/changeset/33f1ccf5b1a928b8680e25b3e419834d139e04e8/1421172175000000
+ <p>
+[1.7.x] Added stub release notes for security releases.
+</p>
+
+ changeset
+ -
+
Changeset [a1a23038]: [1.6.x] Fixed bad model example in admin docs. Backport of ...
+
+ Tim Graham <timograham@…>
+
+ Tue, 13 Jan 2015 16:54:05 GMT
+ https://code.djangoproject.com/changeset/a1a230382d6e510199fb625d2d185a579713b712
+ https://code.djangoproject.com/changeset/a1a230382d6e510199fb625d2d185a579713b712/1421168045000000
+ <p>
+[1.6.x] Fixed bad model example in admin docs.
+</p>
+<p>
+Backport of <a class="changeset" href="https://code.djangoproject.com/changeset/e7771ec380a116dbef481001fb1ce664f5c7311e/" title="Fixed bad model example in admin docs.">e7771ec380a116dbef481001fb1ce664f5c7311e</a> from master
+</p>
+
+ changeset
+ -
+
Changeset [6a08020f]: [1.7.x] Fixed bad model example in admin docs. Backport of ...
+
+ Tim Graham <timograham@…>
+
+ Tue, 13 Jan 2015 16:53:59 GMT
+ https://code.djangoproject.com/changeset/6a08020fcff3681202047ba002c700fedebc6e74
+ https://code.djangoproject.com/changeset/6a08020fcff3681202047ba002c700fedebc6e74/1421168039000000
+ <p>
+[1.7.x] Fixed bad model example in admin docs.
+</p>
+<p>
+Backport of <a class="changeset" href="https://code.djangoproject.com/changeset/e7771ec380a116dbef481001fb1ce664f5c7311e/" title="Fixed bad model example in admin docs.">e7771ec380a116dbef481001fb1ce664f5c7311e</a> from master
+</p>
+
+ changeset
+ -
+
Changeset [e7771ec]: Fixed bad model example in admin docs.
+
+ Tim Graham <timograham@…>
+
+ Tue, 13 Jan 2015 16:53:03 GMT
+ https://code.djangoproject.com/changeset/e7771ec380a116dbef481001fb1ce664f5c7311e
+ https://code.djangoproject.com/changeset/e7771ec380a116dbef481001fb1ce664f5c7311e/1421167983000000
+ <p>
+Fixed bad model example in admin docs.
+</p>
+
+ changeset
+ -
+
Changeset [e084ff0]: Fixed #24136 -- Prevented crash when convert_extent input is None ...
+
+ Claude Paroz <claude@…>
+
+ Tue, 13 Jan 2015 16:27:11 GMT
+ https://code.djangoproject.com/changeset/e084ff01f27a28717eef471ff8e86b074d452f44
+ https://code.djangoproject.com/changeset/e084ff01f27a28717eef471ff8e86b074d452f44/1421166431000000
+ <p>
+Fixed <a class="closed ticket" href="https://code.djangoproject.com/ticket/24136" title="Bug: GeoQueryset.extent() -> TypeError (closed: fixed)">#24136</a> -- Prevented crash when convert_extent input is None
+</p>
+<p>
+Thanks Max Demars for the report.
+</p>
+
+ changeset
+ -
+
Changeset [65246de7]: Fixed #24031 -- Added CASE expressions to the ORM.
+
+ Tim Graham <timograham@…>
+
+ Mon, 12 Jan 2015 23:15:34 GMT
+ https://code.djangoproject.com/changeset/65246de7b1d70d25831ab394c4f4a75813f629fe
+ https://code.djangoproject.com/changeset/65246de7b1d70d25831ab394c4f4a75813f629fe/1421104534000000
+ <p>
+Fixed <a class="closed ticket" href="https://code.djangoproject.com/ticket/24031" title="New feature: Implement a Case/When expression (closed: fixed)">#24031</a> -- Added CASE expressions to the ORM.
+</p>
+
+ changeset
+ -
+
Changeset [aa8ee6a]: Fixed test failures in Oracle introspection Refs #17785
+
+ Shai Berger <shai@…>
+
+ Mon, 12 Jan 2015 23:00:09 GMT
+ https://code.djangoproject.com/changeset/aa8ee6a5731b37b73635e7605521fb1a54a5c10d
+ https://code.djangoproject.com/changeset/aa8ee6a5731b37b73635e7605521fb1a54a5c10d/1421103609000000
+ <p>
+Fixed test failures in Oracle introspection
+</p>
+<p>
+Refs <a class="closed ticket" href="https://code.djangoproject.com/ticket/17785" title="Bug: PostgreSQL Introspection: get_relations() broken after drop column (closed: fixed)">#17785</a>
+</p>
+
+ changeset
+ -
+
Changeset [5d7217dc]: Fixed typo in docs/release/1.8.txt & added word for spelling check.
+
+ Tim Graham <timograham@…>
+
+ Mon, 12 Jan 2015 22:53:32 GMT
+ https://code.djangoproject.com/changeset/5d7217dce307ad55ca9f20da6737f6ab605ab96f
+ https://code.djangoproject.com/changeset/5d7217dce307ad55ca9f20da6737f6ab605ab96f/1421103212000000
+ <p>
+Fixed typo in docs/release/1.8.txt & added word for spelling check.
+</p>
+
+ changeset
+ -
+
Changeset [3844ccc9]: Fixed #24138 -- Added modelform_factory to __all__.
+
+ Tim Graham <timograham@…>
+
+ Mon, 12 Jan 2015 22:44:06 GMT
+ https://code.djangoproject.com/changeset/3844ccc958f59aef731dbb054a51fa37aa1d16ec
+ https://code.djangoproject.com/changeset/3844ccc958f59aef731dbb054a51fa37aa1d16ec/1421102646000000
+ <p>
+Fixed <a class="closed ticket" href="https://code.djangoproject.com/ticket/24138" title="Bug: Expose modelform_factory in __all__ (closed: fixed)">#24138</a> -- Added modelform_factory to <span class="underline">all</span>.
+</p>
+
+ changeset
+ -
+
Changeset [21b858c]: Fixed #24060 -- Added OrderBy Expressions
+
+ Josh Smeaton <josh.smeaton@…>
+
+ Mon, 12 Jan 2015 22:39:55 GMT
+ https://code.djangoproject.com/changeset/21b858cb6735cdfdc695ff7b076e4cbc1981bc88
+ https://code.djangoproject.com/changeset/21b858cb6735cdfdc695ff7b076e4cbc1981bc88/1421102395000000
+ <p>
+Fixed <a class="closed ticket" href="https://code.djangoproject.com/ticket/24060" title="New feature: Allow expressions to be used in order_by queryset method (closed: fixed)">#24060</a> -- Added OrderBy Expressions
+</p>
+
+ changeset
+ -
+
Changeset [f48e2258]: Fixed #24133 -- Replaced formatting syntax in success_url placeholders ...
+
+ Claude Paroz <claude@…>
+
+ Mon, 12 Jan 2015 21:51:22 GMT
+ https://code.djangoproject.com/changeset/f48e2258a96a08dcec843921206bcf7656e3ae45
+ https://code.djangoproject.com/changeset/f48e2258a96a08dcec843921206bcf7656e3ae45/1421099482000000
+ <p>
+Fixed <a class="closed ticket" href="https://code.djangoproject.com/ticket/24133" title="Bug: DeleteView success_url bug for non ascii urls (closed: fixed)">#24133</a> -- Replaced formatting syntax in success_url placeholders
+</p>
+<p>
+Thanks Laurent Payot for the report, and Markus Holtermann, Tim Graham
+for the reviews.
+</p>
+
+ changeset
+ -
+
Changeset [5f7230e]: Fixed #24124 (again) -- Updated tests with new default ...
+
+ Aymeric Augustin <aymeric.augustin@…>
+
+ Mon, 12 Jan 2015 21:31:44 GMT
+ https://code.djangoproject.com/changeset/5f7230e12f0d237cdc19d8930006b0fc7d9dbaa6
+ https://code.djangoproject.com/changeset/5f7230e12f0d237cdc19d8930006b0fc7d9dbaa6/1421098304000000
+ <p>
+Fixed <a class="closed ticket" href="https://code.djangoproject.com/ticket/24124" title="Cleanup/optimization: Review the list of default context processors (closed: fixed)">#24124</a> (again) -- Updated tests with new default context_processors.
+</p>
+<p>
+Thanks Collin for the review.
+</p>
+
+ changeset
+ -
+
Changeset [511a53b]: Avoided exceptions in admindocs' template detail view. This is ...
+
+ Aymeric Augustin <aymeric.augustin@…>
+
+ Mon, 12 Jan 2015 20:01:34 GMT
+ https://code.djangoproject.com/changeset/511a53b3142551a1bc3093ed1b6655f57634f510
+ https://code.djangoproject.com/changeset/511a53b3142551a1bc3093ed1b6655f57634f510/1421092894000000
+ <p>
+Avoided exceptions in admindocs' template detail view.
+</p>
+<p>
+This is marginally better than crashing when several Django template
+engines are configured in a project.
+</p>
+<p>
+Refs <a class="new ticket" href="https://code.djangoproject.com/ticket/24125" title="Bug: TemplateDetailView in admindocs requires a single Django templates engine (new)">#24125</a>.
+</p>
+
+ changeset
+ -
+
Changeset [3bba4b42]: Avoided exceptions in a non-critical check in the admin. This change ...
+
+ Aymeric Augustin <aymeric.augustin@…>
+
+ Mon, 12 Jan 2015 20:01:34 GMT
+ https://code.djangoproject.com/changeset/3bba4b420ed5e89608d9d71ad328fbaed026b232
+ https://code.djangoproject.com/changeset/3bba4b420ed5e89608d9d71ad328fbaed026b232/1421092894000000
+ <p>
+Avoided exceptions in a non-critical check in the admin.
+</p>
+<p>
+This change makes it possible to configure several Django template
+engines in a project and still use the admin. On the flip side the
+check is silently skipped when no Django template engine is configured.
+</p>
+
+ changeset
+ -
+
Changeset [6b5113ec]: Made debug views not crash when there isn't a default template engine.
+
+ Aymeric Augustin <aymeric.augustin@…>
+
+ Mon, 12 Jan 2015 20:01:34 GMT
+ https://code.djangoproject.com/changeset/6b5113ec94e6b9c81e3e4d9a68b0703ccb14c22d
+ https://code.djangoproject.com/changeset/6b5113ec94e6b9c81e3e4d9a68b0703ccb14c22d/1421092894000000
+ <p>
+Made debug views not crash when there isn't a default template engine.
+</p>
+
+ changeset
+ -
+
Changeset [79deb6a0]: Accounted for multiple template engines in template responses.
+
+ Aymeric Augustin <aymeric.augustin@…>
+
+ Mon, 12 Jan 2015 20:01:34 GMT
+ https://code.djangoproject.com/changeset/79deb6a0716e554cac5308e86f5754f19ad436dc
+ https://code.djangoproject.com/changeset/79deb6a0716e554cac5308e86f5754f19ad436dc/1421092894000000
+ <p>
+Accounted for multiple template engines in template responses.
+</p>
+
+ changeset
+ -
+
Changeset [a3e783fe]: Deprecated passing a Context to a generic Template.render. A ...
+
+ Aymeric Augustin <aymeric.augustin@…>
+
+ Mon, 12 Jan 2015 20:01:34 GMT
+ https://code.djangoproject.com/changeset/a3e783fe11dd25bbf84bfb6201186566ed473506
+ https://code.djangoproject.com/changeset/a3e783fe11dd25bbf84bfb6201186566ed473506/1421092894000000
+ <p>
+Deprecated passing a Context to a generic Template.render.
+</p>
+<p>
+A deprecation path is required because the return type of
+django.template.loader.get_template changed during the
+multiple template engines refactor.
+</p>
+<p>
+test_csrf_token_in_404 was incorrect: it tested the case when the
+hardcoded template was rendered, and that template doesn't depend on the
+CSRF token. This commit makes it test the case when a custom template is
+rendered.
+</p>
+
+ changeset
+ -
+
Changeset [71b7668]: Rewrapped TemplateSyntaxError in Jinja2 backend. Changed import style ...
+
+ Aymeric Augustin <aymeric.augustin@…>
+
+ Mon, 12 Jan 2015 20:01:34 GMT
+ https://code.djangoproject.com/changeset/71b7668b75d10589bbbdc7c5ca9ee7a125f91c90
+ https://code.djangoproject.com/changeset/71b7668b75d10589bbbdc7c5ca9ee7a125f91c90/1421092894000000
+ <p>
+Rewrapped TemplateSyntaxError in Jinja2 backend.
+</p>
+<p>
+Changed import style to avoid confusion between Django's and Jinja2's
+APIs.
+</p>
+
+ changeset
+ -
+
Changeset [4c413e23]: Fixed #17785 -- Preferred column names in get_relations introspection ...
+
+ Claude Paroz <claude@…>
+
+ Mon, 12 Jan 2015 18:58:47 GMT
+ https://code.djangoproject.com/changeset/4c413e231cfe788de6e371567f395c8ccbd26103
+ https://code.djangoproject.com/changeset/4c413e231cfe788de6e371567f395c8ccbd26103/1421089127000000
+ <p>
+Fixed <a class="closed ticket" href="https://code.djangoproject.com/ticket/17785" title="Bug: PostgreSQL Introspection: get_relations() broken after drop column (closed: fixed)">#17785</a> -- Preferred column names in get_relations introspection
+</p>
+<p>
+Thanks Thomas Güttler for the report and the initial patch, and
+Tim Graham for the review.
+</p>
+
+ changeset
+ -
+
Changeset [b75c707]: Fixed #24089 -- Added check for when ModelAdmin.fieldsets[1]['fields'] ...
+
+ Tim Graham <timograham@…>
+
+ Mon, 12 Jan 2015 18:47:58 GMT
+ https://code.djangoproject.com/changeset/b75c707943e159b80c179c538721406bbfb8b120
+ https://code.djangoproject.com/changeset/b75c707943e159b80c179c538721406bbfb8b120/1421088478000000
+ <p>
+Fixed <a class="closed ticket" href="https://code.djangoproject.com/ticket/24089" title="Cleanup/optimization: Misleading error raised during system check related to ModelAdmin (closed: fixed)">#24089</a> -- Added check for when ModelAdmin.fieldsets<a class="changeset" href="https://code.djangoproject.com/changeset/1/">[1]</a><a class="missing wiki">fields?</a> isn't a list/tuple.
+</p>
+
+ changeset
+ -
+
Changeset [eeb88123]: Fixed #24129 -- Added indicator that migrations are rendering the ...
+
+ Markus Holtermann <info@…>
+
+ Mon, 12 Jan 2015 18:23:46 GMT
+ https://code.djangoproject.com/changeset/eeb88123e725946802732162a22e5ad856e0c50c
+ https://code.djangoproject.com/changeset/eeb88123e725946802732162a22e5ad856e0c50c/1421087026000000
+ <p>
+Fixed <a class="closed ticket" href="https://code.djangoproject.com/ticket/24129" title="New feature: Add indicator that migrations are rendering model states before ... (closed: fixed)">#24129</a> -- Added indicator that migrations are rendering the initial state
+</p>
+<p>
+Thanks Tim Graham for the review.
+</p>
+
+ changeset
+ -
+
Changeset [9f51d0c]: Fixed test from refs #23913 when running tests in reverse.
+
+ Tim Graham <timograham@…>
+
+ Mon, 12 Jan 2015 18:20:44 GMT
+ https://code.djangoproject.com/changeset/9f51d0c86d9348349d921c393d6ffcbbfd521192
+ https://code.djangoproject.com/changeset/9f51d0c86d9348349d921c393d6ffcbbfd521192/1421086844000000
+ <p>
+Fixed test from refs <a class="closed ticket" href="https://code.djangoproject.com/ticket/23913" title="Cleanup/optimization: = comparison does work in templates although it shouldn't (closed: fixed)">#23913</a> when running tests in reverse.
+</p>
+
+ changeset
+ -
+
Changeset [26a92619]: Fixed #24124 -- Changed context_processors in the default settings.py
+
+ Collin Anderson <cmawebsite@…>
+
+ Mon, 12 Jan 2015 18:17:44 GMT
+ https://code.djangoproject.com/changeset/26a92619f62aeb6f60e15c62e8322c96744eff26
+ https://code.djangoproject.com/changeset/26a92619f62aeb6f60e15c62e8322c96744eff26/1421086664000000
+ <p>
+Fixed <a class="closed ticket" href="https://code.djangoproject.com/ticket/24124" title="Cleanup/optimization: Review the list of default context processors (closed: fixed)">#24124</a> -- Changed context_processors in the default settings.py
+</p>
+
+ changeset
+ -
+
Changeset [bbbed99]: Fixed #24123 -- Used all available migrations to generate the initial ...
+
+ Markus Holtermann <info@…>
+
+ Mon, 12 Jan 2015 17:39:18 GMT
+ https://code.djangoproject.com/changeset/bbbed99f6260a8b3e65cb990e49721b1ce4a441b
+ https://code.djangoproject.com/changeset/bbbed99f6260a8b3e65cb990e49721b1ce4a441b/1421084358000000
+ <p>
+Fixed <a class="closed ticket" href="https://code.djangoproject.com/ticket/24123" title="Bug: LookupError when rolling back migrations (closed: fixed)">#24123</a> -- Used all available migrations to generate the initial migration state
+</p>
+<p>
+Thanks Collin Anderson for the input when creating the patch and Tim Graham for the review.
+</p>
+
+ changeset
+ -
+
Changeset [8f5d6c77]: Fixed #23878 -- Moved Query and Prefetch documentation
+
+ Tim Graham <timograham@…>
+
+ Mon, 12 Jan 2015 16:35:20 GMT
+ https://code.djangoproject.com/changeset/8f5d6c77b6bbe0390b683cea649de6ad24d80fef
+ https://code.djangoproject.com/changeset/8f5d6c77b6bbe0390b683cea649de6ad24d80fef/1421080520000000
+ <p>
+Fixed <a class="closed ticket" href="https://code.djangoproject.com/ticket/23878" title="Cleanup/optimization: Query object/expression documentation needs to tidied (closed: fixed)">#23878</a> -- Moved Query and Prefetch documentation
+</p>
+
+ changeset
+ -
+
Changeset [58833f51]: Made Django's templates get their own LANGUAGE_* variables. Refs #24117
+
+ Tim Graham <timograham@…>
+
+ Mon, 12 Jan 2015 15:54:59 GMT
+ https://code.djangoproject.com/changeset/58833f519797e15ae3cb39b1613e5f7e09c96952
+ https://code.djangoproject.com/changeset/58833f519797e15ae3cb39b1613e5f7e09c96952/1421078099000000
+ <p>
+Made Django's templates get their own LANGUAGE_* variables.
+</p>
+<p>
+Refs <a class="new ticket" href="https://code.djangoproject.com/ticket/24117" title="Cleanup/optimization: make admin not require context_processors (new)">#24117</a>
+</p>
+
+ changeset
+ -
+
Changeset [a7c256c]: Fixed #9893 -- Allowed using a field's max_length in the Storage.
+
+ Tim Graham <timograham@…>
+
+ Mon, 12 Jan 2015 14:09:18 GMT
+ https://code.djangoproject.com/changeset/a7c256cb5491bf2a77abdff01638239db5bfd9d5
+ https://code.djangoproject.com/changeset/a7c256cb5491bf2a77abdff01638239db5bfd9d5/1421071758000000
+ <p>
+Fixed <a class="closed ticket" href="https://code.djangoproject.com/ticket/9893" title="Bug: Filename + path length greater than 100 truncated on database ... (closed: fixed)">#9893</a> -- Allowed using a field's max_length in the Storage.
+</p>
+
+ changeset
+ -
+
Changeset [b5c1a85]: Fixed #24118 -- Added --debug-sql option for tests. Added a ...
+
+ Marc Tamlyn <marc.tamlyn@…>
+
+ Mon, 12 Jan 2015 08:16:08 GMT
+ https://code.djangoproject.com/changeset/b5c1a85b50c709770b8e98aeecfeb8e81ca29dcf
+ https://code.djangoproject.com/changeset/b5c1a85b50c709770b8e98aeecfeb8e81ca29dcf/1421050568000000
+ <p>
+Fixed <a class="closed ticket" href="https://code.djangoproject.com/ticket/24118" title="New feature: Add --debug-sql option to tests to print SQL queries on failure (closed: fixed)">#24118</a> -- Added --debug-sql option for tests.
+</p>
+<p>
+Added a --debug-sql option for tests and runtests.py which outputs the
+SQL logger for failing tests. When combined with --verbosity=2, it also
+outputs the SQL for passing tests.
+</p>
+<p>
+Thanks to Berker, Tim, Markus, Shai, Josh and Anssi for review and
+discussion.
+</p>
+
+ changeset
+ -
+
Changeset [68a439a]: Removed supports_binary_field flag as all backends support them It ...
+
+ Claude Paroz <claude@…>
+
+ Sun, 11 Jan 2015 22:34:47 GMT
+ https://code.djangoproject.com/changeset/68a439a18da17a65555832eff0a7c2090655b583
+ https://code.djangoproject.com/changeset/68a439a18da17a65555832eff0a7c2090655b583/1421015687000000
+ <p>
+Removed supports_binary_field flag as all backends support them
+</p>
+<p>
+It was mainly for MySQL on Python 3, but now the current
+recommended MySQL driver for Python 3 (mysqlclient) does support
+binary fields, it is unneeded. Refs <a class="closed ticket" href="https://code.djangoproject.com/ticket/20377" title="Cleanup/optimization: BinaryField doesn't work on MySQL and Python 3 (closed: fixed)">#20377</a>.
+</p>
+
+ changeset
+ -
+
Changeset [d563e3b]: Fixed #23913 -- Deprecated the `=` comparison in `if` template tag.
+
+ Tim Graham <timograham@…>
+
+ Sun, 11 Jan 2015 20:21:01 GMT
+ https://code.djangoproject.com/changeset/d563e3be68369694a3bac1efd7779d8e03bb6a51
+ https://code.djangoproject.com/changeset/d563e3be68369694a3bac1efd7779d8e03bb6a51/1421007661000000
+ <p>
+Fixed <a class="closed ticket" href="https://code.djangoproject.com/ticket/23913" title="Cleanup/optimization: = comparison does work in templates although it shouldn't (closed: fixed)">#23913</a> -- Deprecated the <tt>=</tt> comparison in <tt>if</tt> template tag.
+</p>
+
+ changeset
+ -
+
Changeset [412066e7]: Revert "Marked a test as an expected failure on MySQL and Python 3.2." ...
+
+ Claude Paroz <claude@…>
+
+ Sun, 11 Jan 2015 19:59:07 GMT
+ https://code.djangoproject.com/changeset/412066e71e81e899bca63523f239e56138ec9e9d
+ https://code.djangoproject.com/changeset/412066e71e81e899bca63523f239e56138ec9e9d/1421006347000000
+ <p>
+Revert "Marked a test as an expected failure on MySQL and Python 3.2."
+</p>
+<p>
+This reverts commit <a class="changeset" href="https://code.djangoproject.com/changeset/832b4a5722ba6b55e7b17c3bac6614ecca9aa88d/" title="Marked a test as an expected failure on MySQL and Python 3.2.
+This ...">832b4a5722ba6b55e7b17c3bac6614ecca9aa88d</a>.
+We officially don't support MySQL on Python 3.2. Refs <a class="closed ticket" href="https://code.djangoproject.com/ticket/20380" title="Cleanup/optimization: MySQL doesn't support dates below 1000 on Python 3.2 (closed: fixed)">#20380</a>.
+</p>
+
+ changeset
+ -
+
Changeset [28de5cd]: Fixed spelling errors in docs.
+
+ Tim Graham <timograham@…>
+
+ Sun, 11 Jan 2015 18:24:13 GMT
+ https://code.djangoproject.com/changeset/28de5cd4de3ddacf67af40c6b31d4fefd85f31f8
+ https://code.djangoproject.com/changeset/28de5cd4de3ddacf67af40c6b31d4fefd85f31f8/1421000653000000
+ <p>
+Fixed spelling errors in docs.
+</p>
+
+ changeset
+
+
+
diff --git a/dashboard/management/__init__.py b/dashboard/management/__init__.py
new file mode 100644
index 000000000..e69de29bb
diff --git a/dashboard/management/commands/__init__.py b/dashboard/management/commands/__init__.py
new file mode 100644
index 000000000..e69de29bb
diff --git a/dashboard/management/commands/update_metrics.py b/dashboard/management/commands/update_metrics.py
new file mode 100644
index 000000000..8da0abaef
--- /dev/null
+++ b/dashboard/management/commands/update_metrics.py
@@ -0,0 +1,17 @@
+from __future__ import absolute_import, print_function
+
+from django.core.management.base import NoArgsCommand
+
+from ...models import Metric
+
+
+class Command(NoArgsCommand):
+ def handle_noargs(self, **options):
+ verbose = int(options.get('verbosity', 0))
+ for MC in Metric.__subclasses__():
+ for metric in MC.objects.all():
+ if verbose:
+ print("Updating %s ... " % metric.name.lower(), end="")
+ datum = metric.data.create(measurement=metric.fetch())
+ if verbose:
+ print(datum.measurement)
diff --git a/dashboard/migrations/0001_initial.py b/dashboard/migrations/0001_initial.py
new file mode 100644
index 000000000..b1f2ceafc
--- /dev/null
+++ b/dashboard/migrations/0001_initial.py
@@ -0,0 +1,129 @@
+# -*- coding: utf-8 -*-
+from __future__ import unicode_literals
+
+from django.db import models, migrations
+import django.db.models.deletion
+import datetime
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('contenttypes', '0001_initial'),
+ ]
+
+ operations = [
+ migrations.CreateModel(
+ name='Category',
+ fields=[
+ ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
+ ('name', models.CharField(max_length=300)),
+ ('position', models.PositiveSmallIntegerField(default=1)),
+ ],
+ options={
+ 'verbose_name_plural': 'categories',
+ },
+ bases=(models.Model,),
+ ),
+ migrations.CreateModel(
+ name='Datum',
+ fields=[
+ ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
+ ('object_id', models.PositiveIntegerField()),
+ ('timestamp', models.DateTimeField(default=datetime.datetime.now)),
+ ('measurement', models.BigIntegerField()),
+ ('content_type', models.ForeignKey(related_name='+', to='contenttypes.ContentType')),
+ ],
+ options={
+ 'ordering': ['-timestamp'],
+ 'get_latest_by': 'timestamp',
+ 'verbose_name_plural': 'data',
+ },
+ bases=(models.Model,),
+ ),
+ migrations.CreateModel(
+ name='GithubItemCountMetric',
+ fields=[
+ ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
+ ('name', models.CharField(max_length=300)),
+ ('slug', models.SlugField()),
+ ('position', models.PositiveSmallIntegerField(default=1)),
+ ('show_on_dashboard', models.BooleanField(default=True)),
+ ('show_sparkline', models.BooleanField(default=True)),
+ ('period', models.CharField(default=b'instant', max_length=15, choices=[(b'instant', b'Instant'), (b'daily', b'Daily'), (b'weekly', b'Weekly')])),
+ ('unit', models.CharField(max_length=100)),
+ ('unit_plural', models.CharField(max_length=100)),
+ ('api_url', models.URLField(max_length=1000)),
+ ('link_url', models.URLField(max_length=1000)),
+ ('category', models.ForeignKey(on_delete=django.db.models.deletion.SET_NULL, blank=True, to='dashboard.Category', null=True)),
+ ],
+ options={
+ 'abstract': False,
+ },
+ bases=(models.Model,),
+ ),
+ migrations.CreateModel(
+ name='JenkinsFailuresMetric',
+ fields=[
+ ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
+ ('name', models.CharField(max_length=300)),
+ ('slug', models.SlugField()),
+ ('position', models.PositiveSmallIntegerField(default=1)),
+ ('show_on_dashboard', models.BooleanField(default=True)),
+ ('show_sparkline', models.BooleanField(default=True)),
+ ('period', models.CharField(default=b'instant', max_length=15, choices=[(b'instant', b'Instant'), (b'daily', b'Daily'), (b'weekly', b'Weekly')])),
+ ('unit', models.CharField(max_length=100)),
+ ('unit_plural', models.CharField(max_length=100)),
+ ('jenkins_root_url', models.URLField(help_text=b'E.g. http://ci.djangoproject.com/', max_length=1000, verbose_name=b'Jenkins instance root URL')),
+ ('build_name', models.CharField(help_text=b'E.g. Django Python3', max_length=100)),
+ ('is_success_cnt', models.BooleanField(default=False, help_text=b'E.g. if there are 50 tests of which 30 are failing the value of this metric will be 20 (or 40%.)', verbose_name=b'Should the metric be a value representing success ratio?')),
+ ('is_percentage', models.BooleanField(default=False, help_text=b'E.g. if there are 50 tests of which 30 are failing the value of this metric will be 60%.', verbose_name=b'Should the metric be a percentage value?')),
+ ('category', models.ForeignKey(on_delete=django.db.models.deletion.SET_NULL, blank=True, to='dashboard.Category', null=True)),
+ ],
+ options={
+ 'abstract': False,
+ },
+ bases=(models.Model,),
+ ),
+ migrations.CreateModel(
+ name='RSSFeedMetric',
+ fields=[
+ ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
+ ('name', models.CharField(max_length=300)),
+ ('slug', models.SlugField()),
+ ('position', models.PositiveSmallIntegerField(default=1)),
+ ('show_on_dashboard', models.BooleanField(default=True)),
+ ('show_sparkline', models.BooleanField(default=True)),
+ ('period', models.CharField(default=b'instant', max_length=15, choices=[(b'instant', b'Instant'), (b'daily', b'Daily'), (b'weekly', b'Weekly')])),
+ ('unit', models.CharField(max_length=100)),
+ ('unit_plural', models.CharField(max_length=100)),
+ ('feed_url', models.URLField(max_length=1000)),
+ ('link_url', models.URLField(max_length=1000)),
+ ('category', models.ForeignKey(on_delete=django.db.models.deletion.SET_NULL, blank=True, to='dashboard.Category', null=True)),
+ ],
+ options={
+ 'abstract': False,
+ },
+ bases=(models.Model,),
+ ),
+ migrations.CreateModel(
+ name='TracTicketMetric',
+ fields=[
+ ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
+ ('name', models.CharField(max_length=300)),
+ ('slug', models.SlugField()),
+ ('position', models.PositiveSmallIntegerField(default=1)),
+ ('show_on_dashboard', models.BooleanField(default=True)),
+ ('show_sparkline', models.BooleanField(default=True)),
+ ('period', models.CharField(default=b'instant', max_length=15, choices=[(b'instant', b'Instant'), (b'daily', b'Daily'), (b'weekly', b'Weekly')])),
+ ('unit', models.CharField(max_length=100)),
+ ('unit_plural', models.CharField(max_length=100)),
+ ('query', models.TextField()),
+ ('category', models.ForeignKey(on_delete=django.db.models.deletion.SET_NULL, blank=True, to='dashboard.Category', null=True)),
+ ],
+ options={
+ 'abstract': False,
+ },
+ bases=(models.Model,),
+ ),
+ ]
diff --git a/dashboard/migrations/__init__.py b/dashboard/migrations/__init__.py
new file mode 100644
index 000000000..e69de29bb
diff --git a/dashboard/models.py b/dashboard/models.py
new file mode 100644
index 000000000..a4969c82c
--- /dev/null
+++ b/dashboard/models.py
@@ -0,0 +1,245 @@
+import ast
+import datetime
+import xmlrpclib
+import feedparser
+import calendar
+import requests
+from django.conf import settings
+from django.contrib.contenttypes.generic import GenericForeignKey, GenericRelation
+from django.contrib.contenttypes.models import ContentType
+from django.db import models, connections
+from django_hosts.resolvers import reverse
+
+METRIC_PERIOD_INSTANT = 'instant'
+METRIC_PERIOD_DAILY = 'daily'
+METRIC_PERIOD_WEEKLY = 'weekly'
+METRIC_PERIOD_CHOICES = (
+ (METRIC_PERIOD_INSTANT, 'Instant'),
+ (METRIC_PERIOD_DAILY, 'Daily'),
+ (METRIC_PERIOD_WEEKLY, 'Weekly'),
+)
+
+
+class Category(models.Model):
+ name = models.CharField(max_length=300)
+ position = models.PositiveSmallIntegerField(default=1)
+
+ class Meta:
+ verbose_name_plural = 'categories'
+
+ def __unicode__(self):
+ return self.name
+
+
+class Metric(models.Model):
+ name = models.CharField(max_length=300)
+ slug = models.SlugField()
+ category = models.ForeignKey(Category, blank=True, null=True,
+ on_delete=models.SET_NULL)
+ position = models.PositiveSmallIntegerField(default=1)
+ data = GenericRelation('Datum')
+ show_on_dashboard = models.BooleanField(default=True)
+ show_sparkline = models.BooleanField(default=True)
+ period = models.CharField(max_length=15, choices=METRIC_PERIOD_CHOICES,
+ default=METRIC_PERIOD_INSTANT)
+ unit = models.CharField(max_length=100)
+ unit_plural = models.CharField(max_length=100)
+
+ class Meta:
+ abstract = True
+
+ def __unicode__(self):
+ return self.name
+
+ def get_absolute_url(self):
+ return reverse("metric-detail", args=[self.slug], host='dashboard')
+
+ @property
+ def display_position(self):
+ cat_position = -1 if self.category is None else self.category.position
+ return cat_position, self.position
+
+ def gather_data(self, since):
+ """
+ Gather all the data from this metric since a given date.
+
+ Returns a list of (timestamp, value) tuples. The timestamp is a Unix
+ timestamp, coverted from localtime to UTC.
+ """
+ if self.period == METRIC_PERIOD_INSTANT:
+ return self._gather_data_instant(since)
+ elif self.period == METRIC_PERIOD_DAILY:
+ return self._gather_data_periodic(since, 'day')
+ elif self.period == METRIC_PERIOD_WEEKLY:
+ return self._gather_data_periodic(since, 'week')
+ else:
+ raise ValueError("Unknown period: %s", self.period)
+
+ def _gather_data_instant(self, since):
+ """
+ Gather data from an "instant" metric.
+
+ Instant metrics change every time we measure them, so they're easy:
+ just return every single measurement.
+ """
+ data = (self.data.filter(timestamp__gt=since)
+ .order_by('timestamp')
+ .values_list('timestamp', 'measurement'))
+ return [(calendar.timegm(t.timetuple()), m) for (t, m) in data]
+
+ def _gather_data_periodic(self, since, period):
+ """
+ Gather data from "periodic" merics.
+
+ Period metrics are reset every day/week/month and count up as the period
+ goes on. Think "commits today" or "new tickets this week".
+
+ XXX I'm not completely sure how to deal with this since time zones wreak
+ havoc, so there's right now a hard-coded offset which doesn't really
+ scale but works for now.
+ """
+ OFFSET = "2 hours" # HACK!
+ ctid = ContentType.objects.get_for_model(self).id
+
+ c = connections['default'].cursor()
+ c.execute('''SELECT
+ DATE_TRUNC(%s, timestamp - INTERVAL %s),
+ MAX(measurement)
+ FROM dashboard_datum
+ WHERE content_type_id = %s
+ AND object_id = %s
+ AND timestamp >= %s
+ GROUP BY 1;''', [period, OFFSET, ctid, self.id, since])
+ return [(calendar.timegm(t.timetuple()), float(m)) for (t, m) in c.fetchall()]
+
+
+class TracTicketMetric(Metric):
+ query = models.TextField()
+
+ def __unicode__(self):
+ return self.name
+
+ def fetch(self):
+ s = xmlrpclib.ServerProxy(settings.TRAC_RPC_URL)
+ return len(s.ticket.query(self.query + "&max=0"))
+
+ def link(self):
+ return "%squery?%s&desc=1&order=changetime" % (settings.TRAC_URL, self.query)
+
+
+class RSSFeedMetric(Metric):
+ feed_url = models.URLField(max_length=1000)
+ link_url = models.URLField(max_length=1000)
+
+ def fetch(self):
+ return len(feedparser.parse(requests.get(self.feed_url).text).entries)
+
+ def link(self):
+ return self.link_url
+
+
+class GithubItemCountMetric(Metric):
+ """Example: https://api.github.com/repos/django/django/pulls?state=open"""
+ api_url = models.URLField(max_length=1000)
+ link_url = models.URLField(max_length=1000)
+
+ def fetch(self):
+ """
+ Request the specified GitHub API URL with 100 items per page. Loop over
+ the pages until no page left. Return total item count.
+ """
+ count = 0
+ page = 1
+ number_of_items_on_page = 101
+ while number_of_items_on_page >= 100:
+ r = requests.get(self.api_url, params={
+ 'page': page,
+ 'per_page': 100
+ })
+ number_of_items_on_page = len(r.json())
+ count += number_of_items_on_page
+ page += 1
+ return count
+
+ def link(self):
+ return self.link_url
+
+
+class JenkinsFailuresMetric(Metric):
+ """
+ Track failures of a job/build. Uses the Python flavor of the Jenkins REST
+ API.
+ """
+ jenkins_root_url = models.URLField(
+ verbose_name='Jenkins instance root URL',
+ max_length=1000,
+ help_text='E.g. http://ci.djangoproject.com/',
+ )
+ build_name = models.CharField(
+ max_length=100,
+ help_text='E.g. Django Python3',
+ )
+ is_success_cnt = models.BooleanField(
+ default=False,
+ verbose_name='Should the metric be a value representing success ratio?',
+ help_text='E.g. if there are 50 tests of which 30 are failing the value of this metric '
+ 'will be 20 (or 40%.)',
+ )
+ is_percentage = models.BooleanField(
+ default=False,
+ verbose_name='Should the metric be a percentage value?',
+ help_text='E.g. if there are 50 tests of which 30 are failing the value of this metric '
+ 'will be 60%.',
+ )
+
+ def urljoin(self, *parts):
+ return '/'.join(p.strip('/') for p in parts)
+
+ def _fetch(self):
+ """
+ Actually get the values we are interested in by using the Jenkins REST
+ API (https://wiki.jenkins-ci.org/display/JENKINS/Remote+access+API)
+ """
+ api_url = self.urljoin(self.link(), 'api/python')
+ job_desc = requests.get(api_url)
+ job_dict = ast.literal_eval(job_desc.text)
+ build_ptr_dict = job_dict['lastCompletedBuild']
+ build_url = self.urljoin(build_ptr_dict['url'], 'api/python')
+ build_desc = requests.get(build_url)
+ build_dict = ast.literal_eval(build_desc.text)
+ return (build_dict['actions'][4]['failCount'], build_dict['actions'][4]['totalCount'])
+
+ def _calculate(self, failures, total):
+ """Calculate the metric value."""
+ if self.is_success_cnt:
+ value = total - failures
+ else:
+ value = failures
+ if self.is_percentage:
+ if not total:
+ return 0
+ value = (value * 100) / total
+ return value
+
+ def fetch(self):
+ failures, total = self._fetch()
+ return self._calculate(failures, total)
+
+ def link(self):
+ return self.urljoin(self.jenkins_root_url, 'job', self.build_name)
+
+
+class Datum(models.Model):
+ metric = GenericForeignKey()
+ content_type = models.ForeignKey(ContentType, related_name='+')
+ object_id = models.PositiveIntegerField()
+ timestamp = models.DateTimeField(default=datetime.datetime.now)
+ measurement = models.BigIntegerField()
+
+ class Meta:
+ ordering = ['-timestamp']
+ get_latest_by = 'timestamp'
+ verbose_name_plural = 'data'
+
+ def __unicode__(self):
+ return "%s at %s: %s" % (self.metric, self.timestamp, self.measurement)
diff --git a/dashboard/tests.py b/dashboard/tests.py
new file mode 100644
index 000000000..2d81ddfae
--- /dev/null
+++ b/dashboard/tests.py
@@ -0,0 +1,111 @@
+# -*- coding: utf-8 -*-
+import codecs
+import json
+from django.http import Http404
+from django.test import TestCase, RequestFactory
+from django_hosts.resolvers import reverse
+import mock
+import requests_mock
+from unipath import Path
+
+from .models import TracTicketMetric, RSSFeedMetric, GithubItemCountMetric, Metric
+from .views import index, metric_detail, metric_json
+
+
+class ViewTests(TestCase):
+ fixtures = ['dashboard_test_data']
+
+ def setUp(self):
+ self.factory = RequestFactory()
+
+ def test_index(self):
+ for MC in Metric.__subclasses__():
+ for metric in MC.objects.filter(show_on_dashboard=True):
+ metric.data.create(measurement=42)
+
+ request = self.factory.get(reverse('dashboard-index', host='dashboard'))
+ response = index(request)
+ self.assertContains(response, 'Django development dashboard')
+ self.assertEqual(response.content.count('
li{position:relative}.icon-li{position:absolute;left:-2.14286em;width:2.14286em;top:0.14286em;text-align:center}.icon-li.icon-lg{left:-1.85714em}.icon-border{padding:.2em .25em .15em;border:solid 0.08em #eee;border-radius:.1em}.pull-right{float:right}.pull-left{float:left}.icon.pull-left{margin-right:.3em}.icon.pull-right{margin-left:.3em}.icon-spin{-webkit-animation:fa-spin 2s infinite linear;animation:fa-spin 2s infinite linear}@-webkit-keyframes fa-spin{0%{-webkit-transform:rotate(0deg);transform:rotate(0deg)}100%{-webkit-transform:rotate(359deg);transform:rotate(359deg)}}@keyframes fa-spin{0%{-webkit-transform:rotate(0deg);transform:rotate(0deg)}100%{-webkit-transform:rotate(359deg);transform:rotate(359deg)}}.icon-rotate-90{filter:progid:DXImageTransform.Microsoft.BasicImage(rotation=1);-webkit-transform:rotate(90deg);-ms-transform:rotate(90deg);transform:rotate(90deg)}.icon-rotate-180{filter:progid:DXImageTransform.Microsoft.BasicImage(rotation=2);-webkit-transform:rotate(180deg);-ms-transform:rotate(180deg);transform:rotate(180deg)}.icon-rotate-270{filter:progid:DXImageTransform.Microsoft.BasicImage(rotation=3);-webkit-transform:rotate(270deg);-ms-transform:rotate(270deg);transform:rotate(270deg)}.icon-flip-horizontal{filter:progid:DXImageTransform.Microsoft.BasicImage(rotation=0);-webkit-transform:scale(-1, 1);-ms-transform:scale(-1, 1);transform:scale(-1, 1)}.icon-flip-vertical{filter:progid:DXImageTransform.Microsoft.BasicImage(rotation=2);-webkit-transform:scale(1, -1);-ms-transform:scale(1, -1);transform:scale(1, -1)}:root .icon-rotate-90,:root .icon-rotate-180,:root .icon-rotate-270,:root .icon-flip-horizontal,:root .icon-flip-vertical{filter:none}.icon-stack{position:relative;display:inline-block;width:2em;height:2em;line-height:2em;vertical-align:middle}.icon-stack-1x,.icon-stack-2x{position:absolute;left:0;width:100%;text-align:center}.icon-stack-1x{line-height:inherit}.icon-stack-2x{font-size:2em}.icon-inverse{color:#fff}.icon-glass:before{content:""}.icon-music:before{content:""}.icon-search:before{content:""}.icon-envelope-o:before{content:""}.icon-heart:before{content:""}.icon-star:before{content:""}.icon-star-o:before{content:""}.icon-user:before{content:""}.icon-film:before{content:""}.icon-th-large:before{content:""}.icon-th:before{content:""}.icon-th-list:before{content:""}.icon-check:before{content:""}.icon-remove:before,.icon-close:before,.icon-times:before{content:""}.icon-search-plus:before{content:""}.icon-search-minus:before{content:""}.icon-power-off:before{content:""}.icon-signal:before{content:""}.icon-gear:before,.icon-cog:before{content:""}.icon-trash-o:before{content:""}.icon-home:before{content:""}.icon-file-o:before{content:""}.icon-clock-o:before{content:""}.icon-road:before{content:""}.icon-download:before{content:""}.icon-arrow-circle-o-down:before{content:""}.icon-arrow-circle-o-up:before{content:""}.icon-inbox:before{content:""}.icon-play-circle-o:before{content:""}.icon-rotate-right:before,.icon-repeat:before{content:""}.icon-refresh:before{content:""}.icon-list-alt:before{content:""}.icon-lock:before{content:""}.icon-flag:before{content:""}.icon-headphones:before{content:""}.icon-volume-off:before{content:""}.icon-volume-down:before{content:""}.icon-volume-up:before{content:""}.icon-qrcode:before{content:""}.icon-barcode:before{content:""}.icon-tag:before{content:""}.icon-tags:before{content:""}.icon-book:before{content:""}.icon-bookmark:before{content:""}.icon-print:before{content:""}.icon-camera:before{content:""}.icon-font:before{content:""}.icon-bold:before{content:""}.icon-italic:before{content:""}.icon-text-height:before{content:""}.icon-text-width:before{content:""}.icon-align-left:before{content:""}.icon-align-center:before{content:""}.icon-align-right:before{content:""}.icon-align-justify:before{content:""}.icon-list:before{content:""}.icon-dedent:before,.icon-outdent:before{content:""}.icon-indent:before{content:""}.icon-video-camera:before{content:""}.icon-photo:before,.icon-image:before,.icon-picture-o:before{content:""}.icon-pencil:before{content:""}.icon-map-marker:before{content:""}.icon-adjust:before{content:""}.icon-tint:before{content:""}.icon-edit:before,.icon-pencil-square-o:before{content:""}.icon-share-square-o:before{content:""}.icon-check-square-o:before{content:""}.icon-arrows:before{content:""}.icon-step-backward:before{content:""}.icon-fast-backward:before{content:""}.icon-backward:before{content:""}.icon-play:before{content:""}.icon-pause:before{content:""}.icon-stop:before{content:""}.icon-forward:before{content:""}.icon-fast-forward:before{content:""}.icon-step-forward:before{content:""}.icon-eject:before{content:""}.icon-chevron-left:before{content:""}.icon-chevron-right:before{content:""}.icon-plus-circle:before{content:""}.icon-minus-circle:before{content:""}.icon-times-circle:before{content:""}.icon-check-circle:before{content:""}.icon-question-circle:before{content:""}.icon-info-circle:before{content:""}.icon-crosshairs:before{content:""}.icon-times-circle-o:before{content:""}.icon-check-circle-o:before{content:""}.icon-ban:before{content:""}.icon-arrow-left:before{content:""}.icon-arrow-right:before{content:""}.icon-arrow-up:before{content:""}.icon-arrow-down:before{content:""}.icon-mail-forward:before,.icon-share:before{content:""}.icon-expand:before{content:""}.icon-compress:before{content:""}.icon-plus:before{content:""}.icon-minus:before{content:""}.icon-asterisk:before{content:""}.icon-exclamation-circle:before{content:""}.icon-gift:before{content:""}.icon-leaf:before{content:""}.icon-fire:before{content:""}.icon-eye:before{content:""}.icon-eye-slash:before{content:""}.icon-warning:before,.icon-exclamation-triangle:before{content:""}.icon-plane:before{content:""}.icon-calendar:before{content:""}.icon-random:before{content:""}.icon-comment:before{content:""}.icon-magnet:before{content:""}.icon-chevron-up:before{content:""}.icon-chevron-down:before{content:""}.icon-retweet:before{content:""}.icon-shopping-cart:before{content:""}.icon-folder:before{content:""}.icon-folder-open:before{content:""}.icon-arrows-v:before{content:""}.icon-arrows-h:before{content:""}.icon-bar-chart-o:before,.icon-bar-chart:before{content:""}.icon-twitter-square:before{content:""}.icon-facebook-square:before{content:""}.icon-camera-retro:before{content:""}.icon-key:before{content:""}.icon-gears:before,.icon-cogs:before{content:""}.icon-comments:before{content:""}.icon-thumbs-o-up:before{content:""}.icon-thumbs-o-down:before{content:""}.icon-star-half:before{content:""}.icon-heart-o:before{content:""}.icon-sign-out:before{content:""}.icon-linkedin-square:before{content:""}.icon-thumb-tack:before{content:""}.icon-external-link:before{content:""}.icon-sign-in:before{content:""}.icon-trophy:before{content:""}.icon-github-square:before{content:""}.icon-upload:before{content:""}.icon-lemon-o:before{content:""}.icon-phone:before{content:""}.icon-square-o:before{content:""}.icon-bookmark-o:before{content:""}.icon-phone-square:before{content:""}.icon-twitter:before{content:""}.icon-facebook:before{content:""}.icon-github:before{content:""}.icon-unlock:before{content:""}.icon-credit-card:before{content:""}.icon-rss:before{content:""}.icon-hdd-o:before{content:""}.icon-bullhorn:before{content:""}.icon-bell:before{content:""}.icon-certificate:before{content:""}.icon-hand-o-right:before{content:""}.icon-hand-o-left:before{content:""}.icon-hand-o-up:before{content:""}.icon-hand-o-down:before{content:""}.icon-arrow-circle-left:before{content:""}.icon-arrow-circle-right:before{content:""}.icon-arrow-circle-up:before{content:""}.icon-arrow-circle-down:before{content:""}.icon-globe:before{content:""}.icon-wrench:before{content:""}.icon-tasks:before{content:""}.icon-filter:before{content:""}.icon-briefcase:before{content:""}.icon-arrows-alt:before{content:""}.icon-group:before,.icon-users:before{content:""}.icon-chain:before,.icon-link:before{content:""}.icon-cloud:before{content:""}.icon-flask:before{content:""}.icon-cut:before,.icon-scissors:before{content:""}.icon-copy:before,.icon-files-o:before{content:""}.icon-paperclip:before{content:""}.icon-save:before,.icon-floppy-o:before{content:""}.icon-square:before{content:""}.icon-navicon:before,.icon-reorder:before,.icon-bars:before{content:""}.icon-list-ul:before{content:""}.icon-list-ol:before{content:""}.icon-strikethrough:before{content:""}.icon-underline:before{content:""}.icon-table:before{content:""}.icon-magic:before{content:""}.icon-truck:before{content:""}.icon-pinterest:before{content:""}.icon-pinterest-square:before{content:""}.icon-google-plus-square:before{content:""}.icon-google-plus:before{content:""}.icon-money:before{content:""}.icon-caret-down:before{content:""}.icon-caret-up:before{content:""}.icon-caret-left:before{content:""}.icon-caret-right:before{content:""}.icon-columns:before{content:""}.icon-unsorted:before,.icon-sort:before{content:""}.icon-sort-down:before,.icon-sort-desc:before{content:""}.icon-sort-up:before,.icon-sort-asc:before{content:""}.icon-envelope:before{content:""}.icon-linkedin:before{content:""}.icon-rotate-left:before,.icon-undo:before{content:""}.icon-legal:before,.icon-gavel:before{content:""}.icon-dashboard:before,.icon-tachometer:before{content:""}.icon-comment-o:before{content:""}.icon-comments-o:before{content:""}.icon-flash:before,.icon-bolt:before{content:""}.icon-sitemap:before{content:""}.icon-umbrella:before{content:""}.icon-paste:before,.icon-clipboard:before{content:""}.icon-lightbulb-o:before{content:""}.icon-exchange:before{content:""}.icon-cloud-download:before{content:""}.icon-cloud-upload:before{content:""}.icon-user-md:before{content:""}.icon-stethoscope:before{content:""}.icon-suitcase:before{content:""}.icon-bell-o:before{content:""}.icon-coffee:before{content:""}.icon-cutlery:before{content:""}.icon-file-text-o:before{content:""}.icon-building-o:before{content:""}.icon-hospital-o:before{content:""}.icon-ambulance:before{content:""}.icon-medkit:before{content:""}.icon-fighter-jet:before{content:""}.icon-beer:before{content:""}.icon-h-square:before{content:""}.icon-plus-square:before{content:""}.icon-angle-double-left:before{content:""}.icon-angle-double-right:before{content:""}.icon-angle-double-up:before{content:""}.icon-angle-double-down:before{content:""}.icon-angle-left:before{content:""}.icon-angle-right:before{content:""}.icon-angle-up:before{content:""}.icon-angle-down:before{content:""}.icon-desktop:before{content:""}.icon-laptop:before{content:""}.icon-tablet:before{content:""}.icon-mobile-phone:before,.icon-mobile:before{content:""}.icon-circle-o:before{content:""}.icon-quote-left:before{content:""}.icon-quote-right:before{content:""}.icon-spinner:before{content:""}.icon-circle:before{content:""}.icon-mail-reply:before,.icon-reply:before{content:""}.icon-github-alt:before{content:""}.icon-folder-o:before{content:""}.icon-folder-open-o:before{content:""}.icon-smile-o:before{content:""}.icon-frown-o:before{content:""}.icon-meh-o:before{content:""}.icon-gamepad:before{content:""}.icon-keyboard-o:before{content:""}.icon-flag-o:before{content:""}.icon-flag-checkered:before{content:""}.icon-terminal:before{content:""}.icon-code:before{content:""}.icon-mail-reply-all:before,.icon-reply-all:before{content:""}.icon-star-half-empty:before,.icon-star-half-full:before,.icon-star-half-o:before{content:""}.icon-location-arrow:before{content:""}.icon-crop:before{content:""}.icon-code-fork:before{content:""}.icon-unlink:before,.icon-chain-broken:before{content:""}.icon-question:before{content:""}.icon-info:before{content:""}.icon-exclamation:before{content:""}.icon-superscript:before{content:""}.icon-subscript:before{content:""}.icon-eraser:before{content:""}.icon-puzzle-piece:before{content:""}.icon-microphone:before{content:""}.icon-microphone-slash:before{content:""}.icon-shield:before{content:""}.icon-calendar-o:before{content:""}.icon-fire-extinguisher:before{content:""}.icon-rocket:before{content:""}.icon-maxcdn:before{content:""}.icon-chevron-circle-left:before{content:""}.icon-chevron-circle-right:before{content:""}.icon-chevron-circle-up:before{content:""}.icon-chevron-circle-down:before{content:""}.icon-html5:before{content:""}.icon-css3:before{content:""}.icon-anchor:before{content:""}.icon-unlock-alt:before{content:""}.icon-bullseye:before{content:""}.icon-ellipsis-h:before{content:""}.icon-ellipsis-v:before{content:""}.icon-rss-square:before{content:""}.icon-play-circle:before{content:""}.icon-ticket:before{content:""}.icon-minus-square:before{content:""}.icon-minus-square-o:before{content:""}.icon-level-up:before{content:""}.icon-level-down:before{content:""}.icon-check-square:before{content:""}.icon-pencil-square:before{content:""}.icon-external-link-square:before{content:""}.icon-share-square:before{content:""}.icon-compass:before{content:""}.icon-toggle-down:before,.icon-caret-square-o-down:before{content:""}.icon-toggle-up:before,.icon-caret-square-o-up:before{content:""}.icon-toggle-right:before,.icon-caret-square-o-right:before{content:""}.icon-euro:before,.icon-eur:before{content:""}.icon-gbp:before{content:""}.icon-dollar:before,.icon-usd:before{content:""}.icon-rupee:before,.icon-inr:before{content:""}.icon-cny:before,.icon-rmb:before,.icon-yen:before,.icon-jpy:before{content:""}.icon-ruble:before,.icon-rouble:before,.icon-rub:before{content:""}.icon-won:before,.icon-krw:before{content:""}.icon-bitcoin:before,.icon-btc:before{content:""}.icon-file:before{content:""}.icon-file-text:before{content:""}.icon-sort-alpha-asc:before{content:""}.icon-sort-alpha-desc:before{content:""}.icon-sort-amount-asc:before{content:""}.icon-sort-amount-desc:before{content:""}.icon-sort-numeric-asc:before{content:""}.icon-sort-numeric-desc:before{content:""}.icon-thumbs-up:before{content:""}.icon-thumbs-down:before{content:""}.icon-youtube-square:before{content:""}.icon-youtube:before{content:""}.icon-xing:before{content:""}.icon-xing-square:before{content:""}.icon-youtube-play:before{content:""}.icon-dropbox:before{content:""}.icon-stack-overflow:before{content:""}.icon-instagram:before{content:""}.icon-flickr:before{content:""}.icon-adn:before{content:""}.icon-bitbucket:before{content:""}.icon-bitbucket-square:before{content:""}.icon-tumblr:before{content:""}.icon-tumblr-square:before{content:""}.icon-long-arrow-down:before{content:""}.icon-long-arrow-up:before{content:""}.icon-long-arrow-left:before{content:""}.icon-long-arrow-right:before{content:""}.icon-apple:before{content:""}.icon-windows:before{content:""}.icon-android:before{content:""}.icon-linux:before{content:""}.icon-dribbble:before{content:""}.icon-skype:before{content:""}.icon-foursquare:before{content:""}.icon-trello:before{content:""}.icon-female:before{content:""}.icon-male:before{content:""}.icon-gittip:before{content:""}.icon-sun-o:before{content:""}.icon-moon-o:before{content:""}.icon-archive:before{content:""}.icon-bug:before{content:""}.icon-vk:before{content:""}.icon-weibo:before{content:""}.icon-renren:before{content:""}.icon-pagelines:before{content:""}.icon-stack-exchange:before{content:""}.icon-arrow-circle-o-right:before{content:""}.icon-arrow-circle-o-left:before{content:""}.icon-toggle-left:before,.icon-caret-square-o-left:before{content:""}.icon-dot-circle-o:before{content:""}.icon-wheelchair:before{content:""}.icon-vimeo-square:before{content:""}.icon-turkish-lira:before,.icon-try:before{content:""}.icon-plus-square-o:before{content:""}.icon-space-shuttle:before{content:""}.icon-slack:before{content:""}.icon-envelope-square:before{content:""}.icon-wordpress:before{content:""}.icon-openid:before{content:""}.icon-institution:before,.icon-bank:before,.icon-university:before{content:""}.icon-mortar-board:before,.icon-graduation-cap:before{content:""}.icon-yahoo:before{content:""}.icon-google:before{content:""}.icon-reddit:before{content:""}.icon-reddit-square:before{content:""}.icon-stumbleupon-circle:before{content:""}.icon-stumbleupon:before{content:""}.icon-delicious:before{content:""}.icon-digg:before{content:""}.icon-pied-piper:before{content:""}.icon-pied-piper-alt:before{content:""}.icon-drupal:before{content:""}.icon-joomla:before{content:""}.icon-language:before{content:""}.icon-fax:before{content:""}.icon-building:before{content:""}.icon-child:before{content:""}.icon-paw:before{content:""}.icon-spoon:before{content:""}.icon-cube:before{content:""}.icon-cubes:before{content:""}.icon-behance:before{content:""}.icon-behance-square:before{content:""}.icon-steam:before{content:""}.icon-steam-square:before{content:""}.icon-recycle:before{content:""}.icon-automobile:before,.icon-car:before{content:""}.icon-cab:before,.icon-taxi:before{content:""}.icon-tree:before{content:""}.icon-spotify:before{content:""}.icon-deviantart:before{content:""}.icon-soundcloud:before{content:""}.icon-database:before{content:""}.icon-file-pdf-o:before{content:""}.icon-file-word-o:before{content:""}.icon-file-excel-o:before{content:""}.icon-file-powerpoint-o:before{content:""}.icon-file-photo-o:before,.icon-file-picture-o:before,.icon-file-image-o:before{content:""}.icon-file-zip-o:before,.icon-file-archive-o:before{content:""}.icon-file-sound-o:before,.icon-file-audio-o:before{content:""}.icon-file-movie-o:before,.icon-file-video-o:before{content:""}.icon-file-code-o:before{content:""}.icon-vine:before{content:""}.icon-codepen:before{content:""}.icon-jsfiddle:before{content:""}.icon-life-bouy:before,.icon-life-buoy:before,.icon-life-saver:before,.icon-support:before,.icon-life-ring:before{content:""}.icon-circle-o-notch:before{content:""}.icon-ra:before,.icon-rebel:before{content:""}.icon-ge:before,.icon-empire:before{content:""}.icon-git-square:before{content:""}.icon-git:before{content:""}.icon-hacker-news:before{content:""}.icon-tencent-weibo:before{content:""}.icon-qq:before{content:""}.icon-wechat:before,.icon-weixin:before{content:""}.icon-send:before,.icon-paper-plane:before{content:""}.icon-send-o:before,.icon-paper-plane-o:before{content:""}.icon-history:before{content:""}.icon-circle-thin:before{content:""}.icon-header:before{content:""}.icon-paragraph:before{content:""}.icon-sliders:before{content:""}.icon-share-alt:before{content:""}.icon-share-alt-square:before{content:""}.icon-bomb:before{content:""}.icon-soccer-ball-o:before,.icon-futbol-o:before{content:""}.icon-tty:before{content:""}.icon-binoculars:before{content:""}.icon-plug:before{content:""}.icon-slideshare:before{content:""}.icon-twitch:before{content:""}.icon-yelp:before{content:""}.icon-newspaper-o:before{content:""}.icon-wifi:before{content:""}.icon-calculator:before{content:""}.icon-paypal:before{content:""}.icon-google-wallet:before{content:""}.icon-cc-visa:before{content:""}.icon-cc-mastercard:before{content:""}.icon-cc-discover:before{content:""}.icon-cc-amex:before{content:""}.icon-cc-paypal:before{content:""}.icon-cc-stripe:before{content:""}.icon-bell-slash:before{content:""}.icon-bell-slash-o:before{content:""}.icon-trash:before{content:""}.icon-copyright:before{content:""}.icon-at:before{content:""}.icon-eyedropper:before{content:""}.icon-paint-brush:before{content:""}.icon-birthday-cake:before{content:""}.icon-area-chart:before{content:""}.icon-pie-chart:before{content:""}.icon-line-chart:before{content:""}.icon-lastfm:before{content:""}.icon-lastfm-square:before{content:""}.icon-toggle-off:before{content:""}.icon-toggle-on:before{content:""}.icon-bicycle:before{content:""}.icon-bus:before{content:""}.icon-ioxhost:before{content:""}.icon-angellist:before{content:""}.icon-cc:before{content:""}.icon-shekel:before,.icon-sheqel:before,.icon-ils:before{content:""}.icon-meanpath:before{content:""}body{font-family:Palatino, "Palatino Linotype", "Book Antiqua", "Hoefler Text", Georgia, "Lucida Bright", Cambria, Times, "Times New Roman", serif;font-size:18px;font-size:1.8rem;background:#f8f8f8;color:#0C3C26;line-height:1.6;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;min-width:768px}body .layout-secondary,body [role="complementary"]{font-family:"Roboto", Corbel, Avenir, "Lucida Grande", "Lucida Sans", sans-serif}a{color:#0C3C26;text-decoration:underline;-webkit-tap-highlight-color:transparent}a:visited{color:#156641}a:active,a:focus,a:hover{color:#1d915c}::selection{background:#C9F0DD}::-moz-selection{background:#C9F0DD}ol li,ul li{margin-top:10px}dl{margin:20px 0 10px}dl dt{font-family:"Roboto", Corbel, Avenir, "Lucida Grande", "Lucida Sans", sans-serif;font-weight:400}dl dd{margin:0.2em 0 1.2em;padding:0}dl dd:last-of-type{margin-bottom:0}h1,h2,h3,h4,h5,h6{font-family:"Roboto", Corbel, Avenir, "Lucida Grande", "Lucida Sans", sans-serif;font-weight:400}h1 a,h2 a,h3 a,h4 a,h5 a,h6 a{text-decoration:none;color:#20AA76;text-decoration:none}h1 a:visited,h2 a:visited,h3 a:visited,h4 a:visited,h5 a:visited,h6 a:visited{color:#20AA76}h1 a:hover,h1 a:active,h1 a:focus,h2 a:hover,h2 a:active,h2 a:focus,h3 a:hover,h3 a:active,h3 a:focus,h4 a:hover,h4 a:active,h4 a:focus,h5 a:hover,h5 a:active,h5 a:focus,h6 a:hover,h6 a:active,h6 a:focus{color:#25c488;text-decoration:none}h1{font-size:28px;font-size:2.8rem;color:#fff;letter-spacing:-1px;line-height:1.1;font-size:32px;font-size:3.2rem}.layout-secondary h1{color:#0C3C26}[role="main"] h1{font-size:32px;font-size:3.2rem;margin:40px 0px 30px;color:#0C3C26}[role="complementary"] h1{font-size:28px;font-size:2.8rem}h2{font-size:24px;font-size:2.4rem}[role="complementary"] h2,.layout-secondary h2{font-size:20px;font-size:2rem;border-bottom:1px solid #CFE3DC;font-weight:400;padding-bottom:15px;margin-top:30px}[role="complementary"] h2:first-of-type,.layout-secondary h2:first-of-type{margin-top:inherit}[role="complementary"] h2:first-child,.layout-secondary h2:first-child{margin-top:20px}.full-width [role="complementary"] h2,.full-width .layout-secondary h2{font-size:24px;font-size:2.4rem}[role="main"] h2{margin-top:40px;margin-bottom:15px}h3{font-size:20px;font-size:2rem;font-weight:700;color:#0C3C26;line-height:1.2;margin:35px 0 20px}[role="complementary"] h3,.layout-secondary h3{font-size:18px;font-size:1.8rem;font-weight:400;padding-bottom:15px}[role="complementary"] h3:first-child,.layout-secondary h3:first-child{margin-top:12px}[role="complementary"] h3{font-size:18px;font-size:1.8rem;border-bottom:1px solid #CFE3DC}h4{font-size:16px;font-size:1.6rem;color:#0C3C26;line-height:1.2;margin:35px 0 20px;font-weight:700}tt,code,kbd,pre,samp{font-family:"Fira Mono", Consolas, Menlo, Monaco, "Courier New", Courier, monospace;font-variant-ligatures:no-common-ligatures;text-rendering:optimizeSpeed;color:#0C4B33;font-size:1em}tt{font-weight:700}span.pre{font-family:"Fira Mono", Consolas, Menlo, Monaco, "Courier New", Courier, monospace;font-variant-ligatures:no-common-ligatures;text-rendering:optimizeSpeed}a:hover tt,a:active tt,a:focus tt{color:#1d915c}[role="main"]>p:first-child{margin-top:30px}ul{padding-left:20px}blockquote{background:#F1FFF7;padding:15px 20px 15px 70px;border:1px solid #C9F0DD;-moz-border-radius:4px;-webkit-border-radius:4px;border-radius:4px;margin:25px 0;position:relative}blockquote p:first-child{margin-top:0}blockquote::before{display:inline-block;font:normal normal normal 14px/1 FontAwesome;font-size:inherit;text-rendering:auto;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;position:absolute;top:20px;left:20px;font-size:30px;width:34px;text-align:center;content:"";opacity:0.5}[role="main"]{background:#fff;padding:1px 10px 40px;min-height:800px;border:1px solid #ddd;float:right;margin:0;padding-bottom:80px;padding-left:3%;padding-right:3%;width:60%}.mdzr-boxshadow [role="main"]{border:none;-moz-box-shadow:0px 0px 0 0 #fff,0px 0px 0 0 #fff,460px 0 0 0 #fff,1000px 0 0 0 #fff,0px 600px 0 0px #fff,460px 600px 0 0px #fff;-webkit-box-shadow:0px 0px 0 0 #fff,0px 0px 0 0 #fff,460px 0 0 0 #fff,1000px 0 0 0 #fff,0px 600px 0 0px #fff,460px 600px 0 0px #fff;box-shadow:0px 0px 0 0 #fff,0px 0px 0 0 #fff,460px 0 0 0 #fff,1000px 0 0 0 #fff,0px 600px 0 0px #fff,460px 600px 0 0px #fff;padding-left:4%;padding-right:0;width:63%}.sidebar-right [role="main"]{float:left}.mdzr-boxshadow .sidebar-right [role="main"]{border:none;-moz-box-shadow:0px 0px 0 0 #fff,0px 0px 0 0 #fff,-460px 0 0 0 #fff,-1000px 0 0 0 #fff,0px 600px 0 0px #fff,-460px 600px 0 0px #fff;-webkit-box-shadow:0px 0px 0 0 #fff,0px 0px 0 0 #fff,-460px 0 0 0 #fff,-1000px 0 0 0 #fff,0px 600px 0 0px #fff,-460px 600px 0 0px #fff;box-shadow:0px 0px 0 0 #fff,0px 0px 0 0 #fff,-460px 0 0 0 #fff,-1000px 0 0 0 #fff,0px 600px 0 0px #fff,-460px 600px 0 0px #fff;padding-left:0;width:62%;padding-right:4%}.full-width [role="main"]{border:none;-moz-box-shadow:none;-webkit-box-shadow:none;box-shadow:none;float:none;margin:0 auto;padding:0 10px 40px;width:auto;max-width:740px;padding:20px 0 40px;width:91.66667%}[role="main"] .section{padding-bottom:40px;border-bottom:1px solid #CFE3DC}[role="main"] .section:last-of-type{padding-bottom:0;border-bottom:0}[role="main"] .section dd.last-child{padding-bottom:0}.full-width{zoom:1}.full-width.container{width:100%;padding:0;max-width:none;border-bottom:1px solid #ddd;background:#fff}.full-width:before,.full-width:after{content:"";display:table}.full-width:after{clear:both}.mdzr-boxshadow .full-width{border:none;-moz-box-shadow:0 4px 8px rgba(12,60,38,0.07);-webkit-box-shadow:0 4px 8px rgba(12,60,38,0.07);box-shadow:0 4px 8px rgba(12,60,38,0.07)}[role="complementary"]{padding:0 10px 20px;font-size:14px;font-size:1.4rem;float:right;margin:20px 0;margin-right:3%;padding:0 0 40px 0;width:30%}.sidebar-right [role="complementary"]{margin-left:3%;margin-right:0}[role="complementary"] span.form-controls{display:none}[role="complementary"] .list-collapsing{margin-top:0;border-bottom:0}[role="complementary"] .list-collapsing.active li{border-top:0}[role="complementary"] .list-collapsing.active h2{padding:10px 40px 10px 0;font-size:18px;border-bottom:0;color:#44B78B}[role="complementary"] .list-collapsing.active h2 .collapsing-icon{font-size:10px}[role="secondary"]{margin:0 10px;padding:40px 0 60px}.full-width [role="secondary"]{max-width:700px;margin:0 auto}.layout-secondary{padding:20px 10px 50px}.layout-tertiary{background:#fff;border-top:1px solid #ddd;padding:20px 10px 50px}.mdzr-boxshadow .layout-tertiary{border:none;-moz-box-shadow:0 -4px 8px rgba(12,60,38,0.07);-webkit-box-shadow:0 -4px 8px rgba(12,60,38,0.07);box-shadow:0 -4px 8px rgba(12,60,38,0.07)}.container{zoom:1;margin:0 auto;max-width:1400px;padding:0 4.16667%;min-width:768px}.container:before,.container:after{content:"";display:table}.container:after{clear:both}.mdzr-boxshadow .container.sidebar-right{-moz-box-shadow:-1200px 0 0 0px #fff;-webkit-box-shadow:-1200px 0 0 0px #fff;box-shadow:-1200px 0 0 0px #fff}[role="banner"]{zoom:1;background:#0C4B33;overflow:hidden;margin:0;padding:10px 0 6px;position:relative;z-index:0}[role="banner"]:before,[role="banner"]:after{content:"";display:table}[role="banner"]:after{clear:both}[role="banner"] .container{position:relative}[role="banner"] .meta,[role="banner"] .list-links dd,.list-links [role="banner"] dd{font-size:13px;font-size:1.3rem;color:#44B78B;font-weight:700;width:auto;float:left;margin:8px 0 0 10px;display:none}[role="banner"] .logo{font-size:40px;font-size:4rem;font-family:"Roboto", Corbel, Avenir, "Lucida Grande", "Lucida Sans", sans-serif;background:url(../img/logo-django.png) 0 0 no-repeat;color:#FFF;display:block;float:left;font-weight:700;margin:10px;overflow:hidden;text-decoration:none;text-indent:100%;width:104px;height:36px;margin-left:0}.mdzr-svg [role="banner"] .logo{background:url(../img/logo-django.svg) center center no-repeat}[role="banner"] .menu-button{font-size:20px;font-size:2rem;background:#0C4B33;-moz-border-radius:23px;-webkit-border-radius:23px;border-radius:23px;color:#fff;cursor:pointer;display:block;float:right;height:45px;line-height:48px;margin:4px 10px;text-align:center;text-decoration:none;width:45px;display:none}[role="banner"] .menu-button:active{color:#44B78B}[role="banner"] .menu-button span{border:0;clip:rect(0 0 0 0);height:1px;margin:-1px;overflow:hidden;padding:0;position:absolute;width:1px}[role="banner"] .menu-button.active{opacity:0.5}[role="banner"] .nav-menu-on{max-height:0;overflow:hidden;-moz-transition:all 0.3s ease-out;-o-transition:all 0.3s ease-out;-webkit-transition:all 0.3s ease-out;transition:all 0.3s ease-out;max-height:none;-moz-transition:none;-o-transition:none;-webkit-transition:none;transition:none}[role="banner"] .nav-menu-on.active{max-height:500px}[role="banner"] [role="navigation"]{background:#0C4B33;width:100%;width:auto;float:right}[role="banner"] [role="navigation"] ul{margin:10px 0 0;padding:0;margin:0}[role="banner"] [role="navigation"] li{font-family:"Roboto", Corbel, Avenir, "Lucida Grande", "Lucida Sans", sans-serif;font-size:13px;font-size:1.3rem;display:block;font-weight:700;line-height:16px;text-align:left;text-transform:uppercase;margin:0 10px;border-top:1px solid #106142;margin:0;border:0;float:left;text-align:left}[role="banner"] [role="navigation"] li.active a{color:#44B78B}[role="banner"] [role="navigation"] a{color:#fff;display:block;padding:20px 0px;text-decoration:none;padding:20px 10px}[role="banner"] [role="navigation"] a:active,[role="banner"] [role="navigation"] a:hover{color:#C9F0DD}[role="banner"] [role="navigation"] .nav-primary{position:absolute;right:0;top:45px}.header{font-family:"Roboto", Corbel, Avenir, "Lucida Grande", "Lucida Sans", sans-serif;background:#0C4B33;margin:0;padding:11px 0px 8px;position:relative}.header h1{margin:0.4em 0}.header p{font-family:Palatino, "Palatino Linotype", "Book Antiqua", "Hoefler Text", Georgia, "Lucida Bright", Cambria, Times, "Times New Roman", serif;font-size:18px;font-size:1.8rem;color:#fff;left:-9999px;line-height:1.5;padding:0 0 10px;position:absolute;top:0;max-width:660px;position:static}.copy-banner{background:#44B78B;padding:1px 10px;padding:1px 0}.copy-banner p,.copy-banner h1{font-family:"Roboto", Corbel, Avenir, "Lucida Grande", "Lucida Sans", sans-serif;font-size:24px;font-size:2.4rem;color:#C9F0DD;font-weight:300;line-height:1.3;padding:1px 0 6px;margin:.45em 0 .35em;font-size:32px;font-size:3.2rem;margin:.35em 0 .35em;color:#C9F0DD;padding:1px 0 6px}.copy-banner p em,.copy-banner h1 em{font-style:normal;color:white}.copy-banner p a,.copy-banner h1 a{font-weight:300;color:#C9F0DD}.copy-banner p a.cta,.copy-banner p .cta,.copy-banner h1 a.cta,.copy-banner h1 .cta{margin:0;font-size:18px;font-size:1.8rem}.copy-banner a.cta,.copy-banner .cta{margin:15px 0;padding:0.4em 1.5em 0.5em;background:#2B8C67;background:none;border:1px solid #C9F0DD;color:#C9F0DD;font-weight:400}.copy-banner a.cta:hover,.copy-banner .cta:hover{background:#309c72}.copy-banner a.cta em,.copy-banner .cta em{color:white}.copy-banner a.cta:hover,.copy-banner .cta:hover{background:#F1FFF7;color:#20AA76;border-color:#F1FFF7}.copy-banner a.cta:hover em,.copy-banner .cta:hover em{color:#20AA76}.copy-banner a.cta.white,.copy-banner .cta.white{background:#F1FFF7;color:#20AA76;font-weight:700;border:0}.copy-banner a.cta.white:hover,.copy-banner .cta.white:hover{background:#fff}.homepage .copy-banner{padding:50px 0;background:white;text-align:center;border-bottom:1px solid #CFE3DC}.homepage .copy-banner p{max-width:700px;margin-left:auto;margin-right:auto;margin:1em auto .5em;color:#0C3C26;font-size:36px;font-size:3.6rem}.homepage .copy-banner p.small{color:#798780;margin:2em auto 1em;font-size:14px;font-size:1.4rem}.homepage .copy-banner p em{color:#0C3C26}.homepage .copy-banner a.cta,.homepage .copy-banner .cta{display:inline-block;padding:1em 50px 1.1em;margin-bottom:40px;background:#44B78B;color:white;border:0;font-weight:700}.homepage .copy-banner a.cta:hover,.homepage .copy-banner .cta:hover{background:#51be95}.homepage .copy-banner a.cta:active,.homepage .copy-banner .cta:active{background:#41b085}.homepage .copy-banner .django-companies{max-width:750px;margin:0 auto;list-style:none;padding:0 0 0 30px}.homepage .copy-banner .django-companies li{width:144px;display:inline-block;text-indent:-1000px;overflow:hidden;margin:0}.homepage .copy-banner .django-companies li a{display:block;height:46px;background-position:center;background-repeat:no-repeat}.homepage .copy-banner .django-companies li a.company-mozilla{background-image:url("../img/company-mozilla.png");background-position:center 7px}.mdzr-svg .homepage .copy-banner .django-companies li a.company-mozilla{background-image:url("../img/company-mozilla.svg")}.homepage .copy-banner .django-companies li a.company-pinterest{background-image:url("../img/company-pinterest.png");background-position:center 7px}.mdzr-svg .homepage .copy-banner .django-companies li a.company-pinterest{background-image:url("../img/company-pinterest.svg")}.homepage .copy-banner .django-companies li a.company-theguardian{background-image:url("../img/company-theguardian.png");background-position:center 14px}.mdzr-svg .homepage .copy-banner .django-companies li a.company-theguardian{background-image:url("../img/company-theguardian.svg")}.homepage .copy-banner .django-companies li a.company-instagram{background-image:url("../img/company-instagram.png");background-position:center 10px}.mdzr-svg .homepage .copy-banner .django-companies li a.company-instagram{background-image:url("../img/company-instagram.svg")}.homepage .copy-banner .django-companies li a.company-rdio{background-image:url("../img/company-rdio.png");background-position:15px 6px}.mdzr-svg .homepage .copy-banner .django-companies li a.company-rdio{background-image:url("../img/company-rdio.svg")}[role="alert"]{clear:both;background:#F1FFF7;position:relative;-moz-box-shadow:0 -2px 8px 0 rgba(0,0,0,0.05);-webkit-box-shadow:0 -2px 8px 0 rgba(0,0,0,0.05);box-shadow:0 -2px 8px 0 rgba(0,0,0,0.05)}[role="alert"] a{color:#20AA76;text-decoration:none}[role="alert"] a:visited{color:#20AA76}[role="alert"] a:hover,[role="alert"] a:active,[role="alert"] a:focus{color:#25c488;text-decoration:none}[role="alert"] a.link-readmore{margin:0}[role="alert"] dl{margin:0 10px;padding:25px 0;zoom:1}[role="alert"] dl:before,[role="alert"] dl:after{content:"";display:table}[role="alert"] dl:after{clear:both}[role="alert"] dl dt i.icon{color:#20AA76;margin-right:8px}[role="alert"] dl dd{float:left;width:60%;margin-top:0;font-family:"Roboto", Corbel, Avenir, "Lucida Grande", "Lucida Sans", sans-serif}[role="alert"] dl .link-readmore{margin-left:10px}[role="alert"] dt{float:left;width:31%;padding-right:2%}[role="alert"] dl{margin:0}[role="contentinfo"]{zoom:1;font-family:"Roboto", Corbel, Avenir, "Lucida Grande", "Lucida Sans", sans-serif;position:relative;background:#44B78B;clear:both;margin-top:0px}[role="contentinfo"]:before,[role="contentinfo"]:after{content:"";display:table}[role="contentinfo"]:after{clear:both}[role="contentinfo"] .container{overflow:hidden}[role="contentinfo"] .subfooter{zoom:1;padding:0 10px;padding:0}[role="contentinfo"] .subfooter:before,[role="contentinfo"] .subfooter:after{content:"";display:table}[role="contentinfo"] .subfooter:after{clear:both}[role="contentinfo"] .subfooter .col{float:left;margin-bottom:-999px;padding:0 3% 999px 0;width:30%}[role="contentinfo"] .subfooter .col:first-child h2{border-top:0}[role="contentinfo"] .subfooter .col:first-child{margin-left:0;padding-left:0}[role="contentinfo"] .subfooter .col.last-child{margin-right:0;padding-right:0}[role="contentinfo"] h2{font-size:16px;font-size:1.6rem;border-top:1px solid #CFE3DC;color:#fff;font-weight:700;margin-top:20px;padding:30px 0 10px;border:none;margin-top:0}[role="contentinfo"] ul{font-size:14px;font-size:1.4rem;font-weight:400;list-style:none;margin:15px 0 0 0;padding:0 0 30px}[role="contentinfo"] ul li{margin:10px 0 0;padding:0}[role="contentinfo"] ul a{color:#F1FFF7;text-decoration:none}[role="contentinfo"] ul a:hover,[role="contentinfo"] ul a:active,[role="contentinfo"] ul a:focus{text-decoration:underline}[role="contentinfo"] .footer{background:#0C4B33;margin-top:20px;padding:10px 0 30px;color:#2B8C67}[role="contentinfo"] .footer .footer-logo{float:left;width:33%}[role="contentinfo"] .footer .logo{margin-right:0;margin-top:28px}.mdzr-svg [role="contentinfo"] .footer .logo{background-position:left center;margin-right:0;margin-top:20px}[role="contentinfo"] .logo{font-size:40px;font-size:4rem;font-family:"Roboto", Corbel, Avenir, "Lucida Grande", "Lucida Sans", sans-serif;background:url(../img/logo-django.png) 0 0 no-repeat;color:#0C4B33;display:block;font-weight:700;height:50px;margin:10px;overflow:hidden;text-decoration:none;text-indent:100%;width:142px;float:left;margin:20px 90px 0 0}.mdzr-svg [role="contentinfo"] .logo{background:url(../img/logo-django.svg) center center no-repeat;height:39px;width:109px}[role="contentinfo"] .thanks{font-size:12px;font-size:1.2rem;color:#2B8C67;margin:0;padding:0;border:none}[role="contentinfo"] .thanks li{zoom:1;margin:0;padding:17px 10px 11px;display:block;clear:both;background:none;float:left;clear:none;padding:0 3% 0 0;width:30%}[role="contentinfo"] .thanks li:before,[role="contentinfo"] .thanks li:after{content:"";display:table}[role="contentinfo"] .thanks li:after{clear:both}[role="contentinfo"] .thanks li.design span.ampersand,[role="contentinfo"] .thanks li.design a{display:inline-block;vertical-align:top}[role="contentinfo"] .thanks li.design span.ampersand.threespot,[role="contentinfo"] .thanks li.design a.threespot{clear:both}[role="contentinfo"] .thanks li.design span.ampersand.ampersand,[role="contentinfo"] .thanks li.design a.ampersand{position:relative;top:6px;margin:0 6px;line-height:36px}[role="contentinfo"] .thanks li span.ampersand{line-height:24px}[role="contentinfo"] .thanks span{display:block;height:24px;line-height:36px;padding-right:12px;white-space:nowrap}[role="contentinfo"] .thanks a{display:block;height:33px;overflow:hidden;text-indent:-200px;width:94px;clear:both;margin-top:5px}[role="contentinfo"] .thanks a.rackspace{background:url(../img/logo-rackspace.png) no-repeat left center}.mdzr-svg [role="contentinfo"] .thanks a.rackspace{background:url(../img/logo-rackspace.svg) no-repeat left center}[role="contentinfo"] .thanks a.threespot{background:url(../img/logo-threespot.png) no-repeat left center}.mdzr-svg [role="contentinfo"] .thanks a.threespot{background:url(../img/logo-threespot.svg) no-repeat left center}[role="contentinfo"] .thanks a.andrevv{background:url(../img/logo-andrevv.png) no-repeat left center}.mdzr-svg [role="contentinfo"] .thanks a.andrevv{background:url(../img/logo-andrevv.svg) no-repeat left center}[role="contentinfo"] .copyright{font-size:12px;font-size:1.2rem;clear:both;margin:20px 0 0 10px;max-width:80%;padding-top:30px;margin:0}[role="contentinfo"] .copyright a{color:#2B8C67}.backtotop{color:#20AA76;text-decoration:none;font-family:"Roboto", Corbel, Avenir, "Lucida Grande", "Lucida Sans", sans-serif;font-size:14px;font-size:1.4rem;display:block;font-weight:700;margin:10px 0;padding:10px 0;text-align:center;text-transform:uppercase;display:none}.backtotop:visited{color:#20AA76}.backtotop:hover,.backtotop:active,.backtotop:focus{color:#25c488;text-decoration:none}.cta,a.cta{font-family:"Roboto", Corbel, Avenir, "Lucida Grande", "Lucida Sans", sans-serif;font-weight:700;-webkit-appearance:none;-moz-appearance:none;background:#44B78B;border:none;-moz-border-radius:5px;-webkit-border-radius:5px;border-radius:5px;color:#fff;display:block;-webkit-font-smoothing:subpixel-antialiased;-moz-osx-font-smoothing:auto;margin:30px auto 0;padding:1em 1.5em;text-align:center;text-decoration:none;margin:20px auto;max-width:400px}.cta em,a.cta em{color:#C9F0DD;font-style:normal}.cta:hover,.cta:focus,a.cta:hover,a.cta:focus{background:#51be95}.cta:active,a.cta:active{background:#41b085}[role="complementary"] .cta,[role="complementary"] a.cta{font-size:16px;font-size:1.6rem}[role="complementary"] .cta+.link-readmore,[role="complementary"] a.cta+.link-readmore{display:block;text-align:center;margin-top:-10px}.cta.outline,a.cta.outline{background:none;border:1px solid #CFE3DC;color:#859D94;font-weight:400}.cta.outline em,a.cta.outline em{color:#0C4B33}.cta.outline:hover,a.cta.outline:hover{border-color:#20AA76;color:#20AA76}.cta.outline:hover em,a.cta.outline:hover em{color:#20AA76}.cta.outline:active,a.cta.outline:active{border-color:#44B78B;color:#44B78B}.cta.outline:active em,a.cta.outline:active em{color:#44B78B}.link-green{color:#20AA76;text-decoration:none}.link-green:visited{color:#20AA76}.link-green:hover,.link-green:active,.link-green:focus{color:#25c488;text-decoration:none}.link-readmore{color:#20AA76;text-decoration:none;font-family:"Roboto", Corbel, Avenir, "Lucida Grande", "Lucida Sans", sans-serif;font-size:14px;font-size:1.4rem;display:inline-block;margin:10px 0;display:inline;zoom:1;font-weight:700;text-transform:uppercase}.link-readmore:visited{color:#20AA76}.link-readmore:hover,.link-readmore:active,.link-readmore:focus{color:#25c488;text-decoration:none}.link-readmore:after{content:" ›";font-size:1.2em}.link-readmore.back-link:after{content:""}.link-readmore.back-link:before{content:"‹ ";font-size:1.2em}[role="complementary"] .link-readmore{font-size:12px;font-size:1.2rem}.meta,.list-links dd{font-family:"Roboto", Corbel, Avenir, "Lucida Grande", "Lucida Sans", sans-serif;font-size:14px;font-size:1.4rem;display:block;line-height:1.3;margin:25px 0 20px}.meta a,.list-links dd a{color:#20AA76;text-decoration:none}.meta a:visited,.list-links dd a:visited{color:#20AA76}.meta a:hover,.list-links dd a:hover,.meta a:active,.list-links dd a:active,.meta a:focus,.list-links dd a:focus{color:#25c488;text-decoration:none}.layout-2col{margin:20px 0;zoom:1;margin:0}.layout-2col:before,.layout-2col:after{content:"";display:table}.layout-2col:after{clear:both}.layout-2col .col{float:left;width:46%;margin:0 4%}.layout-2col .col:first-child{margin-left:0}.layout-2col .col.last-child{margin-right:0}.layout-2col .one-third{width:29%}.layout-2col .two-third{width:62%}.blue{color:#20AA76}.label{font-family:"Roboto", Corbel, Avenir, "Lucida Grande", "Lucida Sans", sans-serif;font-size:16px;font-size:1.6rem;color:#cacfcc;display:block;font-weight:700;margin:20px 0 10px;text-transform:uppercase}.label.form-controls{font-size:14px;font-size:1.4rem;display:block;margin:0;position:relative;text-align:left}.label.form-controls span{cursor:pointer}.label.form-controls span:hover,.label.form-controls span:active,.label.form-controls span:focus{color:#afb7b3}.callout-right{float:right;margin:26px 0 0 35px;width:33%}.callout-right.two-thirds{width:60%}.callout-right img{display:block;max-width:100%}.callout-left{float:left;margin:26px 35px 0 0;width:33%}.callout-left.two-thirds{width:60%}.callout-left img{display:block;max-width:100%}.codedump{background:#f8f8f8;border:1px solid #CFE3DC;padding:10px;-moz-border-radius:4px;-webkit-border-radius:4px;border-radius:4px;font-family:"Roboto", Corbel, Avenir, "Lucida Grande", "Lucida Sans", sans-serif;font-size:14px;font-size:1.4rem;line-height:1.6em}.list-events{font-family:"Roboto", Corbel, Avenir, "Lucida Grande", "Lucida Sans", sans-serif;list-style:none;margin:0;padding:0}.list-events li{font-size:18px;font-size:1.8rem;border-top:1px solid #CFE3DC;display:block;line-height:1.3;margin:0;padding:20px 0 0 30px;position:relative}.list-events li i{font-size:16px;font-size:1.6rem;color:#93D7B7;display:block;left:0;line-height:20px;height:30px;position:absolute;text-align:center;top:20px;width:24px}.list-events li:first-child{border-top:0;padding-top:0}.list-events li:first-child i{top:0px}.list-events .meta,.list-events .list-links dd,.list-links .list-events dd{font-family:"Roboto", Corbel, Avenir, "Lucida Grande", "Lucida Sans", sans-serif;font-size:14px;font-size:1.4rem;display:block;margin-top:10px}.list-events a{color:#20AA76;text-decoration:none}.list-events a:visited{color:#20AA76}.list-events a:hover,.list-events a:active,.list-events a:focus{color:#25c488;text-decoration:none}.list-tags{font-family:"Roboto", Corbel, Avenir, "Lucida Grande", "Lucida Sans", sans-serif;font-size:12px;font-size:1.2rem;font-weight:700;list-style:none;margin:0;padding:0;text-transform:uppercase}.list-tags li{margin-top:10px;display:block;line-height:28px}.list-tags a{background:#93D7B7;color:#F1FFF7;display:inline-block;display:block;line-height:1.2;margin:0;padding:8px 10px 5px;text-decoration:none}.list-tags a:hover,.list-tags a:active,.list-tags a:focus{background-color:#44B78B;color:#fff}.list-news{list-style:none;margin:0;padding:0}.list-news h2{font-weight:400;margin-bottom:5px}.list-news li{border-top:1px solid #CFE3DC;margin-top:35px;padding-top:10px}.list-news li:first-child{border:none;margin-top:0;padding-top:0}.list-news .meta,.list-news .list-links dd,.list-links .list-news dd{margin-top:10px;color:#859D94}.list-news .meta a:link,.list-news .list-links dd a:link,.list-links .list-news dd a:link{color:#798780;text-decoration:underline}.list-case-study{zoom:1;list-style:none;margin:0;padding:0 0 10px}.list-case-study:before,.list-case-study:after{content:"";display:table}.list-case-study:after{clear:both}.list-case-study p{font-size:14px;font-size:1.4rem;margin:10px 0 5px}.list-case-study li{border-top:1px solid #CFE3DC;margin-top:20px;padding-top:20px}.list-case-study li>a{font-family:"Roboto", Corbel, Avenir, "Lucida Grande", "Lucida Sans", sans-serif;color:#20AA76;text-decoration:none;font-size:12px;font-size:1.2rem;font-weight:700;margin-top:10px;text-transform:uppercase}.list-case-study li>a:visited{color:#20AA76}.list-case-study li>a:hover,.list-case-study li>a:active,.list-case-study li>a:focus{color:#25c488;text-decoration:none}.list-case-study li>a:after{content:" ›";font-size:1.2em}.list-case-study h3{margin:10px 0 20px;padding:0}.list-case-study h3.logo{text-indent:-1000%;overflow:hidden}.list-case-study [title="Knight Foundation"]{background:url(../img/logo-knight.png) no-repeat bottom left;height:25px;padding-top:7px;width:190px}.mdzr-svg .list-case-study [title="Knight Foundation"]{background:url(../img/logo-knight.svg) no-repeat bottom left}.list-case-study [title="Mozilla"]{background:url(../img/logo-mozilla.png) no-repeat bottom left;height:32px;width:120px}.mdzr-svg .list-case-study [title="Mozilla"]{background:url(../img/logo-mozilla.svg) no-repeat bottom left}.list-case-study [title="Disqus"]{background:url(../img/logo-disqus.png) no-repeat bottom left;height:28px;padding-top:4px;width:140px}.mdzr-svg .list-case-study [title="Disqus"]{background:url(../img/logo-disqus.svg) no-repeat bottom left}.list-case-study.single-col li{margin-top:0;margin-bottom:30px;width:auto}.list-case-study.single-col li p{font-size:18px;font-size:1.8rem;margin-right:40px}.list-case-study.single-col li h3{margin-top:20px}.case-study-logo{max-width:50%;max-height:70px;height:auto;margin:40px 0 0}.list-link-soup{font-family:"Roboto", Corbel, Avenir, "Lucida Grande", "Lucida Sans", sans-serif;zoom:1;font-size:16px;font-size:1.6rem;border-top:1px solid #CFE3DC;list-style:none;margin:20px 0 0;padding:20px 0 10px}.list-link-soup:before,.list-link-soup:after{content:"";display:table}.list-link-soup:after{clear:both}.list-link-soup li{float:left;margin:10px 5% 0 0;width:45%;margin-right:3%;width:30%}.list-link-soup a{color:#20AA76;text-decoration:none}.list-link-soup a:visited{color:#20AA76}.list-link-soup a:hover,.list-link-soup a:active,.list-link-soup a:focus{color:#25c488;text-decoration:none}[role="complementary"] .list-link-soup li{float:none}h2+.list-link-soup{border-top:0}.list-features{margin:50px 0 40px;padding-bottom:40px}.list-features dt{font-size:24px;font-size:2.4rem;border-top:1px solid #CFE3DC;padding-top:25px}.list-features i{color:#F1FFF7;margin-right:10px;width:40px;height:40px;-moz-border-radius:25px;-webkit-border-radius:25px;border-radius:25px;background:#20AA76;line-height:1.68em;display:inline-block;text-align:center}.list-features i.icon-briefcase{line-height:1.7em}.list-features i.icon-dashboard{line-height:1.5em}.list-features dt{margin-top:60px;padding:60px 0 0 245px;position:relative}.list-features dt:first-child{margin-top:20px}.list-features dt.even{padding-left:0;padding-right:245px}.list-features dt.even i{left:auto !important;right:0}.list-features dd{padding-left:245px;min-height:140px}.list-features dd.even{padding-left:0;padding-right:245px}.list-features i{font-size:120px;font-size:12rem;display:block;height:200px;left:0;position:absolute;text-align:center;top:60px;width:200px;margin-right:0}.mdzr-borderradius .list-features i{background:#44B78B;-moz-border-radius:100px;-webkit-border-radius:100px;border-radius:100px;color:#fff}.mdzr-svg .list-features i{background:url(../img/bg-features.svg) no-repeat center center}.mdzr-svg .list-features i.icon-bolt{background-position:-150px -269px}.mdzr-svg .list-features i.icon-briefcase{background-position:-354px -7px}.mdzr-svg .list-features i.icon-lock{background-position:-36px -96px}.mdzr-svg .list-features i.icon-dashboard{background-position:-270px -9px}.mdzr-svg .list-features i.icon-cogs{background-position:-334px -12px}.mdzr-svg.mdzr-borderradius.mdzr-cssanimations .list-features i{-moz-transition:all 0.3s ease-out;-o-transition:all 0.3s ease-out;-webkit-transition:all 0.3s ease-out;transition:all 0.3s ease-out;-moz-transform:rotate(0.5turn);-ms-transform:rotate(0.5turn);-webkit-transform:rotate(0.5turn);transform:rotate(0.5turn)}.mdzr-svg.mdzr-borderradius.mdzr-cssanimations .list-features i.inview{-moz-transform:rotate(0turn);-ms-transform:rotate(0turn);-webkit-transform:rotate(0turn);transform:rotate(0turn)}.mdzr-svg.mdzr-borderradius.mdzr-cssanimations .list-features i.icon-bolt{background-position:40px -369px}.mdzr-svg.mdzr-borderradius.mdzr-cssanimations .list-features i.icon-bolt.inview{background-position:-150px -269px}.mdzr-svg.mdzr-borderradius.mdzr-cssanimations .list-features i.icon-briefcase{background-position:-494px 207px}.mdzr-svg.mdzr-borderradius.mdzr-cssanimations .list-features i.icon-briefcase.inview{background-position:-354px -7px}.mdzr-svg.mdzr-borderradius.mdzr-cssanimations .list-features i.icon-lock{background-position:144px -206px}.mdzr-svg.mdzr-borderradius.mdzr-cssanimations .list-features i.icon-lock.inview{background-position:-36px -96px}.mdzr-svg.mdzr-borderradius.mdzr-cssanimations .list-features i.icon-dashboard{background-position:-360px 201px}.mdzr-svg.mdzr-borderradius.mdzr-cssanimations .list-features i.icon-dashboard.inview{background-position:-270px -9px}.mdzr-svg.mdzr-borderradius.mdzr-cssanimations .list-features i.icon-cogs{background-position:-500px -180px}.mdzr-svg.mdzr-borderradius.mdzr-cssanimations .list-features i.icon-cogs.inview{background-position:-334px -12px}.list-features i :-o-prefocus,.list-features i{background:#44B78B !important;-moz-transition:none !important;-o-transition:none !important;-webkit-transition:none !important;transition:none !important;-moz-transform:none !important;-ms-transform:none !important;-webkit-transform:none !important;transform:none !important}.homepage .list-features{padding-bottom:0}.homepage .list-features dl{padding-top:0}.homepage .list-features i{color:#F1FFF7;margin-right:10px;width:40px;height:40px;top:10px;-moz-border-radius:20px;-webkit-border-radius:20px;border-radius:20px;background:#20AA76;display:inline-block;text-align:center;font-size:24px;font-size:2.4rem}.mdzr-svg.mdzr-borderradius.mdzr-cssanimations .homepage .list-features i{-moz-transition:none;-o-transition:none;-webkit-transition:none;transition:none;-moz-transform:rotate(0);-ms-transform:rotate(0);-webkit-transform:rotate(0);transform:rotate(0)}.homepage .list-features dt{padding:20px 0 0px 110px;font-size:18px;font-size:1.8rem;border-top:0;margin-top:0}.homepage .list-features dd{padding:0 60px 20px 110px;min-height:0}.homepage .list-features dd p{margin-top:0;font-size:18px;font-size:1.8rem}.homepage .list-features i{margin-right:10px;width:80px;height:80px;top:20px;-moz-border-radius:40px;-webkit-border-radius:40px;border-radius:40px;font-size:46px;font-size:4.6rem}.list-collapsing-header{float:left}.section .list-collapsing-header h2{margin:40px 0 20px}.list-collapsing-header+.form-controls.label{margin:50px 0 0;text-align:right}.form-controls.label{float:right}.list-collapsing{border-bottom:1px solid #CFE3DC;list-style:none;margin:30px 0;padding:0;clear:both}.list-collapsing.active>li{border-top:1px solid #CFE3DC;margin:0;padding:0}.list-collapsing.active>li.active h2 .collapsing-icon:before{content:"\f068"}.list-collapsing.active h2{font-size:18px;font-size:1.8rem;cursor:pointer;margin:0;padding:18px 40px 18px 0;position:relative}.list-collapsing.active h2:hover,.list-collapsing.active h2:focus,.list-collapsing.active h2:active{color:#1d915c;outline:none}.list-collapsing.active h2 .collapsing-icon{position:absolute;right:0;top:24px}.list-collapsing.active h2 .collapsing-icon:before{content:"\f067"}.list-collapsing.active h2.bullet-icon{padding-left:1.5em}.list-collapsing.active h2.bullet-icon>i:first-child{position:absolute;top:24px;left:0}.list-collapsing.active .collapsing-content{overflow:hidden;max-height:0px;-moz-transition:all 0.5s ease-out;-o-transition:all 0.5s ease-out;-webkit-transition:all 0.5s ease-out;transition:all 0.5s ease-out}.list-collapsing.active li.active .collapsing-content{max-height:1000px;overflow:auto}.list-image{list-style:none;margin:0;padding:0}.list-image li{border-top:1px solid #CFE3DC;margin-top:20px;padding-top:40px;zoom:1;margin-top:20px;padding-top:40px}.list-image li:before,.list-image li:after{content:"";display:table}.list-image li:after{clear:both}.list-image li:first-child{border:none;padding-top:0}.list-image a{text-decoration:none}.list-image a:hover,.list-image a:active,.list-image a:focus{color:#798780}.list-image a.link-readmore{margin:0}.list-image img{display:block;margin:0 auto 25px;max-width:100%}.list-image img{float:left;margin:0 40px 0 0;max-width:200px;max-height:200px}.list-image h2,.list-image h3,.list-image h4,.list-image p{padding-left:240px}.list-image h3{margin-top:10px}.layout-secondary .list-image img,[role="complementary"] .list-image img{float:left;max-width:40%;max-height:120px;margin:0 20px 10px 0}.layout-secondary .list-image h2,.layout-secondary .list-image h3,.layout-secondary .list-image h4,.layout-secondary .list-image p,[role="complementary"] .list-image h2,[role="complementary"] .list-image h3,[role="complementary"] .list-image h4,[role="complementary"] .list-image p{padding-left:0px;border:0}.layout-secondary .list-image h3,[role="complementary"] .list-image h3{font-size:14px;font-size:1.4rem;margin-top:0;margin-bottom:1em;padding-bottom:0;font-family:"Roboto", Corbel, Avenir, "Lucida Grande", "Lucida Sans", sans-serif;font-weight:700}#s-django-documentation,#s-feed{font-family:"Roboto", Corbel, Avenir, "Lucida Grande", "Lucida Sans", sans-serif}.list-outline{font-size:16px;font-size:1.6rem;line-height:1.3;list-style:none;margin:0;padding:0}.list-outline a{text-decoration:none}.list-outline>li>ul>li{margin-top:12px}.list-outline>li>ul>li:first-child{margin-top:6px}.list-outline>li>a{text-transform:uppercase;font-weight:700;color:#20AA76;text-decoration:none}.list-outline>li>a:visited{color:#20AA76}.list-outline>li>a:hover,.list-outline>li>a:active,.list-outline>li>a:focus{color:#25c488;text-decoration:none}.list-outline>li>ul{list-style:none;font-size:12px;font-size:1.2rem;padding:5px 0 0 10px}.list-outline>li>ul>li>a{font-weight:700;text-transform:uppercase;color:#20AA76;text-decoration:none}.list-outline>li>ul>li>a:visited{color:#20AA76}.list-outline>li>ul>li>a:hover,.list-outline>li>ul>li>a:active,.list-outline>li>ul>li>a:focus{color:#25c488;text-decoration:none}.list-outline>li>ul>li ul{font-size:14px;font-size:1.4rem;padding:0 0 0 20px}.section h2{margin:50px 0 30px}.section h3{margin:40px 0 20px}.headerlink{opacity:0;padding-left:10px;font-size:0.8em;position:relative;top:-0.17em;font-weight:700;text-decoration:none;-moz-transition:opacity 200ms ease-in-out;-o-transition:opacity 200ms ease-in-out;-webkit-transition:opacity 200ms ease-in-out;transition:opacity 200ms ease-in-out}h1:hover>.headerlink,h2:hover>.headerlink,h3:hover>.headerlink,h4:hover>.headerlink,h5:hover>.headerlink,h6:hover>.headerlink,dl:hover>.headerlink,dt:hover>.headerlink{opacity:1}.note,.admonition,.help-block{background:#F1FFF7;padding:15px 20px 15px 70px;border:1px solid #C9F0DD;-moz-border-radius:4px;-webkit-border-radius:4px;border-radius:4px;margin:25px 0;position:relative}.note h1,.note h2,.note h3,.note h4,.admonition h1,.admonition h2,.admonition h3,.admonition h4,.help-block h1,.help-block h2,.help-block h3,.help-block h4{margin-top:20px}.note p,.admonition p,.help-block p{margin:0.8em 0}.note .first,.admonition .first,.help-block .first{margin-top:0}.note .admonition-title,.admonition .admonition-title,.help-block .admonition-title{font-weight:bold}.note .admonition-title::before,.admonition .admonition-title::before,.help-block .admonition-title::before{display:inline-block;font:normal normal normal 14px/1 FontAwesome;font-size:inherit;text-rendering:auto;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;position:absolute;top:20px;left:20px;font-size:30px;width:34px;text-align:center;content:"";opacity:0.5}.note.warning,.admonition.warning,.help-block.warning{background-color:#FFFDF1;border-color:#F5F1C7}.note.warning .admonition-title::before,.admonition.warning .admonition-title::before,.help-block.warning .admonition-title::before{color:#E9BD46;content:""}.note.admonition-philosophy .admonition-title::before,.admonition.admonition-philosophy .admonition-title::before,.help-block.admonition-philosophy .admonition-title::before{content:""}.note.admonition-behind-the-scenes .admonition-title::before,.admonition.admonition-behind-the-scenes .admonition-title::before,.help-block.admonition-behind-the-scenes .admonition-title::before{content:""}.note .last,.note .highlight,.admonition .last,.admonition .highlight,.help-block .last,.help-block .highlight{margin-bottom:0px}.browse-horizontal{font-family:"Roboto", Corbel, Avenir, "Lucida Grande", "Lucida Sans", sans-serif;font-size:14px;font-size:1.4rem;zoom:1;font-weight:700;border-top:1px solid #CFE3DC;border-bottom:1px solid #CFE3DC;padding:20px 0;margin-top:2em}.browse-horizontal:before,.browse-horizontal:after{content:"";display:table}.browse-horizontal:after{clear:both}.browse-horizontal .left{float:left}.browse-horizontal .left .icon{margin-right:4px;font-size:12px;font-size:1.2rem}.browse-horizontal .right{float:right}.browse-horizontal .right .icon{margin-left:4px;font-size:12px;font-size:1.2rem}.browse-horizontal a{text-decoration:none}#doc-versions{position:fixed;right:15px;bottom:15px;margin:0;padding:0;z-index:1;list-style:none}#doc-versions .icon{margin-right:4px}#doc-versions li{display:none;background:#F1FFF7;margin:0 3px;font-family:"Roboto", Corbel, Avenir, "Lucida Grande", "Lucida Sans", sans-serif;color:#0C3C26;font-size:12px;font-size:1.2rem}#doc-versions li.current{display:inline-block;padding:8px 15px;border:1px solid #CFE3DC;-moz-border-radius:4px;-webkit-border-radius:4px;border-radius:4px}#doc-versions li a{display:inline-block;color:#44B78B;text-decoration:none;font-weight:700;padding:8px 15px;border:1px solid #CFE3DC;-moz-border-radius:4px;-webkit-border-radius:4px;border-radius:4px}#doc-versions li a:hover{color:#20AA76;border:1px solid #93D7B7}#doc-versions:hover li,#doc-versions .hover-on li{display:inline-block}#dev-warning,#outdated-warning{position:absolute;top:0;width:100%;padding:8px 20px 8px;-moz-box-sizing:border-box;-webkit-box-sizing:border-box;box-sizing:border-box;background-image:-webkit-linear-gradient(-45deg, rgba(0,0,0,0.04) 25%, transparent 25%, transparent 50%, rgba(0,0,0,0.04) 50%, rgba(0,0,0,0.04) 75%, transparent 75%, transparent);background-image:-moz-linear-gradient(-45deg, rgba(0,0,0,0.04) 25%, transparent 25%, transparent 50%, rgba(0,0,0,0.04) 50%, rgba(0,0,0,0.04) 75%, transparent 75%, transparent);background-image:linear-gradient(135deg, rgba(0,0,0,0.04) 25%,rgba(0,0,0,0) 25%,rgba(0,0,0,0) 50%,rgba(0,0,0,0.04) 50%,rgba(0,0,0,0.04) 75%,rgba(0,0,0,0) 75%,rgba(0,0,0,0));font-family:"Roboto", Corbel, Avenir, "Lucida Grande", "Lucida Sans", sans-serif;font-size:14px;text-align:center;background-color:#ffe761;position:fixed;min-width:768px}#outdated-warning{background-color:#ffbaba;color:#6A0E0E}#s-getting-help{display:none}#docs-content{font-family:"Roboto", Corbel, Avenir, "Lucida Grande", "Lucida Sans", sans-serif;font-size:14px;font-size:1.4rem;line-height:1.5em}#docs-content h1,#docs-content h2,#docs-content h3,#docs-content h4,#docs-content h5,#docs-content h6{margin:0.6em 0;line-height:1.1em}#docs-content .section{padding:10px 0 20px}#docs-content img{display:block;max-width:100%}#docs-content a.reference{color:#6A0E0E;text-decoration:none;border-bottom:1px dotted #798780}#docs-content a.reference:visited{border-color:#971414}#docs-content a.reference:active,#docs-content a.reference:focus,#docs-content a.reference:hover{background:#F1FFF7;color:#BA2121}#docs-content a.reference em{font-style:normal}.versionadded,.versionchanged,.deprecated,.versionmodified{font-family:"Roboto", Corbel, Avenir, "Lucida Grande", "Lucida Sans", sans-serif;font-weight:bold;margin-bottom:20px;padding:10px 13px;border:1px solid #C9F0DD;-moz-border-radius:4px;-webkit-border-radius:4px;border-radius:4px}.versionadded p,.versionchanged p,.deprecated p,.versionmodified p{font-weight:normal;margin-top:0.3em}.versionadded p:last-child,.versionchanged p:last-child,.deprecated p:last-child,.versionmodified p:last-child{margin-bottom:0}.deprecated .versionadded,.deprecated .versionchanged,.deprecated .deprecated,.deprecated .versionmodified{border:none;padding:0;margin-bottom:0;display:block}.deprecated p{font-weight:normal;margin-top:0}.deprecated p:last-child{margin-bottom:0}dl.function dt,dl.class dt,dl.method dt,dl.attribute dt{font-weight:700}dl.function dd,dl.class dd,dl.method dd,dl.attribute dd{padding-left:1.4em}table.docutils td,table.docutils th{border-bottom:1px solid #CFE3DC}#search-results span.highlighted{font-weight:700;color:#0C3C26}.list-links{font-family:"Roboto", Corbel, Avenir, "Lucida Grande", "Lucida Sans", sans-serif;list-style:none;margin:0;padding:10px 0 0}.list-links a{color:#20AA76;text-decoration:none}.list-links a:visited{color:#20AA76}.list-links a:hover,.list-links a:active,.list-links a:focus{color:#25c488;text-decoration:none}.list-links dt,.list-links li{font-size:16px;font-size:1.6rem;margin-top:15px;font-weight:400}.list-links dt:first-child,.list-links li:first-child{margin-top:0}.list-links dd{margin-top:0;margin-bottom:30px}[role="complementary"] .list-links{padding:0}[role="complementary"] .list-links dt,[role="complementary"] .list-links li{font-size:16px;font-size:1.6rem;border-top:1px solid #CFE3DC;margin-top:0;padding-top:20px}[role="complementary"] .list-links dt:first-child,[role="complementary"] .list-links li:first-child{border:none;padding-top:0}[role="complementary"] .list-links li{padding:14px 0 10px}[role="complementary"] .list-links dd{font-size:14px;font-size:1.4rem;margin-bottom:16px}.list-links+h2{margin-top:34px}.list-links-small{padding-left:0;list-style:none}.list-links-small a{color:#20AA76;text-decoration:none;text-decoration:none}.list-links-small a:visited{color:#20AA76}.list-links-small a:hover,.list-links-small a:active,.list-links-small a:focus{color:#25c488;text-decoration:none}.list-links-small li>a:before,.list-links-small dt>a:before{font-family:FontAwesome;font-weight:normal;font-style:normal;float:left;width:23px;height:20px}.list-links-small dt{font-weight:400}.list-links-small dd{color:#798780;padding-top:2px}.list-links-small.docs-list{list-style:none}.list-links-small.docs-list li>a:before,.list-links-small.docs-list dt>a:before{content:"\f0f6"}.list-links-small.docs-list dd{padding-left:24px}.list-links-small.news-list{list-style:none}.list-links-small.news-list li>a:before,.list-links-small.news-list dt>a:before{content:"\f0a1"}.list-links-small.news-list dt.event>a:before{content:"\f133"}.list-links-small.news-list dd{padding-left:24px}.list-links-small.resource-list{list-style:none}.list-links-small.resource-list li>a:before,.list-links-small.resource-list dt>a:before{content:"\f0c1"}.list-links-small.resource-list dd{padding-left:24px}.list-links-small.rss-list{list-style:none}.list-links-small.rss-list li>a:before,.list-links-small.rss-list dt>a:before{content:"\f09e"}.list-links-small.rss-list dd{padding-left:24px}form{font-family:"Roboto", Corbel, Avenir, "Lucida Grande", "Lucida Sans", sans-serif;font-size:16px;font-size:1.6rem}form input[type="search"],form input[type="text"],form input[type="email"],form input[type="password"],form input[type="url"],form textarea{font-family:"Roboto", Corbel, Avenir, "Lucida Grande", "Lucida Sans", sans-serif;font-size:16px;font-size:1.6rem;-webkit-appearance:none;-moz-appearance:none;background:#fff;border:1px solid #CFE3DC;-moz-border-radius:4px;-webkit-border-radius:4px;border-radius:4px;cursor:auto;display:block;font-weight:400;height:30px;margin:10px 0px;padding:6px 14% 8px 10px;text-indent:0;vertical-align:middle;width:82%;padding:6px 18% 8px 10px;width:80%}form input[type="search"]::-ms-clear,form input[type="text"]::-ms-clear,form input[type="email"]::-ms-clear,form input[type="password"]::-ms-clear,form input[type="url"]::-ms-clear,form textarea::-ms-clear{display:none}form input[type="search"]:active,form input[type="search"]:focus,form input[type="text"]:active,form input[type="text"]:focus,form input[type="email"]:active,form input[type="email"]:focus,form input[type="password"]:active,form input[type="password"]:focus,form input[type="url"]:active,form input[type="url"]:focus,form textarea:active,form textarea:focus{outline:none;border-color:#20AA76}form textarea{height:auto}form input[type=checkbox],form input[type=radio]{margin-right:6px}form select{border:1px solid #CFE3DC;background:white;height:46px;padding:0 10px;-moz-border-radius:4px;-webkit-border-radius:4px;border-radius:4px;font-size:16px;font-size:1.6rem}[role="complementary"] form select{height:36px;font-size:14px;font-size:1.4rem}form button{-moz-appearance:none;-webkit-appearance:none;background:#20AA76;-moz-border-radius:4px;-webkit-border-radius:4px;border-radius:4px;color:white;border:0;height:46px;padding:0 15px;font-family:"Roboto", Corbel, Avenir, "Lucida Grande", "Lucida Sans", sans-serif;font-size:16px;font-size:1.6rem}form button:hover{background:#44B78B}.form-general fieldset{max-width:700px;border:0;padding:0;margin:15px 0}.form-general fieldset input[type="search"],.form-general fieldset input[type="text"],.form-general fieldset input[type="email"],.form-general fieldset input[type="password"],.form-general fieldset input[type="url"]{margin:10px 0}.form-input{min-height:40px;margin:30px 0 20px;position:relative}.form-input:focus{background:#000}.form-input button{background:none;border:none;color:#44B78B;height:40px;padding:0;position:absolute;right:2%;top:6%;width:40px;right:1%}.form-input button i{font-size:20px;font-size:2rem;line-height:1}.form-input button:hover,.form-input button:focus,.form-input button:active{background:none;color:#0C4B33;outline:none}[role="complementary"] .form-input{min-height:30px;margin:20px 0 30px}[role="complementary"] .form-input input[type="search"],[role="complementary"] .form-input input[type="text"],[role="complementary"] .form-input input[type="email"]{height:20px;font-size:14px;font-size:1.4rem}[role="complementary"] .form-input button{height:30px;width:30px;top:3px;right:0}[role="complementary"] .form-input button i{font-size:20px;font-size:2rem}form.donate{max-width:150px}form.donate label{position:absolute;left:0px;color:#2B8C67;padding-top:0.3em;padding-left:0.5em}form.donate input[type=text]{padding-left:20px;padding-right:9px}div[role=main] form.donate label{padding-top:0.7em}::-webkit-input-placeholder,:-moz-placeholder,::-moz-placeholder,:-ms-input-placeholder{color:#859D94}.form-email h3{font-size:18px;font-size:1.8rem;margin:10px 0}.form-email .meta,.form-email .list-links dd,.list-links .form-email dd{margin:0}.form-email form{margin:10px 0 30px}.nav-pagination{font-family:"Roboto", Corbel, Avenir, "Lucida Grande", "Lucida Sans", sans-serif;font-size:14px;font-size:1.4rem;border-top:1px solid #CFE3DC;font-weight:700;line-height:31px;list-style:none;margin:30px 0;padding:30px 0 0;text-align:center}.nav-pagination li{display:inline-block;display:inline;zoom:1}.nav-pagination a{border:none;color:#798780;height:auto;width:auto;margin:0 5px;-moz-border-radius:15px;-webkit-border-radius:15px;border-radius:15px;display:block;text-decoration:none;background:#798780;color:#fff;height:30px;margin:0 2px;width:30px}.nav-pagination a.previous,.nav-pagination a.next{font-size:16px;font-size:1.6rem;-moz-border-radius:20px;-webkit-border-radius:20px;border-radius:20px;height:40px;line-height:43px;width:40px}.nav-pagination a.previous{margin-right:10px;margin-right:70px}.nav-pagination a.next{margin-left:10px;text-indent:1px;margin-left:70px}.nav-pagination a:hover,.nav-pagination a:focus,.nav-pagination a:active,.nav-pagination a.active{background:none;color:#20AA76;background:#20AA76;color:white}.mdzr-no-borderradius .nav-pagination a{display:inline;background:none;color:#798780;height:auto;width:auto;margin:0 5px !important}.mdzr-no-borderradius .nav-pagination a:hover,.mdzr-no-borderradius .nav-pagination a:active,.mdzr-no-borderradius .nav-pagination a:focus,.mdzr-no-borderradius .nav-pagination a.active{background:none;color:#20AA76}hr{border:0;border-top:1px solid #CFE3DC}.badge{-moz-border-radius:4px;-webkit-border-radius:4px;border-radius:4px;font-size:12px;padding:2px 6px;margin:0 5px;letter-spacing:0px;position:relative;bottom:0.3em;color:#F1FFF7;background-color:#20AA76}.user-info .avatar{padding:20px;border:1px solid #CFE3DC;-moz-border-radius:4px;-webkit-border-radius:4px;border-radius:4px;float:right}.visuallyhidden{border:0;clip:rect(0 0 0 0);height:1px;margin:-1px;overflow:hidden;padding:0;position:absolute;width:1px}.clearfix,.group-container{zoom:1}.clearfix:before,.clearfix:after,.group-container:before,.group-container:after{content:"";display:table}.clearfix:after,.group-container:after{clear:both}.fundraising-index{margin-top:50px}.fundraising-index .fundraising-heart{width:100%;float:none;width:55%;float:left}.fundraising-index .fundraising-heart img,.fundraising-index .fundraising-heart svg{width:100%;height:auto}.fundraising-index .fundraising-heart rect{-moz-transition:opacity 250ms ease-out;-o-transition:opacity 250ms ease-out;-webkit-transition:opacity 250ms ease-out;transition:opacity 250ms ease-out}.fundraising-index .fundraising-heart rect.faded{opacity:0.05}.fundraising-index .fundraising-heart rect.faded:hover{opacity:0.9}.fundraising-index .description{margin-left:5%;width:100%;float:none;margin-top:30px;width:40%;float:left;margin-top:0px}.fundraising-index .description h2{margin-top:0;font-size:28px;font-size:2.8rem}.fundraising-index .donate select{width:80%;width:100%}.fundraising-index .donate .cta{margin:10px 0}.fundraising-index .donate .custom-donation{display:none}.fundraising-index .donate .prefix{float:left;font-size:19px;font-size:1.9rem;margin:8px 9px 0 3px}.fundraising-index .donate input[type="text"]{width:70%}.fundraising-index .cls{clear:both}.fundraising-donation select{width:100%}.fundraising-donation input.error{border:1px solid #BA2121 !important}.fundraising-donation p.validation-errors{color:#BA2121}.fundraising-donation .custom-donation{display:none;margin-top:10px}.fundraising-sidebar{clear:both}.fundraising-sidebar .small-heart{margin-top:20px;width:20%;float:left;margin-bottom:20px}.fundraising-sidebar .small-heart img{width:100%;max-width:64px}.fundraising-sidebar .small-cta{width:70%;float:left;vertical-align:top;margin-left:5%;margin-right:5%;margin-bottom:20px}.footnote{color:#859D94;font-size:14px;font-size:1.4rem;margin-top:20px;text-align:center}form .footnote{margin-top:10px;text-align:left}.heros-section{overflow:hidden}.heros-section .heros{clear:both}.heros-section .heros .hero{width:25%;position:relative;height:auto}.heros-section .heros .hero div{width:100%}.heros-section .heros .hero-logo{height:170px;line-height:170px}.heros-section .heros .hero-logo img{vertical-align:middle}.heros-section .heros .hero-name{height:92px;vertical-align:top}.heros-section .heros .no-logo-hero{min-height:60px;margin-right:24px}.heros-section .heros div{float:left;text-align:center;margin:15px 0}.heros-section .heros div img{max-width:90%;max-height:170px}.heros-section .pagination{clear:both}pre.literal-block,.literal-block{font-size:14px;font-size:1.4rem;border:1px solid #EAEAEA;background:#F4F4F4;background:#f8f8f8;overflow:auto;border-radius:4px;margin:25px 0;padding:10px 20px;color:#0C4B33}.snippet-filename{background:#C9F0DD;color:#0C4B33;font-family:"Fira Mono", Consolas, Menlo, Monaco, "Courier New", Courier, monospace;font-variant-ligatures:no-common-ligatures;text-rendering:optimizeSpeed;font-size:1em;padding:5px 20px;-moz-border-radius:4px 4px 0 0;-webkit-border-radius:4px;border-radius:4px 4px 0 0}.snippet-filename+.highlight{margin-top:0;-moz-border-radius:0 0 4px 4px;-webkit-border-radius:0;border-radius:0 0 4px 4px;border-top:0}.highlight{font-size:14px;font-size:1.4rem;border:1px solid #EAEAEA;background:#F4F4F4;background:#f8f8f8;overflow:auto;border-radius:4px;margin:25px 0}.highlight pre{margin:15px 20px}.highlight li{margin-top:0;border-left:1px solid #EAEAEA;padding:0 0 2px 15px}.highlight li:first-child{padding-top:2px}.highlight .hll{background-color:#ffc}.highlight .c{color:#408080;font-style:italic}.highlight .err{border:1px solid red}.highlight .k{color:#008000;font-weight:bold}.highlight .o{color:#666}.highlight .cm{color:#408080;font-style:italic}.highlight .cp{color:#BC7A00}.highlight .c1{color:#408080;font-style:italic}.highlight .cs{color:#408080;font-style:italic}.highlight .gd{color:#A00000}.highlight .ge{font-style:italic}.highlight .gr{color:red}.highlight .gh{color:#000080;font-weight:bold}.highlight .gi{color:#00A000}.highlight .go{color:gray}.highlight .gp{color:#000080;font-weight:bold}.highlight .gs{font-weight:bold}.highlight .gu{color:#800080;font-weight:bold}.highlight .gt{color:#0040D0}.highlight .kc{color:#008000;font-weight:bold}.highlight .kd{color:#008000;font-weight:bold}.highlight .kn{color:#008000;font-weight:bold}.highlight .kp{color:green}.highlight .kr{color:#008000;font-weight:bold}.highlight .kt{color:#B00040}.highlight .m{color:#666}.highlight .s{color:#BA2121}.highlight .na{color:#7D9029}.highlight .nb{color:green}.highlight .nc{color:#0000FF;font-weight:bold}.highlight .no{color:#800}.highlight .nd{color:#a2f}.highlight .ni{color:#999999;font-weight:bold}.highlight .ne{color:#D2413A;font-weight:bold}.highlight .nf{color:blue}.highlight .nl{color:#A0A000}.highlight .nn{color:#0000FF;font-weight:bold}.highlight .nt{color:#008000;font-weight:bold}.highlight .nv{color:#19177C}.highlight .ow{color:#AA22FF;font-weight:bold}.highlight .w{color:#bbb}.highlight .mf{color:#666}.highlight .mh{color:#666}.highlight .mi{color:#666}.highlight .mo{color:#666}.highlight .sb{color:#BA2121}.highlight .sc{color:#BA2121}.highlight .sd{color:#BA2121;font-style:italic}.highlight .s2{color:#BA2121}.highlight .se{color:#BB6622;font-weight:bold}.highlight .sh{color:#BA2121}.highlight .si{color:#BB6688;font-weight:bold}.highlight .sx{color:green}.highlight .sr{color:#b68}.highlight .s1{color:#BA2121}.highlight .ss{color:#19177C}.highlight .bp{color:green}.highlight .vc{color:#19177C}.highlight .vg{color:#19177C}.highlight .vi{color:#19177C}.highlight .il{color:#666}.highlight .lineno{color:#000000;background-color:#dddddd}.styleguide .example{padding:0 20px 20px;border:1px solid #CFE3DC;-moz-border-radius:4px;-webkit-border-radius:4px;border-radius:4px;margin-top:20px;margin-bottom:64px}.styleguide .example:before{content:"Example";font-size:16px;font-weight:700;display:block;color:#CFE3DC;font-family:"Roboto", Corbel, Avenir, "Lucida Grande", "Lucida Sans", sans-serif;text-align:left;padding:10px 0}.styleguide .example [role="complementary"]{float:none;width:auto;padding:0;margin:0}.styleguide .iframe{display:block;height:400px;cursor:zoom-in;border:1px solid #CFE3DC;overflow:hidden}.styleguide .iframe iframe{pointer-events:none;position:relative;width:200%;border:0;height:800px;-moz-transform:scale(0.5) translate(-50%, -50%);-ms-transform:scale(0.5) translate(-50%, -50%);-webkit-transform:scale(0.5) translate(-50%, -50%);transform:scale(0.5) translate(-50%, -50%);top:0;left:0;overflow:hidden}.styleguide .swatches{margin:0;padding:0;list-style:none;zoom:1;margin:30px 0}.styleguide .swatches:before,.styleguide .swatches:after{content:"";display:table}.styleguide .swatches:after{clear:both}.styleguide .swatches li{width:30%;height:30px;margin-right:2%;-moz-box-sizing:border-box;-webkit-box-sizing:border-box;box-sizing:border-box;float:left}.styleguide .swatches li.text{background:#0C3C26}.styleguide .swatches li.green-dark{background:#0C4B33}.styleguide .swatches li.green{background:#20AA76}.styleguide .swatches li.green-light{background:#93D7B7}.styleguide .swatches li.white{background:#F1FFF7;border:1px solid #CFE3DC}.styleguide .swatches li.red-dark{background:#6A0E0E}.styleguide .swatches li.text-light{background:#798780}.styleguide .swatches li.green-medium-dark{background:#2B8C67}.styleguide .swatches li.green-medium{background:#44B78B}.styleguide .swatches li.green-very-light{background:#C9F0DD}.styleguide .swatches li.gray-line{background:#CFE3DC}.styleguide .swatches li.red{background:#BA2121}.styleguide #layout{overflow:hidden}.styleguide #icons .icon{font-size:32px;font-size:3.2rem;color:#20AA76;padding:0 .2em}@media print{*{background:transparent !important;color:#000 !important;box-shadow:none !important;text-shadow:none !important}a,a:visited{text-decoration:underline}pre,blockquote{border:1px solid #999;page-break-inside:avoid}thead{display:table-header-group}tr,img{page-break-inside:avoid}img{max-width:100% !important}@page{margin:0.5cm}p,h2,h3{orphans:3;widows:3}h2,h3{page-break-after:avoid}html{font-size:40%}.menu-button,.news-search,.backtotop,.nav-pagination,[role="contentinfo"]>.container,[role="contentinfo"] .logo,.thanks,[role="complementary"],[role="navigation"],.form-input{display:none !important}.logo{text-indent:0 !important}[role="contentinfo"],[role="contentinfo"] .copyright{margin:0 !important;padding:0 !important}.internal-container{float:none;width:auto}.list-news li{margin-top:0}}
+ */@font-face{font-family:'FontAwesome';src:url("//netdna.bootstrapcdn.com/font-awesome/4.2.0/fonts/fontawesome-webfont.eot?v=4.2.0");src:url("//netdna.bootstrapcdn.com/font-awesome/4.2.0/fonts/fontawesome-webfont.eot?#iefix&v=4.2.0") format("embedded-opentype"),url("//netdna.bootstrapcdn.com/font-awesome/4.2.0/fonts/fontawesome-webfont.woff?v=4.2.0") format("woff"),url("//netdna.bootstrapcdn.com/font-awesome/4.2.0/fonts/fontawesome-webfont.ttf?v=4.2.0") format("truetype"),url("//netdna.bootstrapcdn.com/font-awesome/4.2.0/fonts/fontawesome-webfont.svg?v=4.2.0#fontawesomeregular") format("svg");font-weight:normal;font-style:normal}.icon{display:inline-block;font:normal normal normal 14px/1 FontAwesome;font-size:inherit;text-rendering:auto;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.icon-lg{font-size:1.33333em;line-height:0.75em;vertical-align:-15%}.icon-2x{font-size:2em}.icon-3x{font-size:3em}.icon-4x{font-size:4em}.icon-5x{font-size:5em}.icon-fw{width:1.28571em;text-align:center}.icon-ul{padding-left:0;margin-left:2.14286em;list-style-type:none}.icon-ul>li{position:relative}.icon-li{position:absolute;left:-2.14286em;width:2.14286em;top:0.14286em;text-align:center}.icon-li.icon-lg{left:-1.85714em}.icon-border{padding:.2em .25em .15em;border:solid 0.08em #eee;border-radius:.1em}.pull-right{float:right}.pull-left{float:left}.icon.pull-left{margin-right:.3em}.icon.pull-right{margin-left:.3em}.icon-spin{-webkit-animation:fa-spin 2s infinite linear;animation:fa-spin 2s infinite linear}@-webkit-keyframes fa-spin{0%{-webkit-transform:rotate(0deg);transform:rotate(0deg)}100%{-webkit-transform:rotate(359deg);transform:rotate(359deg)}}@keyframes fa-spin{0%{-webkit-transform:rotate(0deg);transform:rotate(0deg)}100%{-webkit-transform:rotate(359deg);transform:rotate(359deg)}}.icon-rotate-90{filter:progid:DXImageTransform.Microsoft.BasicImage(rotation=1);-webkit-transform:rotate(90deg);-ms-transform:rotate(90deg);transform:rotate(90deg)}.icon-rotate-180{filter:progid:DXImageTransform.Microsoft.BasicImage(rotation=2);-webkit-transform:rotate(180deg);-ms-transform:rotate(180deg);transform:rotate(180deg)}.icon-rotate-270{filter:progid:DXImageTransform.Microsoft.BasicImage(rotation=3);-webkit-transform:rotate(270deg);-ms-transform:rotate(270deg);transform:rotate(270deg)}.icon-flip-horizontal{filter:progid:DXImageTransform.Microsoft.BasicImage(rotation=0);-webkit-transform:scale(-1, 1);-ms-transform:scale(-1, 1);transform:scale(-1, 1)}.icon-flip-vertical{filter:progid:DXImageTransform.Microsoft.BasicImage(rotation=2);-webkit-transform:scale(1, -1);-ms-transform:scale(1, -1);transform:scale(1, -1)}:root .icon-rotate-90,:root .icon-rotate-180,:root .icon-rotate-270,:root .icon-flip-horizontal,:root .icon-flip-vertical{filter:none}.icon-stack{position:relative;display:inline-block;width:2em;height:2em;line-height:2em;vertical-align:middle}.icon-stack-1x,.icon-stack-2x{position:absolute;left:0;width:100%;text-align:center}.icon-stack-1x{line-height:inherit}.icon-stack-2x{font-size:2em}.icon-inverse{color:#fff}.icon-glass:before{content:""}.icon-music:before{content:""}.icon-search:before{content:""}.icon-envelope-o:before{content:""}.icon-heart:before{content:""}.icon-star:before{content:""}.icon-star-o:before{content:""}.icon-user:before{content:""}.icon-film:before{content:""}.icon-th-large:before{content:""}.icon-th:before{content:""}.icon-th-list:before{content:""}.icon-check:before{content:""}.icon-remove:before,.icon-close:before,.icon-times:before{content:""}.icon-search-plus:before{content:""}.icon-search-minus:before{content:""}.icon-power-off:before{content:""}.icon-signal:before{content:""}.icon-gear:before,.icon-cog:before{content:""}.icon-trash-o:before{content:""}.icon-home:before{content:""}.icon-file-o:before{content:""}.icon-clock-o:before{content:""}.icon-road:before{content:""}.icon-download:before{content:""}.icon-arrow-circle-o-down:before{content:""}.icon-arrow-circle-o-up:before{content:""}.icon-inbox:before{content:""}.icon-play-circle-o:before{content:""}.icon-rotate-right:before,.icon-repeat:before{content:""}.icon-refresh:before{content:""}.icon-list-alt:before{content:""}.icon-lock:before{content:""}.icon-flag:before{content:""}.icon-headphones:before{content:""}.icon-volume-off:before{content:""}.icon-volume-down:before{content:""}.icon-volume-up:before{content:""}.icon-qrcode:before{content:""}.icon-barcode:before{content:""}.icon-tag:before{content:""}.icon-tags:before{content:""}.icon-book:before{content:""}.icon-bookmark:before{content:""}.icon-print:before{content:""}.icon-camera:before{content:""}.icon-font:before{content:""}.icon-bold:before{content:""}.icon-italic:before{content:""}.icon-text-height:before{content:""}.icon-text-width:before{content:""}.icon-align-left:before{content:""}.icon-align-center:before{content:""}.icon-align-right:before{content:""}.icon-align-justify:before{content:""}.icon-list:before{content:""}.icon-dedent:before,.icon-outdent:before{content:""}.icon-indent:before{content:""}.icon-video-camera:before{content:""}.icon-photo:before,.icon-image:before,.icon-picture-o:before{content:""}.icon-pencil:before{content:""}.icon-map-marker:before{content:""}.icon-adjust:before{content:""}.icon-tint:before{content:""}.icon-edit:before,.icon-pencil-square-o:before{content:""}.icon-share-square-o:before{content:""}.icon-check-square-o:before{content:""}.icon-arrows:before{content:""}.icon-step-backward:before{content:""}.icon-fast-backward:before{content:""}.icon-backward:before{content:""}.icon-play:before{content:""}.icon-pause:before{content:""}.icon-stop:before{content:""}.icon-forward:before{content:""}.icon-fast-forward:before{content:""}.icon-step-forward:before{content:""}.icon-eject:before{content:""}.icon-chevron-left:before{content:""}.icon-chevron-right:before{content:""}.icon-plus-circle:before{content:""}.icon-minus-circle:before{content:""}.icon-times-circle:before{content:""}.icon-check-circle:before{content:""}.icon-question-circle:before{content:""}.icon-info-circle:before{content:""}.icon-crosshairs:before{content:""}.icon-times-circle-o:before{content:""}.icon-check-circle-o:before{content:""}.icon-ban:before{content:""}.icon-arrow-left:before{content:""}.icon-arrow-right:before{content:""}.icon-arrow-up:before{content:""}.icon-arrow-down:before{content:""}.icon-mail-forward:before,.icon-share:before{content:""}.icon-expand:before{content:""}.icon-compress:before{content:""}.icon-plus:before{content:""}.icon-minus:before{content:""}.icon-asterisk:before{content:""}.icon-exclamation-circle:before{content:""}.icon-gift:before{content:""}.icon-leaf:before{content:""}.icon-fire:before{content:""}.icon-eye:before{content:""}.icon-eye-slash:before{content:""}.icon-warning:before,.icon-exclamation-triangle:before{content:""}.icon-plane:before{content:""}.icon-calendar:before{content:""}.icon-random:before{content:""}.icon-comment:before{content:""}.icon-magnet:before{content:""}.icon-chevron-up:before{content:""}.icon-chevron-down:before{content:""}.icon-retweet:before{content:""}.icon-shopping-cart:before{content:""}.icon-folder:before{content:""}.icon-folder-open:before{content:""}.icon-arrows-v:before{content:""}.icon-arrows-h:before{content:""}.icon-bar-chart-o:before,.icon-bar-chart:before{content:""}.icon-twitter-square:before{content:""}.icon-facebook-square:before{content:""}.icon-camera-retro:before{content:""}.icon-key:before{content:""}.icon-gears:before,.icon-cogs:before{content:""}.icon-comments:before{content:""}.icon-thumbs-o-up:before{content:""}.icon-thumbs-o-down:before{content:""}.icon-star-half:before{content:""}.icon-heart-o:before{content:""}.icon-sign-out:before{content:""}.icon-linkedin-square:before{content:""}.icon-thumb-tack:before{content:""}.icon-external-link:before{content:""}.icon-sign-in:before{content:""}.icon-trophy:before{content:""}.icon-github-square:before{content:""}.icon-upload:before{content:""}.icon-lemon-o:before{content:""}.icon-phone:before{content:""}.icon-square-o:before{content:""}.icon-bookmark-o:before{content:""}.icon-phone-square:before{content:""}.icon-twitter:before{content:""}.icon-facebook:before{content:""}.icon-github:before{content:""}.icon-unlock:before{content:""}.icon-credit-card:before{content:""}.icon-rss:before{content:""}.icon-hdd-o:before{content:""}.icon-bullhorn:before{content:""}.icon-bell:before{content:""}.icon-certificate:before{content:""}.icon-hand-o-right:before{content:""}.icon-hand-o-left:before{content:""}.icon-hand-o-up:before{content:""}.icon-hand-o-down:before{content:""}.icon-arrow-circle-left:before{content:""}.icon-arrow-circle-right:before{content:""}.icon-arrow-circle-up:before{content:""}.icon-arrow-circle-down:before{content:""}.icon-globe:before{content:""}.icon-wrench:before{content:""}.icon-tasks:before{content:""}.icon-filter:before{content:""}.icon-briefcase:before{content:""}.icon-arrows-alt:before{content:""}.icon-group:before,.icon-users:before{content:""}.icon-chain:before,.icon-link:before{content:""}.icon-cloud:before{content:""}.icon-flask:before{content:""}.icon-cut:before,.icon-scissors:before{content:""}.icon-copy:before,.icon-files-o:before{content:""}.icon-paperclip:before{content:""}.icon-save:before,.icon-floppy-o:before{content:""}.icon-square:before{content:""}.icon-navicon:before,.icon-reorder:before,.icon-bars:before{content:""}.icon-list-ul:before{content:""}.icon-list-ol:before{content:""}.icon-strikethrough:before{content:""}.icon-underline:before{content:""}.icon-table:before{content:""}.icon-magic:before{content:""}.icon-truck:before{content:""}.icon-pinterest:before{content:""}.icon-pinterest-square:before{content:""}.icon-google-plus-square:before{content:""}.icon-google-plus:before{content:""}.icon-money:before{content:""}.icon-caret-down:before{content:""}.icon-caret-up:before{content:""}.icon-caret-left:before{content:""}.icon-caret-right:before{content:""}.icon-columns:before{content:""}.icon-unsorted:before,.icon-sort:before{content:""}.icon-sort-down:before,.icon-sort-desc:before{content:""}.icon-sort-up:before,.icon-sort-asc:before{content:""}.icon-envelope:before{content:""}.icon-linkedin:before{content:""}.icon-rotate-left:before,.icon-undo:before{content:""}.icon-legal:before,.icon-gavel:before{content:""}.icon-dashboard:before,.icon-tachometer:before{content:""}.icon-comment-o:before{content:""}.icon-comments-o:before{content:""}.icon-flash:before,.icon-bolt:before{content:""}.icon-sitemap:before{content:""}.icon-umbrella:before{content:""}.icon-paste:before,.icon-clipboard:before{content:""}.icon-lightbulb-o:before{content:""}.icon-exchange:before{content:""}.icon-cloud-download:before{content:""}.icon-cloud-upload:before{content:""}.icon-user-md:before{content:""}.icon-stethoscope:before{content:""}.icon-suitcase:before{content:""}.icon-bell-o:before{content:""}.icon-coffee:before{content:""}.icon-cutlery:before{content:""}.icon-file-text-o:before{content:""}.icon-building-o:before{content:""}.icon-hospital-o:before{content:""}.icon-ambulance:before{content:""}.icon-medkit:before{content:""}.icon-fighter-jet:before{content:""}.icon-beer:before{content:""}.icon-h-square:before{content:""}.icon-plus-square:before{content:""}.icon-angle-double-left:before{content:""}.icon-angle-double-right:before{content:""}.icon-angle-double-up:before{content:""}.icon-angle-double-down:before{content:""}.icon-angle-left:before{content:""}.icon-angle-right:before{content:""}.icon-angle-up:before{content:""}.icon-angle-down:before{content:""}.icon-desktop:before{content:""}.icon-laptop:before{content:""}.icon-tablet:before{content:""}.icon-mobile-phone:before,.icon-mobile:before{content:""}.icon-circle-o:before{content:""}.icon-quote-left:before{content:""}.icon-quote-right:before{content:""}.icon-spinner:before{content:""}.icon-circle:before{content:""}.icon-mail-reply:before,.icon-reply:before{content:""}.icon-github-alt:before{content:""}.icon-folder-o:before{content:""}.icon-folder-open-o:before{content:""}.icon-smile-o:before{content:""}.icon-frown-o:before{content:""}.icon-meh-o:before{content:""}.icon-gamepad:before{content:""}.icon-keyboard-o:before{content:""}.icon-flag-o:before{content:""}.icon-flag-checkered:before{content:""}.icon-terminal:before{content:""}.icon-code:before{content:""}.icon-mail-reply-all:before,.icon-reply-all:before{content:""}.icon-star-half-empty:before,.icon-star-half-full:before,.icon-star-half-o:before{content:""}.icon-location-arrow:before{content:""}.icon-crop:before{content:""}.icon-code-fork:before{content:""}.icon-unlink:before,.icon-chain-broken:before{content:""}.icon-question:before{content:""}.icon-info:before{content:""}.icon-exclamation:before{content:""}.icon-superscript:before{content:""}.icon-subscript:before{content:""}.icon-eraser:before{content:""}.icon-puzzle-piece:before{content:""}.icon-microphone:before{content:""}.icon-microphone-slash:before{content:""}.icon-shield:before{content:""}.icon-calendar-o:before{content:""}.icon-fire-extinguisher:before{content:""}.icon-rocket:before{content:""}.icon-maxcdn:before{content:""}.icon-chevron-circle-left:before{content:""}.icon-chevron-circle-right:before{content:""}.icon-chevron-circle-up:before{content:""}.icon-chevron-circle-down:before{content:""}.icon-html5:before{content:""}.icon-css3:before{content:""}.icon-anchor:before{content:""}.icon-unlock-alt:before{content:""}.icon-bullseye:before{content:""}.icon-ellipsis-h:before{content:""}.icon-ellipsis-v:before{content:""}.icon-rss-square:before{content:""}.icon-play-circle:before{content:""}.icon-ticket:before{content:""}.icon-minus-square:before{content:""}.icon-minus-square-o:before{content:""}.icon-level-up:before{content:""}.icon-level-down:before{content:""}.icon-check-square:before{content:""}.icon-pencil-square:before{content:""}.icon-external-link-square:before{content:""}.icon-share-square:before{content:""}.icon-compass:before{content:""}.icon-toggle-down:before,.icon-caret-square-o-down:before{content:""}.icon-toggle-up:before,.icon-caret-square-o-up:before{content:""}.icon-toggle-right:before,.icon-caret-square-o-right:before{content:""}.icon-euro:before,.icon-eur:before{content:""}.icon-gbp:before{content:""}.icon-dollar:before,.icon-usd:before{content:""}.icon-rupee:before,.icon-inr:before{content:""}.icon-cny:before,.icon-rmb:before,.icon-yen:before,.icon-jpy:before{content:""}.icon-ruble:before,.icon-rouble:before,.icon-rub:before{content:""}.icon-won:before,.icon-krw:before{content:""}.icon-bitcoin:before,.icon-btc:before{content:""}.icon-file:before{content:""}.icon-file-text:before{content:""}.icon-sort-alpha-asc:before{content:""}.icon-sort-alpha-desc:before{content:""}.icon-sort-amount-asc:before{content:""}.icon-sort-amount-desc:before{content:""}.icon-sort-numeric-asc:before{content:""}.icon-sort-numeric-desc:before{content:""}.icon-thumbs-up:before{content:""}.icon-thumbs-down:before{content:""}.icon-youtube-square:before{content:""}.icon-youtube:before{content:""}.icon-xing:before{content:""}.icon-xing-square:before{content:""}.icon-youtube-play:before{content:""}.icon-dropbox:before{content:""}.icon-stack-overflow:before{content:""}.icon-instagram:before{content:""}.icon-flickr:before{content:""}.icon-adn:before{content:""}.icon-bitbucket:before{content:""}.icon-bitbucket-square:before{content:""}.icon-tumblr:before{content:""}.icon-tumblr-square:before{content:""}.icon-long-arrow-down:before{content:""}.icon-long-arrow-up:before{content:""}.icon-long-arrow-left:before{content:""}.icon-long-arrow-right:before{content:""}.icon-apple:before{content:""}.icon-windows:before{content:""}.icon-android:before{content:""}.icon-linux:before{content:""}.icon-dribbble:before{content:""}.icon-skype:before{content:""}.icon-foursquare:before{content:""}.icon-trello:before{content:""}.icon-female:before{content:""}.icon-male:before{content:""}.icon-gittip:before{content:""}.icon-sun-o:before{content:""}.icon-moon-o:before{content:""}.icon-archive:before{content:""}.icon-bug:before{content:""}.icon-vk:before{content:""}.icon-weibo:before{content:""}.icon-renren:before{content:""}.icon-pagelines:before{content:""}.icon-stack-exchange:before{content:""}.icon-arrow-circle-o-right:before{content:""}.icon-arrow-circle-o-left:before{content:""}.icon-toggle-left:before,.icon-caret-square-o-left:before{content:""}.icon-dot-circle-o:before{content:""}.icon-wheelchair:before{content:""}.icon-vimeo-square:before{content:""}.icon-turkish-lira:before,.icon-try:before{content:""}.icon-plus-square-o:before{content:""}.icon-space-shuttle:before{content:""}.icon-slack:before{content:""}.icon-envelope-square:before{content:""}.icon-wordpress:before{content:""}.icon-openid:before{content:""}.icon-institution:before,.icon-bank:before,.icon-university:before{content:""}.icon-mortar-board:before,.icon-graduation-cap:before{content:""}.icon-yahoo:before{content:""}.icon-google:before{content:""}.icon-reddit:before{content:""}.icon-reddit-square:before{content:""}.icon-stumbleupon-circle:before{content:""}.icon-stumbleupon:before{content:""}.icon-delicious:before{content:""}.icon-digg:before{content:""}.icon-pied-piper:before{content:""}.icon-pied-piper-alt:before{content:""}.icon-drupal:before{content:""}.icon-joomla:before{content:""}.icon-language:before{content:""}.icon-fax:before{content:""}.icon-building:before{content:""}.icon-child:before{content:""}.icon-paw:before{content:""}.icon-spoon:before{content:""}.icon-cube:before{content:""}.icon-cubes:before{content:""}.icon-behance:before{content:""}.icon-behance-square:before{content:""}.icon-steam:before{content:""}.icon-steam-square:before{content:""}.icon-recycle:before{content:""}.icon-automobile:before,.icon-car:before{content:""}.icon-cab:before,.icon-taxi:before{content:""}.icon-tree:before{content:""}.icon-spotify:before{content:""}.icon-deviantart:before{content:""}.icon-soundcloud:before{content:""}.icon-database:before{content:""}.icon-file-pdf-o:before{content:""}.icon-file-word-o:before{content:""}.icon-file-excel-o:before{content:""}.icon-file-powerpoint-o:before{content:""}.icon-file-photo-o:before,.icon-file-picture-o:before,.icon-file-image-o:before{content:""}.icon-file-zip-o:before,.icon-file-archive-o:before{content:""}.icon-file-sound-o:before,.icon-file-audio-o:before{content:""}.icon-file-movie-o:before,.icon-file-video-o:before{content:""}.icon-file-code-o:before{content:""}.icon-vine:before{content:""}.icon-codepen:before{content:""}.icon-jsfiddle:before{content:""}.icon-life-bouy:before,.icon-life-buoy:before,.icon-life-saver:before,.icon-support:before,.icon-life-ring:before{content:""}.icon-circle-o-notch:before{content:""}.icon-ra:before,.icon-rebel:before{content:""}.icon-ge:before,.icon-empire:before{content:""}.icon-git-square:before{content:""}.icon-git:before{content:""}.icon-hacker-news:before{content:""}.icon-tencent-weibo:before{content:""}.icon-qq:before{content:""}.icon-wechat:before,.icon-weixin:before{content:""}.icon-send:before,.icon-paper-plane:before{content:""}.icon-send-o:before,.icon-paper-plane-o:before{content:""}.icon-history:before{content:""}.icon-circle-thin:before{content:""}.icon-header:before{content:""}.icon-paragraph:before{content:""}.icon-sliders:before{content:""}.icon-share-alt:before{content:""}.icon-share-alt-square:before{content:""}.icon-bomb:before{content:""}.icon-soccer-ball-o:before,.icon-futbol-o:before{content:""}.icon-tty:before{content:""}.icon-binoculars:before{content:""}.icon-plug:before{content:""}.icon-slideshare:before{content:""}.icon-twitch:before{content:""}.icon-yelp:before{content:""}.icon-newspaper-o:before{content:""}.icon-wifi:before{content:""}.icon-calculator:before{content:""}.icon-paypal:before{content:""}.icon-google-wallet:before{content:""}.icon-cc-visa:before{content:""}.icon-cc-mastercard:before{content:""}.icon-cc-discover:before{content:""}.icon-cc-amex:before{content:""}.icon-cc-paypal:before{content:""}.icon-cc-stripe:before{content:""}.icon-bell-slash:before{content:""}.icon-bell-slash-o:before{content:""}.icon-trash:before{content:""}.icon-copyright:before{content:""}.icon-at:before{content:""}.icon-eyedropper:before{content:""}.icon-paint-brush:before{content:""}.icon-birthday-cake:before{content:""}.icon-area-chart:before{content:""}.icon-pie-chart:before{content:""}.icon-line-chart:before{content:""}.icon-lastfm:before{content:""}.icon-lastfm-square:before{content:""}.icon-toggle-off:before{content:""}.icon-toggle-on:before{content:""}.icon-bicycle:before{content:""}.icon-bus:before{content:""}.icon-ioxhost:before{content:""}.icon-angellist:before{content:""}.icon-cc:before{content:""}.icon-shekel:before,.icon-sheqel:before,.icon-ils:before{content:""}.icon-meanpath:before{content:""}body{font-family:Palatino, "Palatino Linotype", "Book Antiqua", "Hoefler Text", Georgia, "Lucida Bright", Cambria, Times, "Times New Roman", serif;font-size:18px;font-size:1.8rem;background:#f8f8f8;color:#0C3C26;line-height:1.6;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;min-width:768px}body .layout-secondary,body [role="complementary"]{font-family:"Roboto", Corbel, Avenir, "Lucida Grande", "Lucida Sans", sans-serif}a{color:#0C3C26;text-decoration:underline;-webkit-tap-highlight-color:transparent}a:visited{color:#156641}a:active,a:focus,a:hover{color:#1d915c}::selection{background:#C9F0DD}::-moz-selection{background:#C9F0DD}ol li,ul li{margin-top:10px}dl{margin:20px 0 10px}dl dt{font-family:"Roboto", Corbel, Avenir, "Lucida Grande", "Lucida Sans", sans-serif;font-weight:400}dl dd{margin:0.2em 0 1.2em;padding:0}dl dd:last-of-type{margin-bottom:0}h1,h2,h3,h4,h5,h6{font-family:"Roboto", Corbel, Avenir, "Lucida Grande", "Lucida Sans", sans-serif;font-weight:400}h1 a,h2 a,h3 a,h4 a,h5 a,h6 a{text-decoration:none;color:#20AA76;text-decoration:none}h1 a:visited,h2 a:visited,h3 a:visited,h4 a:visited,h5 a:visited,h6 a:visited{color:#20AA76}h1 a:hover,h1 a:active,h1 a:focus,h2 a:hover,h2 a:active,h2 a:focus,h3 a:hover,h3 a:active,h3 a:focus,h4 a:hover,h4 a:active,h4 a:focus,h5 a:hover,h5 a:active,h5 a:focus,h6 a:hover,h6 a:active,h6 a:focus{color:#25c488;text-decoration:none}h1{font-size:28px;font-size:2.8rem;color:#fff;letter-spacing:-1px;line-height:1.1;font-size:32px;font-size:3.2rem}.layout-secondary h1{color:#0C3C26}[role="main"] h1{font-size:32px;font-size:3.2rem;margin:40px 0px 30px;color:#0C3C26}[role="complementary"] h1{font-size:28px;font-size:2.8rem}h2{font-size:24px;font-size:2.4rem}[role="complementary"] h2,.layout-secondary h2{font-size:20px;font-size:2rem;border-bottom:1px solid #CFE3DC;font-weight:400;padding-bottom:15px;margin-top:30px}[role="complementary"] h2:first-of-type,.layout-secondary h2:first-of-type{margin-top:inherit}[role="complementary"] h2:first-child,.layout-secondary h2:first-child{margin-top:20px}.full-width [role="complementary"] h2,.full-width .layout-secondary h2{font-size:24px;font-size:2.4rem}[role="main"] h2{margin-top:40px;margin-bottom:15px}h3{font-size:20px;font-size:2rem;font-weight:700;color:#0C3C26;line-height:1.2;margin:35px 0 20px}[role="complementary"] h3,.layout-secondary h3{font-size:18px;font-size:1.8rem;font-weight:400;padding-bottom:15px}[role="complementary"] h3:first-child,.layout-secondary h3:first-child{margin-top:12px}[role="complementary"] h3{font-size:18px;font-size:1.8rem;border-bottom:1px solid #CFE3DC}h4{font-size:16px;font-size:1.6rem;color:#0C3C26;line-height:1.2;margin:35px 0 20px;font-weight:700}tt,code,kbd,pre,samp{font-family:"Fira Mono", Consolas, Menlo, Monaco, "Courier New", Courier, monospace;font-variant-ligatures:no-common-ligatures;text-rendering:optimizeSpeed;color:#0C4B33;font-size:1em}tt{font-weight:700}span.pre{font-family:"Fira Mono", Consolas, Menlo, Monaco, "Courier New", Courier, monospace;font-variant-ligatures:no-common-ligatures;text-rendering:optimizeSpeed}a:hover tt,a:active tt,a:focus tt{color:#1d915c}[role="main"]>p:first-child{margin-top:30px}ul{padding-left:20px}blockquote{background:#F1FFF7;padding:15px 20px 15px 70px;border:1px solid #C9F0DD;-moz-border-radius:4px;-webkit-border-radius:4px;border-radius:4px;margin:25px 0;position:relative}blockquote p:first-child{margin-top:0}blockquote::before{display:inline-block;font:normal normal normal 14px/1 FontAwesome;font-size:inherit;text-rendering:auto;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;position:absolute;top:20px;left:20px;font-size:30px;width:34px;text-align:center;content:"";opacity:0.5}[role="main"]{background:#fff;padding:1px 10px 40px;min-height:800px;border:1px solid #ddd;float:right;margin:0;padding-bottom:80px;padding-left:3%;padding-right:3%;width:60%}.mdzr-boxshadow [role="main"]{border:none;-moz-box-shadow:0px 0px 0 0 #fff,0px 0px 0 0 #fff,460px 0 0 0 #fff,1000px 0 0 0 #fff,0px 600px 0 0px #fff,460px 600px 0 0px #fff;-webkit-box-shadow:0px 0px 0 0 #fff,0px 0px 0 0 #fff,460px 0 0 0 #fff,1000px 0 0 0 #fff,0px 600px 0 0px #fff,460px 600px 0 0px #fff;box-shadow:0px 0px 0 0 #fff,0px 0px 0 0 #fff,460px 0 0 0 #fff,1000px 0 0 0 #fff,0px 600px 0 0px #fff,460px 600px 0 0px #fff;padding-left:4%;padding-right:0;width:63%}.sidebar-right [role="main"]{float:left}.mdzr-boxshadow .sidebar-right [role="main"]{border:none;-moz-box-shadow:0px 0px 0 0 #fff,0px 0px 0 0 #fff,-460px 0 0 0 #fff,-1000px 0 0 0 #fff,0px 600px 0 0px #fff,-460px 600px 0 0px #fff;-webkit-box-shadow:0px 0px 0 0 #fff,0px 0px 0 0 #fff,-460px 0 0 0 #fff,-1000px 0 0 0 #fff,0px 600px 0 0px #fff,-460px 600px 0 0px #fff;box-shadow:0px 0px 0 0 #fff,0px 0px 0 0 #fff,-460px 0 0 0 #fff,-1000px 0 0 0 #fff,0px 600px 0 0px #fff,-460px 600px 0 0px #fff;padding-left:0;width:62%;padding-right:4%}.full-width [role="main"]{border:none;-moz-box-shadow:none;-webkit-box-shadow:none;box-shadow:none;float:none;margin:0 auto;padding:0 10px 40px;width:auto;max-width:740px;padding:20px 0 40px;width:91.66667%}[role="main"] .section{padding-bottom:40px;border-bottom:1px solid #CFE3DC}[role="main"] .section:last-of-type{padding-bottom:0;border-bottom:0}[role="main"] .section dd.last-child{padding-bottom:0}.full-width{zoom:1}.full-width.container{width:100%;padding:0;max-width:none;border-bottom:1px solid #ddd;background:#fff}.full-width:before,.full-width:after{content:"";display:table}.full-width:after{clear:both}.mdzr-boxshadow .full-width{border:none;-moz-box-shadow:0 4px 8px rgba(12,60,38,0.07);-webkit-box-shadow:0 4px 8px rgba(12,60,38,0.07);box-shadow:0 4px 8px rgba(12,60,38,0.07)}[role="complementary"]{padding:0 10px 20px;font-size:14px;font-size:1.4rem;float:right;margin:20px 0;margin-right:3%;padding:0 0 40px 0;width:30%}.sidebar-right [role="complementary"]{margin-left:3%;margin-right:0}[role="complementary"] span.form-controls{display:none}[role="complementary"] .list-collapsing{margin-top:0;border-bottom:0}[role="complementary"] .list-collapsing.active li{border-top:0}[role="complementary"] .list-collapsing.active h2{padding:10px 40px 10px 0;font-size:18px;border-bottom:0;color:#44B78B}[role="complementary"] .list-collapsing.active h2 .collapsing-icon{font-size:10px}[role="secondary"]{margin:0 10px;padding:40px 0 60px}.full-width [role="secondary"]{max-width:700px;margin:0 auto}.layout-secondary{padding:20px 10px 50px}.layout-tertiary{background:#fff;border-top:1px solid #ddd;padding:20px 10px 50px}.mdzr-boxshadow .layout-tertiary{border:none;-moz-box-shadow:0 -4px 8px rgba(12,60,38,0.07);-webkit-box-shadow:0 -4px 8px rgba(12,60,38,0.07);box-shadow:0 -4px 8px rgba(12,60,38,0.07)}.container{zoom:1;margin:0 auto;max-width:1400px;padding:0 4.16667%;min-width:768px}.container:before,.container:after{content:"";display:table}.container:after{clear:both}.mdzr-boxshadow .container.sidebar-right{-moz-box-shadow:-1200px 0 0 0px #fff;-webkit-box-shadow:-1200px 0 0 0px #fff;box-shadow:-1200px 0 0 0px #fff}[role="banner"]{zoom:1;background:#0C4B33;overflow:hidden;margin:0;padding:10px 0 6px;position:relative;z-index:0}[role="banner"]:before,[role="banner"]:after{content:"";display:table}[role="banner"]:after{clear:both}[role="banner"] .container{position:relative}[role="banner"] .meta,[role="banner"] .list-links dd,.list-links [role="banner"] dd{font-size:13px;font-size:1.3rem;color:#44B78B;font-weight:700;width:auto;float:left;margin:8px 0 0 10px;display:none}[role="banner"] .logo{font-size:40px;font-size:4rem;font-family:"Roboto", Corbel, Avenir, "Lucida Grande", "Lucida Sans", sans-serif;background:url(../img/logo-django.png) 0 0 no-repeat;color:#FFF;display:block;float:left;font-weight:700;margin:10px;overflow:hidden;text-decoration:none;text-indent:100%;width:104px;height:36px;margin-left:0}.mdzr-svg [role="banner"] .logo{background:url(../img/logo-django.svg) center center no-repeat}[role="banner"] .menu-button{font-size:20px;font-size:2rem;background:#0C4B33;-moz-border-radius:23px;-webkit-border-radius:23px;border-radius:23px;color:#fff;cursor:pointer;display:block;float:right;height:45px;line-height:48px;margin:4px 10px;text-align:center;text-decoration:none;width:45px;display:none}[role="banner"] .menu-button:active{color:#44B78B}[role="banner"] .menu-button span{border:0;clip:rect(0 0 0 0);height:1px;margin:-1px;overflow:hidden;padding:0;position:absolute;width:1px}[role="banner"] .menu-button.active{opacity:0.5}[role="banner"] .nav-menu-on{max-height:0;overflow:hidden;-moz-transition:all 0.3s ease-out;-o-transition:all 0.3s ease-out;-webkit-transition:all 0.3s ease-out;transition:all 0.3s ease-out;max-height:none;-moz-transition:none;-o-transition:none;-webkit-transition:none;transition:none}[role="banner"] .nav-menu-on.active{max-height:500px}[role="banner"] [role="navigation"]{background:#0C4B33;width:100%;width:auto;float:right}[role="banner"] [role="navigation"] ul{margin:10px 0 0;padding:0;margin:0}[role="banner"] [role="navigation"] li{font-family:"Roboto", Corbel, Avenir, "Lucida Grande", "Lucida Sans", sans-serif;font-size:13px;font-size:1.3rem;display:block;font-weight:700;line-height:16px;text-align:left;text-transform:uppercase;margin:0 10px;border-top:1px solid #106142;margin:0;border:0;float:left;text-align:left}[role="banner"] [role="navigation"] li.active a{color:#44B78B}[role="banner"] [role="navigation"] a{color:#fff;display:block;padding:20px 0px;text-decoration:none;padding:20px 10px}[role="banner"] [role="navigation"] a:active,[role="banner"] [role="navigation"] a:hover{color:#C9F0DD}[role="banner"] [role="navigation"] .nav-primary{position:absolute;right:0;top:45px}.header{font-family:"Roboto", Corbel, Avenir, "Lucida Grande", "Lucida Sans", sans-serif;background:#0C4B33;margin:0;padding:11px 0px 8px;position:relative}.header h1{margin:0.4em 0}.header p{font-family:Palatino, "Palatino Linotype", "Book Antiqua", "Hoefler Text", Georgia, "Lucida Bright", Cambria, Times, "Times New Roman", serif;font-size:18px;font-size:1.8rem;color:#fff;left:-9999px;line-height:1.5;padding:0 0 10px;position:absolute;top:0;max-width:660px;position:static}.copy-banner{background:#44B78B;padding:1px 10px;padding:1px 0}.copy-banner p,.copy-banner h1{font-family:"Roboto", Corbel, Avenir, "Lucida Grande", "Lucida Sans", sans-serif;font-size:24px;font-size:2.4rem;color:#C9F0DD;font-weight:300;line-height:1.3;padding:1px 0 6px;margin:.45em 0 .35em;font-size:32px;font-size:3.2rem;margin:.35em 0 .35em;color:#C9F0DD;padding:1px 0 6px}.copy-banner p em,.copy-banner h1 em{font-style:normal;color:white}.copy-banner p a,.copy-banner h1 a{font-weight:300;color:#C9F0DD}.copy-banner p a.cta,.copy-banner p .cta,.copy-banner h1 a.cta,.copy-banner h1 .cta{margin:0;font-size:18px;font-size:1.8rem}.copy-banner a.cta,.copy-banner .cta{margin:15px 0;padding:0.4em 1.5em 0.5em;background:#2B8C67;background:none;border:1px solid #C9F0DD;color:#C9F0DD;font-weight:400}.copy-banner a.cta:hover,.copy-banner .cta:hover{background:#309c72}.copy-banner a.cta em,.copy-banner .cta em{color:white}.copy-banner a.cta:hover,.copy-banner .cta:hover{background:#F1FFF7;color:#20AA76;border-color:#F1FFF7}.copy-banner a.cta:hover em,.copy-banner .cta:hover em{color:#20AA76}.copy-banner a.cta.white,.copy-banner .cta.white{background:#F1FFF7;color:#20AA76;font-weight:700;border:0}.copy-banner a.cta.white:hover,.copy-banner .cta.white:hover{background:#fff}.homepage .copy-banner{padding:50px 0;background:white;text-align:center;border-bottom:1px solid #CFE3DC}.homepage .copy-banner p{max-width:700px;margin-left:auto;margin-right:auto;margin:1em auto .5em;color:#0C3C26;font-size:36px;font-size:3.6rem}.homepage .copy-banner p.small{color:#798780;margin:2em auto 1em;font-size:14px;font-size:1.4rem}.homepage .copy-banner p em{color:#0C3C26}.homepage .copy-banner a.cta,.homepage .copy-banner .cta{display:inline-block;padding:1em 50px 1.1em;margin-bottom:40px;background:#44B78B;color:white;border:0;font-weight:700}.homepage .copy-banner a.cta:hover,.homepage .copy-banner .cta:hover{background:#51be95}.homepage .copy-banner a.cta:active,.homepage .copy-banner .cta:active{background:#41b085}.homepage .copy-banner .django-companies{max-width:750px;margin:0 auto;list-style:none;padding:0 0 0 30px}.homepage .copy-banner .django-companies li{width:144px;display:inline-block;text-indent:-1000px;overflow:hidden;margin:0}.homepage .copy-banner .django-companies li a{display:block;height:46px;background-position:center;background-repeat:no-repeat}.homepage .copy-banner .django-companies li a.company-mozilla{background-image:url("../img/company-mozilla.png");background-position:center 7px}.mdzr-svg .homepage .copy-banner .django-companies li a.company-mozilla{background-image:url("../img/company-mozilla.svg")}.homepage .copy-banner .django-companies li a.company-pinterest{background-image:url("../img/company-pinterest.png");background-position:center 7px}.mdzr-svg .homepage .copy-banner .django-companies li a.company-pinterest{background-image:url("../img/company-pinterest.svg")}.homepage .copy-banner .django-companies li a.company-theguardian{background-image:url("../img/company-theguardian.png");background-position:center 14px}.mdzr-svg .homepage .copy-banner .django-companies li a.company-theguardian{background-image:url("../img/company-theguardian.svg")}.homepage .copy-banner .django-companies li a.company-instagram{background-image:url("../img/company-instagram.png");background-position:center 10px}.mdzr-svg .homepage .copy-banner .django-companies li a.company-instagram{background-image:url("../img/company-instagram.svg")}.homepage .copy-banner .django-companies li a.company-rdio{background-image:url("../img/company-rdio.png");background-position:15px 6px}.mdzr-svg .homepage .copy-banner .django-companies li a.company-rdio{background-image:url("../img/company-rdio.svg")}[role="alert"]{clear:both;background:#F1FFF7;position:relative;-moz-box-shadow:0 -2px 8px 0 rgba(0,0,0,0.05);-webkit-box-shadow:0 -2px 8px 0 rgba(0,0,0,0.05);box-shadow:0 -2px 8px 0 rgba(0,0,0,0.05)}[role="alert"] a{color:#20AA76;text-decoration:none}[role="alert"] a:visited{color:#20AA76}[role="alert"] a:hover,[role="alert"] a:active,[role="alert"] a:focus{color:#25c488;text-decoration:none}[role="alert"] a.link-readmore{margin:0}[role="alert"] dl{margin:0 10px;padding:25px 0;zoom:1}[role="alert"] dl:before,[role="alert"] dl:after{content:"";display:table}[role="alert"] dl:after{clear:both}[role="alert"] dl dt i.icon{color:#20AA76;margin-right:8px}[role="alert"] dl dd{float:left;width:60%;margin-top:0;font-family:"Roboto", Corbel, Avenir, "Lucida Grande", "Lucida Sans", sans-serif}[role="alert"] dl .link-readmore{margin-left:10px}[role="alert"] dt{float:left;width:31%;padding-right:2%}[role="alert"] dl{margin:0}[role="contentinfo"]{zoom:1;font-family:"Roboto", Corbel, Avenir, "Lucida Grande", "Lucida Sans", sans-serif;position:relative;background:#44B78B;clear:both;margin-top:0px}[role="contentinfo"]:before,[role="contentinfo"]:after{content:"";display:table}[role="contentinfo"]:after{clear:both}[role="contentinfo"] .container{overflow:hidden}[role="contentinfo"] .subfooter{zoom:1;padding:0 10px;padding:0}[role="contentinfo"] .subfooter:before,[role="contentinfo"] .subfooter:after{content:"";display:table}[role="contentinfo"] .subfooter:after{clear:both}[role="contentinfo"] .subfooter .col{float:left;margin-bottom:-999px;padding:0 3% 999px 0;width:30%}[role="contentinfo"] .subfooter .col:first-child h2{border-top:0}[role="contentinfo"] .subfooter .col:first-child{margin-left:0;padding-left:0}[role="contentinfo"] .subfooter .col.last-child{margin-right:0;padding-right:0}[role="contentinfo"] h2{font-size:16px;font-size:1.6rem;border-top:1px solid #CFE3DC;color:#fff;font-weight:700;margin-top:20px;padding:30px 0 10px;border:none;margin-top:0}[role="contentinfo"] ul{font-size:14px;font-size:1.4rem;font-weight:400;list-style:none;margin:15px 0 0 0;padding:0 0 30px}[role="contentinfo"] ul li{margin:10px 0 0;padding:0}[role="contentinfo"] ul a{color:#F1FFF7;text-decoration:none}[role="contentinfo"] ul a:hover,[role="contentinfo"] ul a:active,[role="contentinfo"] ul a:focus{text-decoration:underline}[role="contentinfo"] .footer{background:#0C4B33;margin-top:20px;padding:10px 0 30px;color:#2B8C67}[role="contentinfo"] .footer .footer-logo{float:left;width:33%}[role="contentinfo"] .footer .logo{margin-right:0;margin-top:28px}.mdzr-svg [role="contentinfo"] .footer .logo{background-position:left center;margin-right:0;margin-top:20px}[role="contentinfo"] .logo{font-size:40px;font-size:4rem;font-family:"Roboto", Corbel, Avenir, "Lucida Grande", "Lucida Sans", sans-serif;background:url(../img/logo-django.png) 0 0 no-repeat;color:#0C4B33;display:block;font-weight:700;height:50px;margin:10px;overflow:hidden;text-decoration:none;text-indent:100%;width:142px;float:left;margin:20px 90px 0 0}.mdzr-svg [role="contentinfo"] .logo{background:url(../img/logo-django.svg) center center no-repeat;height:39px;width:109px}[role="contentinfo"] .thanks{font-size:12px;font-size:1.2rem;color:#2B8C67;margin:0;padding:0;border:none}[role="contentinfo"] .thanks li{zoom:1;margin:0;padding:17px 10px 11px;display:block;clear:both;background:none;float:left;clear:none;padding:0 3% 0 0;width:30%}[role="contentinfo"] .thanks li:before,[role="contentinfo"] .thanks li:after{content:"";display:table}[role="contentinfo"] .thanks li:after{clear:both}[role="contentinfo"] .thanks li.design span.ampersand,[role="contentinfo"] .thanks li.design a{display:inline-block;vertical-align:top}[role="contentinfo"] .thanks li.design span.ampersand.threespot,[role="contentinfo"] .thanks li.design a.threespot{clear:both}[role="contentinfo"] .thanks li.design span.ampersand.ampersand,[role="contentinfo"] .thanks li.design a.ampersand{position:relative;top:6px;margin:0 6px;line-height:36px}[role="contentinfo"] .thanks li span.ampersand{line-height:24px}[role="contentinfo"] .thanks span{display:block;height:24px;line-height:36px;padding-right:12px;white-space:nowrap}[role="contentinfo"] .thanks a{display:block;height:33px;overflow:hidden;text-indent:-200px;width:94px;clear:both;margin-top:5px}[role="contentinfo"] .thanks a.rackspace{background:url(../img/logo-rackspace.png) no-repeat left center}.mdzr-svg [role="contentinfo"] .thanks a.rackspace{background:url(../img/logo-rackspace.svg) no-repeat left center}[role="contentinfo"] .thanks a.threespot{background:url(../img/logo-threespot.png) no-repeat left center}.mdzr-svg [role="contentinfo"] .thanks a.threespot{background:url(../img/logo-threespot.svg) no-repeat left center}[role="contentinfo"] .thanks a.andrevv{background:url(../img/logo-andrevv.png) no-repeat left center}.mdzr-svg [role="contentinfo"] .thanks a.andrevv{background:url(../img/logo-andrevv.svg) no-repeat left center}[role="contentinfo"] .copyright{font-size:12px;font-size:1.2rem;clear:both;margin:20px 0 0 10px;max-width:80%;padding-top:30px;margin:0}[role="contentinfo"] .copyright a{color:#2B8C67}.backtotop{color:#20AA76;text-decoration:none;font-family:"Roboto", Corbel, Avenir, "Lucida Grande", "Lucida Sans", sans-serif;font-size:14px;font-size:1.4rem;display:block;font-weight:700;margin:10px 0;padding:10px 0;text-align:center;text-transform:uppercase;display:none}.backtotop:visited{color:#20AA76}.backtotop:hover,.backtotop:active,.backtotop:focus{color:#25c488;text-decoration:none}.cta,a.cta{font-family:"Roboto", Corbel, Avenir, "Lucida Grande", "Lucida Sans", sans-serif;font-weight:700;-webkit-appearance:none;-moz-appearance:none;background:#44B78B;border:none;-moz-border-radius:5px;-webkit-border-radius:5px;border-radius:5px;color:#fff;display:block;-webkit-font-smoothing:subpixel-antialiased;-moz-osx-font-smoothing:auto;margin:30px auto 0;padding:1em 1.5em;text-align:center;text-decoration:none;margin:20px auto;max-width:400px}.cta em,a.cta em{color:#C9F0DD;font-style:normal}.cta:hover,.cta:focus,a.cta:hover,a.cta:focus{background:#51be95}.cta:active,a.cta:active{background:#41b085}[role="complementary"] .cta,[role="complementary"] a.cta{font-size:16px;font-size:1.6rem}[role="complementary"] .cta+.link-readmore,[role="complementary"] a.cta+.link-readmore{display:block;text-align:center;margin-top:-10px}.cta.outline,a.cta.outline{background:none;border:1px solid #CFE3DC;color:#859D94;font-weight:400}.cta.outline em,a.cta.outline em{color:#0C4B33}.cta.outline:hover,a.cta.outline:hover{border-color:#20AA76;color:#20AA76}.cta.outline:hover em,a.cta.outline:hover em{color:#20AA76}.cta.outline:active,a.cta.outline:active{border-color:#44B78B;color:#44B78B}.cta.outline:active em,a.cta.outline:active em{color:#44B78B}.link-green{color:#20AA76;text-decoration:none}.link-green:visited{color:#20AA76}.link-green:hover,.link-green:active,.link-green:focus{color:#25c488;text-decoration:none}.link-readmore{color:#20AA76;text-decoration:none;font-family:"Roboto", Corbel, Avenir, "Lucida Grande", "Lucida Sans", sans-serif;font-size:14px;font-size:1.4rem;display:inline-block;margin:10px 0;display:inline;zoom:1;font-weight:700;text-transform:uppercase}.link-readmore:visited{color:#20AA76}.link-readmore:hover,.link-readmore:active,.link-readmore:focus{color:#25c488;text-decoration:none}.link-readmore:after{content:" ›";font-size:1.2em}.link-readmore.back-link:after{content:""}.link-readmore.back-link:before{content:"‹ ";font-size:1.2em}[role="complementary"] .link-readmore{font-size:12px;font-size:1.2rem}.meta,.list-links dd{font-family:"Roboto", Corbel, Avenir, "Lucida Grande", "Lucida Sans", sans-serif;font-size:14px;font-size:1.4rem;display:block;line-height:1.3;margin:25px 0 20px}.meta a,.list-links dd a{color:#20AA76;text-decoration:none}.meta a:visited,.list-links dd a:visited{color:#20AA76}.meta a:hover,.list-links dd a:hover,.meta a:active,.list-links dd a:active,.meta a:focus,.list-links dd a:focus{color:#25c488;text-decoration:none}.layout-2col{margin:20px 0;zoom:1;margin:0}.layout-2col:before,.layout-2col:after{content:"";display:table}.layout-2col:after{clear:both}.layout-2col .col{float:left;width:46%;margin:0 4%}.layout-2col .col:first-child{margin-left:0}.layout-2col .col.last-child{margin-right:0}.layout-2col .one-third{width:29%}.layout-2col .two-third{width:62%}.blue{color:#20AA76}.label{font-family:"Roboto", Corbel, Avenir, "Lucida Grande", "Lucida Sans", sans-serif;font-size:16px;font-size:1.6rem;color:#cacfcc;display:block;font-weight:700;margin:20px 0 10px;text-transform:uppercase}.label.form-controls{font-size:14px;font-size:1.4rem;display:block;margin:0;position:relative;text-align:left}.label.form-controls span{cursor:pointer}.label.form-controls span:hover,.label.form-controls span:active,.label.form-controls span:focus{color:#afb7b3}.callout-right{float:right;margin:26px 0 0 35px;width:33%}.callout-right.two-thirds{width:60%}.callout-right img{display:block;max-width:100%}.callout-left{float:left;margin:26px 35px 0 0;width:33%}.callout-left.two-thirds{width:60%}.callout-left img{display:block;max-width:100%}.codedump{background:#f8f8f8;border:1px solid #CFE3DC;padding:10px;-moz-border-radius:4px;-webkit-border-radius:4px;border-radius:4px;font-family:"Roboto", Corbel, Avenir, "Lucida Grande", "Lucida Sans", sans-serif;font-size:14px;font-size:1.4rem;line-height:1.6em}.list-events{font-family:"Roboto", Corbel, Avenir, "Lucida Grande", "Lucida Sans", sans-serif;list-style:none;margin:0;padding:0}.list-events li{font-size:18px;font-size:1.8rem;border-top:1px solid #CFE3DC;display:block;line-height:1.3;margin:0;padding:20px 0 0 30px;position:relative}.list-events li i{font-size:16px;font-size:1.6rem;color:#93D7B7;display:block;left:0;line-height:20px;height:30px;position:absolute;text-align:center;top:20px;width:24px}.list-events li:first-child{border-top:0;padding-top:0}.list-events li:first-child i{top:0px}.list-events .meta,.list-events .list-links dd,.list-links .list-events dd{font-family:"Roboto", Corbel, Avenir, "Lucida Grande", "Lucida Sans", sans-serif;font-size:14px;font-size:1.4rem;display:block;margin-top:10px}.list-events a{color:#20AA76;text-decoration:none}.list-events a:visited{color:#20AA76}.list-events a:hover,.list-events a:active,.list-events a:focus{color:#25c488;text-decoration:none}.list-tags{font-family:"Roboto", Corbel, Avenir, "Lucida Grande", "Lucida Sans", sans-serif;font-size:12px;font-size:1.2rem;font-weight:700;list-style:none;margin:0;padding:0;text-transform:uppercase}.list-tags li{margin-top:10px;display:block;line-height:28px}.list-tags a{background:#93D7B7;color:#F1FFF7;display:inline-block;display:block;line-height:1.2;margin:0;padding:8px 10px 5px;text-decoration:none}.list-tags a:hover,.list-tags a:active,.list-tags a:focus{background-color:#44B78B;color:#fff}.list-news{list-style:none;margin:0;padding:0}.list-news h2{font-weight:400;margin-bottom:5px}.list-news li{border-top:1px solid #CFE3DC;margin-top:35px;padding-top:10px}.list-news li:first-child{border:none;margin-top:0;padding-top:0}.list-news .meta,.list-news .list-links dd,.list-links .list-news dd{margin-top:10px;color:#859D94}.list-news .meta a:link,.list-news .list-links dd a:link,.list-links .list-news dd a:link{color:#798780;text-decoration:underline}.list-case-study{zoom:1;list-style:none;margin:0;padding:0 0 10px}.list-case-study:before,.list-case-study:after{content:"";display:table}.list-case-study:after{clear:both}.list-case-study p{font-size:14px;font-size:1.4rem;margin:10px 0 5px}.list-case-study li{border-top:1px solid #CFE3DC;margin-top:20px;padding-top:20px}.list-case-study li>a{font-family:"Roboto", Corbel, Avenir, "Lucida Grande", "Lucida Sans", sans-serif;color:#20AA76;text-decoration:none;font-size:12px;font-size:1.2rem;font-weight:700;margin-top:10px;text-transform:uppercase}.list-case-study li>a:visited{color:#20AA76}.list-case-study li>a:hover,.list-case-study li>a:active,.list-case-study li>a:focus{color:#25c488;text-decoration:none}.list-case-study li>a:after{content:" ›";font-size:1.2em}.list-case-study h3{margin:10px 0 20px;padding:0}.list-case-study h3.logo{text-indent:-1000%;overflow:hidden}.list-case-study [title="Knight Foundation"]{background:url(../img/logo-knight.png) no-repeat bottom left;height:25px;padding-top:7px;width:190px}.mdzr-svg .list-case-study [title="Knight Foundation"]{background:url(../img/logo-knight.svg) no-repeat bottom left}.list-case-study [title="Mozilla"]{background:url(../img/logo-mozilla.png) no-repeat bottom left;height:32px;width:120px}.mdzr-svg .list-case-study [title="Mozilla"]{background:url(../img/logo-mozilla.svg) no-repeat bottom left}.list-case-study [title="Disqus"]{background:url(../img/logo-disqus.png) no-repeat bottom left;height:28px;padding-top:4px;width:140px}.mdzr-svg .list-case-study [title="Disqus"]{background:url(../img/logo-disqus.svg) no-repeat bottom left}.list-case-study.single-col li{margin-top:0;margin-bottom:30px;width:auto}.list-case-study.single-col li p{font-size:18px;font-size:1.8rem;margin-right:40px}.list-case-study.single-col li h3{margin-top:20px}.case-study-logo{max-width:50%;max-height:70px;height:auto;margin:40px 0 0}.list-link-soup{font-family:"Roboto", Corbel, Avenir, "Lucida Grande", "Lucida Sans", sans-serif;zoom:1;font-size:16px;font-size:1.6rem;border-top:1px solid #CFE3DC;list-style:none;margin:20px 0 0;padding:20px 0 10px}.list-link-soup:before,.list-link-soup:after{content:"";display:table}.list-link-soup:after{clear:both}.list-link-soup li{float:left;margin:10px 5% 0 0;width:45%;margin-right:3%;width:30%}.list-link-soup a{color:#20AA76;text-decoration:none}.list-link-soup a:visited{color:#20AA76}.list-link-soup a:hover,.list-link-soup a:active,.list-link-soup a:focus{color:#25c488;text-decoration:none}[role="complementary"] .list-link-soup li{float:none}h2+.list-link-soup{border-top:0}.list-features{margin:50px 0 40px;padding-bottom:40px}.list-features dt{font-size:24px;font-size:2.4rem;border-top:1px solid #CFE3DC;padding-top:25px}.list-features i{color:#F1FFF7;margin-right:10px;width:40px;height:40px;-moz-border-radius:25px;-webkit-border-radius:25px;border-radius:25px;background:#20AA76;line-height:1.68em;display:inline-block;text-align:center}.list-features i.icon-briefcase{line-height:1.7em}.list-features i.icon-dashboard{line-height:1.5em}.list-features dt{margin-top:60px;padding:60px 0 0 245px;position:relative}.list-features dt:first-child{margin-top:20px}.list-features dt.even{padding-left:0;padding-right:245px}.list-features dt.even i{left:auto !important;right:0}.list-features dd{padding-left:245px;min-height:140px}.list-features dd.even{padding-left:0;padding-right:245px}.list-features i{font-size:120px;font-size:12rem;display:block;height:200px;left:0;position:absolute;text-align:center;top:60px;width:200px;margin-right:0}.mdzr-borderradius .list-features i{background:#44B78B;-moz-border-radius:100px;-webkit-border-radius:100px;border-radius:100px;color:#fff}.mdzr-svg .list-features i{background:url(../img/bg-features.svg) no-repeat center center}.mdzr-svg .list-features i.icon-bolt{background-position:-150px -269px}.mdzr-svg .list-features i.icon-briefcase{background-position:-354px -7px}.mdzr-svg .list-features i.icon-lock{background-position:-36px -96px}.mdzr-svg .list-features i.icon-dashboard{background-position:-270px -9px}.mdzr-svg .list-features i.icon-cogs{background-position:-334px -12px}.mdzr-svg.mdzr-borderradius.mdzr-cssanimations .list-features i{-moz-transition:all 0.3s ease-out;-o-transition:all 0.3s ease-out;-webkit-transition:all 0.3s ease-out;transition:all 0.3s ease-out;-moz-transform:rotate(0.5turn);-ms-transform:rotate(0.5turn);-webkit-transform:rotate(0.5turn);transform:rotate(0.5turn)}.mdzr-svg.mdzr-borderradius.mdzr-cssanimations .list-features i.inview{-moz-transform:rotate(0turn);-ms-transform:rotate(0turn);-webkit-transform:rotate(0turn);transform:rotate(0turn)}.mdzr-svg.mdzr-borderradius.mdzr-cssanimations .list-features i.icon-bolt{background-position:40px -369px}.mdzr-svg.mdzr-borderradius.mdzr-cssanimations .list-features i.icon-bolt.inview{background-position:-150px -269px}.mdzr-svg.mdzr-borderradius.mdzr-cssanimations .list-features i.icon-briefcase{background-position:-494px 207px}.mdzr-svg.mdzr-borderradius.mdzr-cssanimations .list-features i.icon-briefcase.inview{background-position:-354px -7px}.mdzr-svg.mdzr-borderradius.mdzr-cssanimations .list-features i.icon-lock{background-position:144px -206px}.mdzr-svg.mdzr-borderradius.mdzr-cssanimations .list-features i.icon-lock.inview{background-position:-36px -96px}.mdzr-svg.mdzr-borderradius.mdzr-cssanimations .list-features i.icon-dashboard{background-position:-360px 201px}.mdzr-svg.mdzr-borderradius.mdzr-cssanimations .list-features i.icon-dashboard.inview{background-position:-270px -9px}.mdzr-svg.mdzr-borderradius.mdzr-cssanimations .list-features i.icon-cogs{background-position:-500px -180px}.mdzr-svg.mdzr-borderradius.mdzr-cssanimations .list-features i.icon-cogs.inview{background-position:-334px -12px}.list-features i :-o-prefocus,.list-features i{background:#44B78B !important;-moz-transition:none !important;-o-transition:none !important;-webkit-transition:none !important;transition:none !important;-moz-transform:none !important;-ms-transform:none !important;-webkit-transform:none !important;transform:none !important}.homepage .list-features{padding-bottom:0}.homepage .list-features dl{padding-top:0}.homepage .list-features i{color:#F1FFF7;margin-right:10px;width:40px;height:40px;top:10px;-moz-border-radius:20px;-webkit-border-radius:20px;border-radius:20px;background:#20AA76;display:inline-block;text-align:center;font-size:24px;font-size:2.4rem}.mdzr-svg.mdzr-borderradius.mdzr-cssanimations .homepage .list-features i{-moz-transition:none;-o-transition:none;-webkit-transition:none;transition:none;-moz-transform:rotate(0);-ms-transform:rotate(0);-webkit-transform:rotate(0);transform:rotate(0)}.homepage .list-features dt{padding:20px 0 0px 110px;font-size:18px;font-size:1.8rem;border-top:0;margin-top:0}.homepage .list-features dd{padding:0 60px 20px 110px;min-height:0}.homepage .list-features dd p{margin-top:0;font-size:18px;font-size:1.8rem}.homepage .list-features i{margin-right:10px;width:80px;height:80px;top:20px;-moz-border-radius:40px;-webkit-border-radius:40px;border-radius:40px;font-size:46px;font-size:4.6rem}.list-collapsing-header{float:left}.section .list-collapsing-header h2{margin:40px 0 20px}.list-collapsing-header+.form-controls.label{margin:50px 0 0;text-align:right}.form-controls.label{float:right}.list-collapsing{border-bottom:1px solid #CFE3DC;list-style:none;margin:30px 0;padding:0;clear:both}.list-collapsing.active>li{border-top:1px solid #CFE3DC;margin:0;padding:0}.list-collapsing.active>li.active h2 .collapsing-icon:before{content:"\f068"}.list-collapsing.active h2{font-size:18px;font-size:1.8rem;cursor:pointer;margin:0;padding:18px 40px 18px 0;position:relative}.list-collapsing.active h2:hover,.list-collapsing.active h2:focus,.list-collapsing.active h2:active{color:#1d915c;outline:none}.list-collapsing.active h2 .collapsing-icon{position:absolute;right:0;top:24px}.list-collapsing.active h2 .collapsing-icon:before{content:"\f067"}.list-collapsing.active h2.bullet-icon{padding-left:1.5em}.list-collapsing.active h2.bullet-icon>i:first-child{position:absolute;top:24px;left:0}.list-collapsing.active .collapsing-content{overflow:hidden;max-height:0px;-moz-transition:all 0.5s ease-out;-o-transition:all 0.5s ease-out;-webkit-transition:all 0.5s ease-out;transition:all 0.5s ease-out}.list-collapsing.active li.active .collapsing-content{max-height:1000px;overflow:auto}.list-image{list-style:none;margin:0;padding:0}.list-image li{border-top:1px solid #CFE3DC;margin-top:20px;padding-top:40px;zoom:1;margin-top:20px;padding-top:40px}.list-image li:before,.list-image li:after{content:"";display:table}.list-image li:after{clear:both}.list-image li:first-child{border:none;padding-top:0}.list-image a{text-decoration:none}.list-image a:hover,.list-image a:active,.list-image a:focus{color:#798780}.list-image a.link-readmore{margin:0}.list-image img{display:block;margin:0 auto 25px;max-width:100%}.list-image img{float:left;margin:0 40px 0 0;max-width:200px;max-height:200px}.list-image h2,.list-image h3,.list-image h4,.list-image p{padding-left:240px}.list-image h3{margin-top:10px}.layout-secondary .list-image img,[role="complementary"] .list-image img{float:left;max-width:40%;max-height:120px;margin:0 20px 10px 0}.layout-secondary .list-image h2,.layout-secondary .list-image h3,.layout-secondary .list-image h4,.layout-secondary .list-image p,[role="complementary"] .list-image h2,[role="complementary"] .list-image h3,[role="complementary"] .list-image h4,[role="complementary"] .list-image p{padding-left:0px;border:0}.layout-secondary .list-image h3,[role="complementary"] .list-image h3{font-size:14px;font-size:1.4rem;margin-top:0;margin-bottom:1em;padding-bottom:0;font-family:"Roboto", Corbel, Avenir, "Lucida Grande", "Lucida Sans", sans-serif;font-weight:700}#s-django-documentation,#s-feed{font-family:"Roboto", Corbel, Avenir, "Lucida Grande", "Lucida Sans", sans-serif}.list-outline{font-size:16px;font-size:1.6rem;line-height:1.3;list-style:none;margin:0;padding:0}.list-outline a{text-decoration:none}.list-outline>li>ul>li{margin-top:12px}.list-outline>li>ul>li:first-child{margin-top:6px}.list-outline>li>a{text-transform:uppercase;font-weight:700;color:#20AA76;text-decoration:none}.list-outline>li>a:visited{color:#20AA76}.list-outline>li>a:hover,.list-outline>li>a:active,.list-outline>li>a:focus{color:#25c488;text-decoration:none}.list-outline>li>ul{list-style:none;font-size:12px;font-size:1.2rem;padding:5px 0 0 10px}.list-outline>li>ul>li>a{font-weight:700;text-transform:uppercase;color:#20AA76;text-decoration:none}.list-outline>li>ul>li>a:visited{color:#20AA76}.list-outline>li>ul>li>a:hover,.list-outline>li>ul>li>a:active,.list-outline>li>ul>li>a:focus{color:#25c488;text-decoration:none}.list-outline>li>ul>li ul{font-size:14px;font-size:1.4rem;padding:0 0 0 20px}.section h2{margin:50px 0 30px}.section h3{margin:40px 0 20px}.headerlink{opacity:0;padding-left:10px;font-size:0.8em;position:relative;top:-0.17em;font-weight:700;text-decoration:none;-moz-transition:opacity 200ms ease-in-out;-o-transition:opacity 200ms ease-in-out;-webkit-transition:opacity 200ms ease-in-out;transition:opacity 200ms ease-in-out}h1:hover>.headerlink,h2:hover>.headerlink,h3:hover>.headerlink,h4:hover>.headerlink,h5:hover>.headerlink,h6:hover>.headerlink,dl:hover>.headerlink,dt:hover>.headerlink{opacity:1}.note,.admonition,.help-block{background:#F1FFF7;padding:15px 20px 15px 70px;border:1px solid #C9F0DD;-moz-border-radius:4px;-webkit-border-radius:4px;border-radius:4px;margin:25px 0;position:relative}.note h1,.note h2,.note h3,.note h4,.admonition h1,.admonition h2,.admonition h3,.admonition h4,.help-block h1,.help-block h2,.help-block h3,.help-block h4{margin-top:20px}.note p,.admonition p,.help-block p{margin:0.8em 0}.note .first,.admonition .first,.help-block .first{margin-top:0}.note .admonition-title,.admonition .admonition-title,.help-block .admonition-title{font-weight:bold}.note .admonition-title::before,.admonition .admonition-title::before,.help-block .admonition-title::before{display:inline-block;font:normal normal normal 14px/1 FontAwesome;font-size:inherit;text-rendering:auto;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;position:absolute;top:20px;left:20px;font-size:30px;width:34px;text-align:center;content:"";opacity:0.5}.note.warning,.admonition.warning,.help-block.warning{background-color:#FFFDF1;border-color:#F5F1C7}.note.warning .admonition-title::before,.admonition.warning .admonition-title::before,.help-block.warning .admonition-title::before{color:#E9BD46;content:""}.note.admonition-philosophy .admonition-title::before,.admonition.admonition-philosophy .admonition-title::before,.help-block.admonition-philosophy .admonition-title::before{content:""}.note.admonition-behind-the-scenes .admonition-title::before,.admonition.admonition-behind-the-scenes .admonition-title::before,.help-block.admonition-behind-the-scenes .admonition-title::before{content:""}.note .last,.note .highlight,.admonition .last,.admonition .highlight,.help-block .last,.help-block .highlight{margin-bottom:0px}.browse-horizontal{font-family:"Roboto", Corbel, Avenir, "Lucida Grande", "Lucida Sans", sans-serif;font-size:14px;font-size:1.4rem;zoom:1;font-weight:700;border-top:1px solid #CFE3DC;border-bottom:1px solid #CFE3DC;padding:20px 0;margin-top:2em}.browse-horizontal:before,.browse-horizontal:after{content:"";display:table}.browse-horizontal:after{clear:both}.browse-horizontal .left{float:left}.browse-horizontal .left .icon{margin-right:4px;font-size:12px;font-size:1.2rem}.browse-horizontal .right{float:right}.browse-horizontal .right .icon{margin-left:4px;font-size:12px;font-size:1.2rem}.browse-horizontal a{text-decoration:none}#doc-versions{position:fixed;right:15px;bottom:15px;margin:0;padding:0;z-index:1;list-style:none}#doc-versions .icon{margin-right:4px}#doc-versions li{display:none;background:#F1FFF7;margin:0 3px;font-family:"Roboto", Corbel, Avenir, "Lucida Grande", "Lucida Sans", sans-serif;color:#0C3C26;font-size:12px;font-size:1.2rem}#doc-versions li.current{display:inline-block;padding:8px 15px;border:1px solid #CFE3DC;-moz-border-radius:4px;-webkit-border-radius:4px;border-radius:4px}#doc-versions li a{display:inline-block;color:#44B78B;text-decoration:none;font-weight:700;padding:8px 15px;border:1px solid #CFE3DC;-moz-border-radius:4px;-webkit-border-radius:4px;border-radius:4px}#doc-versions li a:hover{color:#20AA76;border:1px solid #93D7B7}#doc-versions:hover li,#doc-versions .hover-on li{display:inline-block}#dev-warning,#outdated-warning{position:absolute;top:0;width:100%;padding:8px 20px 8px;-moz-box-sizing:border-box;-webkit-box-sizing:border-box;box-sizing:border-box;background-image:-webkit-linear-gradient(-45deg, rgba(0,0,0,0.04) 25%, transparent 25%, transparent 50%, rgba(0,0,0,0.04) 50%, rgba(0,0,0,0.04) 75%, transparent 75%, transparent);background-image:-moz-linear-gradient(-45deg, rgba(0,0,0,0.04) 25%, transparent 25%, transparent 50%, rgba(0,0,0,0.04) 50%, rgba(0,0,0,0.04) 75%, transparent 75%, transparent);background-image:linear-gradient(135deg, rgba(0,0,0,0.04) 25%,rgba(0,0,0,0) 25%,rgba(0,0,0,0) 50%,rgba(0,0,0,0.04) 50%,rgba(0,0,0,0.04) 75%,rgba(0,0,0,0) 75%,rgba(0,0,0,0));font-family:"Roboto", Corbel, Avenir, "Lucida Grande", "Lucida Sans", sans-serif;font-size:14px;text-align:center;background-color:#ffe761;position:fixed;min-width:768px}#outdated-warning{background-color:#ffbaba;color:#6A0E0E}#s-getting-help{display:none}#docs-content{font-family:"Roboto", Corbel, Avenir, "Lucida Grande", "Lucida Sans", sans-serif;font-size:14px;font-size:1.4rem;line-height:1.5em}#docs-content h1,#docs-content h2,#docs-content h3,#docs-content h4,#docs-content h5,#docs-content h6{margin:0.6em 0;line-height:1.1em}#docs-content .section{padding:10px 0 20px}#docs-content img{display:block;max-width:100%}#docs-content a.reference{color:#6A0E0E;text-decoration:none;border-bottom:1px dotted #798780}#docs-content a.reference:visited{border-color:#971414}#docs-content a.reference:active,#docs-content a.reference:focus,#docs-content a.reference:hover{background:#F1FFF7;color:#BA2121}#docs-content a.reference em{font-style:normal}.versionadded,.versionchanged,.deprecated,.versionmodified{font-family:"Roboto", Corbel, Avenir, "Lucida Grande", "Lucida Sans", sans-serif;font-weight:bold;margin-bottom:20px;padding:10px 13px;border:1px solid #C9F0DD;-moz-border-radius:4px;-webkit-border-radius:4px;border-radius:4px}.versionadded p,.versionchanged p,.deprecated p,.versionmodified p{font-weight:normal;margin-top:0.3em}.versionadded p:last-child,.versionchanged p:last-child,.deprecated p:last-child,.versionmodified p:last-child{margin-bottom:0}.deprecated .versionadded,.deprecated .versionchanged,.deprecated .deprecated,.deprecated .versionmodified{border:none;padding:0;margin-bottom:0;display:block}.deprecated p{font-weight:normal;margin-top:0}.deprecated p:last-child{margin-bottom:0}dl.function dt,dl.class dt,dl.method dt,dl.attribute dt{font-weight:700}dl.function dd,dl.class dd,dl.method dd,dl.attribute dd{padding-left:1.4em}table.docutils td,table.docutils th{border-bottom:1px solid #CFE3DC}#search-results span.highlighted{font-weight:700;color:#0C3C26}.list-links{font-family:"Roboto", Corbel, Avenir, "Lucida Grande", "Lucida Sans", sans-serif;list-style:none;margin:0;padding:10px 0 0}.list-links a{color:#20AA76;text-decoration:none}.list-links a:visited{color:#20AA76}.list-links a:hover,.list-links a:active,.list-links a:focus{color:#25c488;text-decoration:none}.list-links dt,.list-links li{font-size:16px;font-size:1.6rem;margin-top:15px;font-weight:400}.list-links dt:first-child,.list-links li:first-child{margin-top:0}.list-links dd{margin-top:0;margin-bottom:30px}[role="complementary"] .list-links{padding:0}[role="complementary"] .list-links dt,[role="complementary"] .list-links li{font-size:16px;font-size:1.6rem;border-top:1px solid #CFE3DC;margin-top:0;padding-top:20px}[role="complementary"] .list-links dt:first-child,[role="complementary"] .list-links li:first-child{border:none;padding-top:0}[role="complementary"] .list-links li{padding:14px 0 10px}[role="complementary"] .list-links dd{font-size:14px;font-size:1.4rem;margin-bottom:16px}.list-links+h2{margin-top:34px}.list-links-small{padding-left:0;list-style:none}.list-links-small a{color:#20AA76;text-decoration:none;text-decoration:none}.list-links-small a:visited{color:#20AA76}.list-links-small a:hover,.list-links-small a:active,.list-links-small a:focus{color:#25c488;text-decoration:none}.list-links-small li>a:before,.list-links-small dt>a:before{font-family:FontAwesome;font-weight:normal;font-style:normal;float:left;width:23px;height:20px}.list-links-small dt{font-weight:400}.list-links-small dd{color:#798780;padding-top:2px}.list-links-small.docs-list{list-style:none}.list-links-small.docs-list li>a:before,.list-links-small.docs-list dt>a:before{content:"\f0f6"}.list-links-small.docs-list dd{padding-left:24px}.list-links-small.news-list{list-style:none}.list-links-small.news-list li>a:before,.list-links-small.news-list dt>a:before{content:"\f0a1"}.list-links-small.news-list dt.event>a:before{content:"\f133"}.list-links-small.news-list dd{padding-left:24px}.list-links-small.resource-list{list-style:none}.list-links-small.resource-list li>a:before,.list-links-small.resource-list dt>a:before{content:"\f0c1"}.list-links-small.resource-list dd{padding-left:24px}.list-links-small.rss-list{list-style:none}.list-links-small.rss-list li>a:before,.list-links-small.rss-list dt>a:before{content:"\f09e"}.list-links-small.rss-list dd{padding-left:24px}form{font-family:"Roboto", Corbel, Avenir, "Lucida Grande", "Lucida Sans", sans-serif;font-size:16px;font-size:1.6rem}form input[type="search"],form input[type="text"],form input[type="email"],form input[type="password"],form input[type="url"],form textarea{font-family:"Roboto", Corbel, Avenir, "Lucida Grande", "Lucida Sans", sans-serif;font-size:16px;font-size:1.6rem;-webkit-appearance:none;-moz-appearance:none;background:#fff;border:1px solid #CFE3DC;-moz-border-radius:4px;-webkit-border-radius:4px;border-radius:4px;cursor:auto;display:block;font-weight:400;height:30px;margin:10px 0px;padding:6px 14% 8px 10px;text-indent:0;vertical-align:middle;width:82%;padding:6px 18% 8px 10px;width:80%}form input[type="search"]::-ms-clear,form input[type="text"]::-ms-clear,form input[type="email"]::-ms-clear,form input[type="password"]::-ms-clear,form input[type="url"]::-ms-clear,form textarea::-ms-clear{display:none}form input[type="search"]:active,form input[type="search"]:focus,form input[type="text"]:active,form input[type="text"]:focus,form input[type="email"]:active,form input[type="email"]:focus,form input[type="password"]:active,form input[type="password"]:focus,form input[type="url"]:active,form input[type="url"]:focus,form textarea:active,form textarea:focus{outline:none;border-color:#20AA76}form textarea{height:auto}form input[type=checkbox],form input[type=radio]{margin-right:6px}form select{border:1px solid #CFE3DC;background:white;height:46px;padding:0 10px;-moz-border-radius:4px;-webkit-border-radius:4px;border-radius:4px;font-size:16px;font-size:1.6rem}[role="complementary"] form select{height:36px;font-size:14px;font-size:1.4rem}form button{-moz-appearance:none;-webkit-appearance:none;background:#20AA76;-moz-border-radius:4px;-webkit-border-radius:4px;border-radius:4px;color:white;border:0;height:46px;padding:0 15px;font-family:"Roboto", Corbel, Avenir, "Lucida Grande", "Lucida Sans", sans-serif;font-size:16px;font-size:1.6rem}form button:hover{background:#44B78B}.form-general fieldset{max-width:700px;border:0;padding:0;margin:15px 0}.form-general fieldset input[type="search"],.form-general fieldset input[type="text"],.form-general fieldset input[type="email"],.form-general fieldset input[type="password"],.form-general fieldset input[type="url"]{margin:10px 0}.form-input{min-height:40px;margin:30px 0 20px;position:relative}.form-input:focus{background:#000}.form-input button{background:none;border:none;color:#44B78B;height:40px;padding:0;position:absolute;right:2%;top:6%;width:40px;right:1%}.form-input button i{font-size:20px;font-size:2rem;line-height:1}.form-input button:hover,.form-input button:focus,.form-input button:active{background:none;color:#0C4B33;outline:none}[role="complementary"] .form-input{min-height:30px;margin:20px 0 30px}[role="complementary"] .form-input input[type="search"],[role="complementary"] .form-input input[type="text"],[role="complementary"] .form-input input[type="email"]{height:20px;font-size:14px;font-size:1.4rem}[role="complementary"] .form-input button{height:30px;width:30px;top:3px;right:0}[role="complementary"] .form-input button i{font-size:20px;font-size:2rem}form.donate{max-width:150px}form.donate label{position:absolute;left:0px;color:#2B8C67;padding-top:0.3em;padding-left:0.5em}form.donate input[type=text]{padding-left:20px;padding-right:9px}div[role=main] form.donate label{padding-top:0.7em}::-webkit-input-placeholder,:-moz-placeholder,::-moz-placeholder,:-ms-input-placeholder{color:#859D94}.form-email h3{font-size:18px;font-size:1.8rem;margin:10px 0}.form-email .meta,.form-email .list-links dd,.list-links .form-email dd{margin:0}.form-email form{margin:10px 0 30px}.nav-pagination{font-family:"Roboto", Corbel, Avenir, "Lucida Grande", "Lucida Sans", sans-serif;font-size:14px;font-size:1.4rem;border-top:1px solid #CFE3DC;font-weight:700;line-height:31px;list-style:none;margin:30px 0;padding:30px 0 0;text-align:center}.nav-pagination li{display:inline-block;display:inline;zoom:1}.nav-pagination a{border:none;color:#798780;height:auto;width:auto;margin:0 5px;-moz-border-radius:15px;-webkit-border-radius:15px;border-radius:15px;display:block;text-decoration:none;background:#798780;color:#fff;height:30px;margin:0 2px;width:30px}.nav-pagination a.previous,.nav-pagination a.next{font-size:16px;font-size:1.6rem;-moz-border-radius:20px;-webkit-border-radius:20px;border-radius:20px;height:40px;line-height:43px;width:40px}.nav-pagination a.previous{margin-right:10px;margin-right:70px}.nav-pagination a.next{margin-left:10px;text-indent:1px;margin-left:70px}.nav-pagination a:hover,.nav-pagination a:focus,.nav-pagination a:active,.nav-pagination a.active{background:none;color:#20AA76;background:#20AA76;color:white}.mdzr-no-borderradius .nav-pagination a{display:inline;background:none;color:#798780;height:auto;width:auto;margin:0 5px !important}.mdzr-no-borderradius .nav-pagination a:hover,.mdzr-no-borderradius .nav-pagination a:active,.mdzr-no-borderradius .nav-pagination a:focus,.mdzr-no-borderradius .nav-pagination a.active{background:none;color:#20AA76}hr{border:0;border-top:1px solid #CFE3DC}.badge{-moz-border-radius:4px;-webkit-border-radius:4px;border-radius:4px;font-size:12px;padding:2px 6px;margin:0 5px;letter-spacing:0px;position:relative;bottom:0.3em;color:#F1FFF7;background-color:#20AA76}.user-info .avatar{padding:20px;border:1px solid #CFE3DC;-moz-border-radius:4px;-webkit-border-radius:4px;border-radius:4px;float:right}.visuallyhidden{border:0;clip:rect(0 0 0 0);height:1px;margin:-1px;overflow:hidden;padding:0;position:absolute;width:1px}.clearfix,.group-container{zoom:1}.clearfix:before,.clearfix:after,.group-container:before,.group-container:after{content:"";display:table}.clearfix:after,.group-container:after{clear:both}.fundraising-index{margin-top:50px}.fundraising-index .fundraising-heart{width:100%;float:none;width:55%;float:left}.fundraising-index .fundraising-heart img,.fundraising-index .fundraising-heart svg{width:100%;height:auto}.fundraising-index .fundraising-heart rect{-moz-transition:opacity 250ms ease-out;-o-transition:opacity 250ms ease-out;-webkit-transition:opacity 250ms ease-out;transition:opacity 250ms ease-out}.fundraising-index .fundraising-heart rect.faded{opacity:0.05}.fundraising-index .fundraising-heart rect.faded:hover{opacity:0.9}.fundraising-index .description{margin-left:5%;width:100%;float:none;margin-top:30px;width:40%;float:left;margin-top:0px}.fundraising-index .description h2{margin-top:0;font-size:28px;font-size:2.8rem}.fundraising-index .donate select{width:80%;width:100%}.fundraising-index .donate .cta{margin:10px 0}.fundraising-index .donate .custom-donation{display:none}.fundraising-index .donate .prefix{float:left;font-size:19px;font-size:1.9rem;margin:8px 9px 0 3px}.fundraising-index .donate input[type="text"]{width:70%}.fundraising-index .cls{clear:both}.fundraising-donation select{width:100%}.fundraising-donation input.error{border:1px solid #BA2121 !important}.fundraising-donation p.validation-errors{color:#BA2121}.fundraising-donation .custom-donation{display:none;margin-top:10px}.fundraising-sidebar{clear:both}.fundraising-sidebar .small-heart{margin-top:20px;width:20%;float:left;margin-bottom:20px}.fundraising-sidebar .small-heart img{width:100%;max-width:64px}.fundraising-sidebar .small-cta{width:70%;float:left;vertical-align:top;margin-left:5%;margin-right:5%;margin-bottom:20px}.footnote{color:#859D94;font-size:14px;font-size:1.4rem;margin-top:20px;text-align:center}form .footnote{margin-top:10px;text-align:left}.heros-section{overflow:hidden}.heros-section .heros{clear:both}.heros-section .heros .hero{width:25%;position:relative;height:auto}.heros-section .heros .hero div{width:100%}.heros-section .heros .hero-logo{height:170px;line-height:170px}.heros-section .heros .hero-logo img{vertical-align:middle}.heros-section .heros .hero-name{height:92px;vertical-align:top}.heros-section .heros .no-logo-hero{min-height:60px;margin-right:24px}.heros-section .heros div{float:left;text-align:center;margin:15px 0}.heros-section .heros div img{max-width:90%;max-height:170px}.heros-section .pagination{clear:both}pre.literal-block,.literal-block{font-size:14px;font-size:1.4rem;border:1px solid #EAEAEA;background:#F4F4F4;background:#f8f8f8;overflow:auto;border-radius:4px;margin:25px 0;padding:10px 20px;color:#0C4B33}.snippet-filename{background:#C9F0DD;color:#0C4B33;font-family:"Fira Mono", Consolas, Menlo, Monaco, "Courier New", Courier, monospace;font-variant-ligatures:no-common-ligatures;text-rendering:optimizeSpeed;font-size:1em;padding:5px 20px;-moz-border-radius:4px 4px 0 0;-webkit-border-radius:4px;border-radius:4px 4px 0 0}.snippet-filename+.highlight{margin-top:0;-moz-border-radius:0 0 4px 4px;-webkit-border-radius:0;border-radius:0 0 4px 4px;border-top:0}.highlight{font-size:14px;font-size:1.4rem;border:1px solid #EAEAEA;background:#F4F4F4;background:#f8f8f8;overflow:auto;border-radius:4px;margin:25px 0}.highlight pre{margin:15px 20px}.highlight li{margin-top:0;border-left:1px solid #EAEAEA;padding:0 0 2px 15px}.highlight li:first-child{padding-top:2px}.highlight .hll{background-color:#ffc}.highlight .c{color:#408080;font-style:italic}.highlight .err{border:1px solid red}.highlight .k{color:#008000;font-weight:bold}.highlight .o{color:#666}.highlight .cm{color:#408080;font-style:italic}.highlight .cp{color:#BC7A00}.highlight .c1{color:#408080;font-style:italic}.highlight .cs{color:#408080;font-style:italic}.highlight .gd{color:#A00000}.highlight .ge{font-style:italic}.highlight .gr{color:red}.highlight .gh{color:#000080;font-weight:bold}.highlight .gi{color:#00A000}.highlight .go{color:gray}.highlight .gp{color:#000080;font-weight:bold}.highlight .gs{font-weight:bold}.highlight .gu{color:#800080;font-weight:bold}.highlight .gt{color:#0040D0}.highlight .kc{color:#008000;font-weight:bold}.highlight .kd{color:#008000;font-weight:bold}.highlight .kn{color:#008000;font-weight:bold}.highlight .kp{color:green}.highlight .kr{color:#008000;font-weight:bold}.highlight .kt{color:#B00040}.highlight .m{color:#666}.highlight .s{color:#BA2121}.highlight .na{color:#7D9029}.highlight .nb{color:green}.highlight .nc{color:#0000FF;font-weight:bold}.highlight .no{color:#800}.highlight .nd{color:#a2f}.highlight .ni{color:#999999;font-weight:bold}.highlight .ne{color:#D2413A;font-weight:bold}.highlight .nf{color:blue}.highlight .nl{color:#A0A000}.highlight .nn{color:#0000FF;font-weight:bold}.highlight .nt{color:#008000;font-weight:bold}.highlight .nv{color:#19177C}.highlight .ow{color:#AA22FF;font-weight:bold}.highlight .w{color:#bbb}.highlight .mf{color:#666}.highlight .mh{color:#666}.highlight .mi{color:#666}.highlight .mo{color:#666}.highlight .sb{color:#BA2121}.highlight .sc{color:#BA2121}.highlight .sd{color:#BA2121;font-style:italic}.highlight .s2{color:#BA2121}.highlight .se{color:#BB6622;font-weight:bold}.highlight .sh{color:#BA2121}.highlight .si{color:#BB6688;font-weight:bold}.highlight .sx{color:green}.highlight .sr{color:#b68}.highlight .s1{color:#BA2121}.highlight .ss{color:#19177C}.highlight .bp{color:green}.highlight .vc{color:#19177C}.highlight .vg{color:#19177C}.highlight .vi{color:#19177C}.highlight .il{color:#666}.highlight .lineno{color:#000000;background-color:#dddddd}.styleguide .example{padding:0 20px 20px;border:1px solid #CFE3DC;-moz-border-radius:4px;-webkit-border-radius:4px;border-radius:4px;margin-top:20px;margin-bottom:64px}.styleguide .example:before{content:"Example";font-size:16px;font-weight:700;display:block;color:#CFE3DC;font-family:"Roboto", Corbel, Avenir, "Lucida Grande", "Lucida Sans", sans-serif;text-align:left;padding:10px 0}.styleguide .example [role="complementary"]{float:none;width:auto;padding:0;margin:0}.styleguide .iframe{display:block;height:400px;cursor:zoom-in;border:1px solid #CFE3DC;overflow:hidden}.styleguide .iframe iframe{pointer-events:none;position:relative;width:200%;border:0;height:800px;-moz-transform:scale(0.5) translate(-50%, -50%);-ms-transform:scale(0.5) translate(-50%, -50%);-webkit-transform:scale(0.5) translate(-50%, -50%);transform:scale(0.5) translate(-50%, -50%);top:0;left:0;overflow:hidden}.styleguide .swatches{margin:0;padding:0;list-style:none;zoom:1;margin:30px 0}.styleguide .swatches:before,.styleguide .swatches:after{content:"";display:table}.styleguide .swatches:after{clear:both}.styleguide .swatches li{width:30%;height:30px;margin-right:2%;-moz-box-sizing:border-box;-webkit-box-sizing:border-box;box-sizing:border-box;float:left}.styleguide .swatches li.text{background:#0C3C26}.styleguide .swatches li.green-dark{background:#0C4B33}.styleguide .swatches li.green{background:#20AA76}.styleguide .swatches li.green-light{background:#93D7B7}.styleguide .swatches li.white{background:#F1FFF7;border:1px solid #CFE3DC}.styleguide .swatches li.red-dark{background:#6A0E0E}.styleguide .swatches li.text-light{background:#798780}.styleguide .swatches li.green-medium-dark{background:#2B8C67}.styleguide .swatches li.green-medium{background:#44B78B}.styleguide .swatches li.green-very-light{background:#C9F0DD}.styleguide .swatches li.gray-line{background:#CFE3DC}.styleguide .swatches li.red{background:#BA2121}.styleguide #layout{overflow:hidden}.styleguide #icons .icon{font-size:32px;font-size:3.2rem;color:#20AA76;padding:0 .2em}@media print{*{background:transparent !important;color:#000 !important;box-shadow:none !important;text-shadow:none !important}a,a:visited{text-decoration:underline}pre,blockquote{border:1px solid #999;page-break-inside:avoid}thead{display:table-header-group}tr,img{page-break-inside:avoid}img{max-width:100% !important}@page{margin:0.5cm}p,h2,h3{orphans:3;widows:3}h2,h3{page-break-after:avoid}html{font-size:40%}.menu-button,.news-search,.backtotop,.nav-pagination,[role="contentinfo"]>.container,[role="contentinfo"] .logo,.thanks,[role="complementary"],[role="navigation"],.form-input{display:none !important}.logo{text-indent:0 !important}[role="contentinfo"],[role="contentinfo"] .copyright{margin:0 !important;padding:0 !important}.internal-container{float:none;width:auto}.list-news li{margin-top:0}}#dashboard .full-width [role="main"]{width:80%;max-width:1200px;margin:0 auto;text-align:center}.metric{height:8em;width:240px;padding:0 20px 10px 20px;margin:10px 10px 20px 10px;border:1px solid #CFE3DC;-moz-border-radius:4px;-webkit-border-radius:4px;border-radius:4px;display:inline-block;position:relative}.metric h3{padding:10px;margin:0 -20px;font-size:18px;font-size:1.8rem;font-weight:200;display:block;text-align:left;color:#C9F0DD;background:#44B78B}.metric h3 a{color:#C9F0DD}.metric .value{position:absolute;bottom:0;left:0;width:100%;height:80%;padding:0;margin:0;text-align:center;font-size:5em;line-height:1.5em;font-family:"Roboto", Corbel, Avenir, "Lucida Grande", "Lucida Sans", sans-serif;z-index:10;pointer-events:none}.metric .value a{text-decoration:none;z-index:10}.metric .value .timestamp{font-size:0.16em;line-height:1em}.metric .sparkline{position:absolute;bottom:0;left:0;width:100%;height:54px;z-index:1}.graph{width:100%;margin:0 auto;height:500px}
diff --git a/djangoproject/static/css/output.css b/djangoproject/static/css/output.css
index 133793ea1..d7d75ff62 100644
--- a/djangoproject/static/css/output.css
+++ b/djangoproject/static/css/output.css
@@ -1,4 +1,4 @@
/*! normalize.css v2.0.1 | MIT License | git.io/normalize */article,aside,details,figcaption,figure,footer,header,hgroup,nav,section,summary{display:block}audio,canvas,video{display:inline-block}audio:not([controls]){display:none;height:0}[hidden]{display:none}html{font-family:sans-serif;-webkit-text-size-adjust:100%;-ms-text-size-adjust:100%}body{margin:0}a:focus{outline:thin dotted}a:active,a:hover{outline:0}abbr[title]{border-bottom:1px dotted}b,strong{font-weight:bold}dfn{font-style:italic}mark{background:#ff0;color:#000}code,kbd,pre,samp{font-family:monospace, serif;font-size:1em}pre{white-space:pre;white-space:pre-wrap;word-wrap:break-word}q{quotes:"\201C" "\201D" "\2018" "\2019"}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sup{top:-0.5em}sub{bottom:-0.25em}p{margin:1em 0}img{border:0}svg:not(:root){overflow:hidden}figure{margin:0}fieldset{border:1px solid #c0c0c0;margin:0 2px;padding:0.35em 0.625em 0.75em}legend{border:0;padding:0}button,input,select,textarea{font-family:inherit;font-size:100%;margin:0}button,input{line-height:normal}button,html input[type="button"],input[type="reset"],input[type="submit"]{-webkit-appearance:button;cursor:pointer}button[disabled],input[disabled]{cursor:default}input[type="checkbox"],input[type="radio"]{box-sizing:border-box;padding:0}input[type="search"]{-webkit-appearance:textfield;-moz-box-sizing:content-box;-webkit-box-sizing:content-box;box-sizing:content-box}input[type="search"]::-webkit-search-cancel-button,input[type="search"]::-webkit-search-decoration{-webkit-appearance:none}button::-moz-focus-inner,input::-moz-focus-inner{border:0;padding:0}textarea{overflow:auto;vertical-align:top}table{border-collapse:collapse;border-spacing:0}@font-face{font-family:'Fira Mono';src:url("../fonts/fira-mono/FiraMono-Regular.eot");src:url("../fonts/fira-mono/FiraMono-Regular.eot") format("embedded-opentype"),url("../fonts/fira-mono/FiraMono-Regular.woff") format("woff"),url("../fonts/fira-mono/FiraMono-Regular.ttf") format("truetype");font-weight:400;font-style:normal}@font-face{font-family:'Fira Mono';src:url("../fonts/fira-mono/FiraMono-Bold.eot");src:url("../fonts/fira-mono/FiraMono-Bold.eot") format("embedded-opentype"),url("../fonts/fira-mono/FiraMono-Bold.woff") format("woff"),url("../fonts/fira-mono/FiraMono-Bold.ttf") format("truetype");font-weight:700;font-style:normal}html{font-size:62.5%}/*!
* Font Awesome 4.2.0 by @davegandy - http://fontawesome.io - @fontawesome
* License - http://fontawesome.io/license (Font: SIL OFL 1.1, CSS: MIT License)
- */@font-face{font-family:'FontAwesome';src:url("//netdna.bootstrapcdn.com/font-awesome/4.2.0/fonts/fontawesome-webfont.eot?v=4.2.0");src:url("//netdna.bootstrapcdn.com/font-awesome/4.2.0/fonts/fontawesome-webfont.eot?#iefix&v=4.2.0") format("embedded-opentype"),url("//netdna.bootstrapcdn.com/font-awesome/4.2.0/fonts/fontawesome-webfont.woff?v=4.2.0") format("woff"),url("//netdna.bootstrapcdn.com/font-awesome/4.2.0/fonts/fontawesome-webfont.ttf?v=4.2.0") format("truetype"),url("//netdna.bootstrapcdn.com/font-awesome/4.2.0/fonts/fontawesome-webfont.svg?v=4.2.0#fontawesomeregular") format("svg");font-weight:normal;font-style:normal}.icon{display:inline-block;font:normal normal normal 14px/1 FontAwesome;font-size:inherit;text-rendering:auto;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.icon-lg{font-size:1.33333em;line-height:0.75em;vertical-align:-15%}.icon-2x{font-size:2em}.icon-3x{font-size:3em}.icon-4x{font-size:4em}.icon-5x{font-size:5em}.icon-fw{width:1.28571em;text-align:center}.icon-ul{padding-left:0;margin-left:2.14286em;list-style-type:none}.icon-ul>li{position:relative}.icon-li{position:absolute;left:-2.14286em;width:2.14286em;top:0.14286em;text-align:center}.icon-li.icon-lg{left:-1.85714em}.icon-border{padding:.2em .25em .15em;border:solid 0.08em #eee;border-radius:.1em}.pull-right{float:right}.pull-left{float:left}.icon.pull-left{margin-right:.3em}.icon.pull-right{margin-left:.3em}.icon-spin{-webkit-animation:fa-spin 2s infinite linear;animation:fa-spin 2s infinite linear}@-webkit-keyframes fa-spin{0%{-webkit-transform:rotate(0deg);transform:rotate(0deg)}100%{-webkit-transform:rotate(359deg);transform:rotate(359deg)}}@keyframes fa-spin{0%{-webkit-transform:rotate(0deg);transform:rotate(0deg)}100%{-webkit-transform:rotate(359deg);transform:rotate(359deg)}}.icon-rotate-90{filter:progid:DXImageTransform.Microsoft.BasicImage(rotation=1);-webkit-transform:rotate(90deg);-ms-transform:rotate(90deg);transform:rotate(90deg)}.icon-rotate-180{filter:progid:DXImageTransform.Microsoft.BasicImage(rotation=2);-webkit-transform:rotate(180deg);-ms-transform:rotate(180deg);transform:rotate(180deg)}.icon-rotate-270{filter:progid:DXImageTransform.Microsoft.BasicImage(rotation=3);-webkit-transform:rotate(270deg);-ms-transform:rotate(270deg);transform:rotate(270deg)}.icon-flip-horizontal{filter:progid:DXImageTransform.Microsoft.BasicImage(rotation=0);-webkit-transform:scale(-1, 1);-ms-transform:scale(-1, 1);transform:scale(-1, 1)}.icon-flip-vertical{filter:progid:DXImageTransform.Microsoft.BasicImage(rotation=2);-webkit-transform:scale(1, -1);-ms-transform:scale(1, -1);transform:scale(1, -1)}:root .icon-rotate-90,:root .icon-rotate-180,:root .icon-rotate-270,:root .icon-flip-horizontal,:root .icon-flip-vertical{filter:none}.icon-stack{position:relative;display:inline-block;width:2em;height:2em;line-height:2em;vertical-align:middle}.icon-stack-1x,.icon-stack-2x{position:absolute;left:0;width:100%;text-align:center}.icon-stack-1x{line-height:inherit}.icon-stack-2x{font-size:2em}.icon-inverse{color:#fff}.icon-glass:before{content:""}.icon-music:before{content:""}.icon-search:before{content:""}.icon-envelope-o:before{content:""}.icon-heart:before{content:""}.icon-star:before{content:""}.icon-star-o:before{content:""}.icon-user:before{content:""}.icon-film:before{content:""}.icon-th-large:before{content:""}.icon-th:before{content:""}.icon-th-list:before{content:""}.icon-check:before{content:""}.icon-remove:before,.icon-close:before,.icon-times:before{content:""}.icon-search-plus:before{content:""}.icon-search-minus:before{content:""}.icon-power-off:before{content:""}.icon-signal:before{content:""}.icon-gear:before,.icon-cog:before{content:""}.icon-trash-o:before{content:""}.icon-home:before{content:""}.icon-file-o:before{content:""}.icon-clock-o:before{content:""}.icon-road:before{content:""}.icon-download:before{content:""}.icon-arrow-circle-o-down:before{content:""}.icon-arrow-circle-o-up:before{content:""}.icon-inbox:before{content:""}.icon-play-circle-o:before{content:""}.icon-rotate-right:before,.icon-repeat:before{content:""}.icon-refresh:before{content:""}.icon-list-alt:before{content:""}.icon-lock:before{content:""}.icon-flag:before{content:""}.icon-headphones:before{content:""}.icon-volume-off:before{content:""}.icon-volume-down:before{content:""}.icon-volume-up:before{content:""}.icon-qrcode:before{content:""}.icon-barcode:before{content:""}.icon-tag:before{content:""}.icon-tags:before{content:""}.icon-book:before{content:""}.icon-bookmark:before{content:""}.icon-print:before{content:""}.icon-camera:before{content:""}.icon-font:before{content:""}.icon-bold:before{content:""}.icon-italic:before{content:""}.icon-text-height:before{content:""}.icon-text-width:before{content:""}.icon-align-left:before{content:""}.icon-align-center:before{content:""}.icon-align-right:before{content:""}.icon-align-justify:before{content:""}.icon-list:before{content:""}.icon-dedent:before,.icon-outdent:before{content:""}.icon-indent:before{content:""}.icon-video-camera:before{content:""}.icon-photo:before,.icon-image:before,.icon-picture-o:before{content:""}.icon-pencil:before{content:""}.icon-map-marker:before{content:""}.icon-adjust:before{content:""}.icon-tint:before{content:""}.icon-edit:before,.icon-pencil-square-o:before{content:""}.icon-share-square-o:before{content:""}.icon-check-square-o:before{content:""}.icon-arrows:before{content:""}.icon-step-backward:before{content:""}.icon-fast-backward:before{content:""}.icon-backward:before{content:""}.icon-play:before{content:""}.icon-pause:before{content:""}.icon-stop:before{content:""}.icon-forward:before{content:""}.icon-fast-forward:before{content:""}.icon-step-forward:before{content:""}.icon-eject:before{content:""}.icon-chevron-left:before{content:""}.icon-chevron-right:before{content:""}.icon-plus-circle:before{content:""}.icon-minus-circle:before{content:""}.icon-times-circle:before{content:""}.icon-check-circle:before{content:""}.icon-question-circle:before{content:""}.icon-info-circle:before{content:""}.icon-crosshairs:before{content:""}.icon-times-circle-o:before{content:""}.icon-check-circle-o:before{content:""}.icon-ban:before{content:""}.icon-arrow-left:before{content:""}.icon-arrow-right:before{content:""}.icon-arrow-up:before{content:""}.icon-arrow-down:before{content:""}.icon-mail-forward:before,.icon-share:before{content:""}.icon-expand:before{content:""}.icon-compress:before{content:""}.icon-plus:before{content:""}.icon-minus:before{content:""}.icon-asterisk:before{content:""}.icon-exclamation-circle:before{content:""}.icon-gift:before{content:""}.icon-leaf:before{content:""}.icon-fire:before{content:""}.icon-eye:before{content:""}.icon-eye-slash:before{content:""}.icon-warning:before,.icon-exclamation-triangle:before{content:""}.icon-plane:before{content:""}.icon-calendar:before{content:""}.icon-random:before{content:""}.icon-comment:before{content:""}.icon-magnet:before{content:""}.icon-chevron-up:before{content:""}.icon-chevron-down:before{content:""}.icon-retweet:before{content:""}.icon-shopping-cart:before{content:""}.icon-folder:before{content:""}.icon-folder-open:before{content:""}.icon-arrows-v:before{content:""}.icon-arrows-h:before{content:""}.icon-bar-chart-o:before,.icon-bar-chart:before{content:""}.icon-twitter-square:before{content:""}.icon-facebook-square:before{content:""}.icon-camera-retro:before{content:""}.icon-key:before{content:""}.icon-gears:before,.icon-cogs:before{content:""}.icon-comments:before{content:""}.icon-thumbs-o-up:before{content:""}.icon-thumbs-o-down:before{content:""}.icon-star-half:before{content:""}.icon-heart-o:before{content:""}.icon-sign-out:before{content:""}.icon-linkedin-square:before{content:""}.icon-thumb-tack:before{content:""}.icon-external-link:before{content:""}.icon-sign-in:before{content:""}.icon-trophy:before{content:""}.icon-github-square:before{content:""}.icon-upload:before{content:""}.icon-lemon-o:before{content:""}.icon-phone:before{content:""}.icon-square-o:before{content:""}.icon-bookmark-o:before{content:""}.icon-phone-square:before{content:""}.icon-twitter:before{content:""}.icon-facebook:before{content:""}.icon-github:before{content:""}.icon-unlock:before{content:""}.icon-credit-card:before{content:""}.icon-rss:before{content:""}.icon-hdd-o:before{content:""}.icon-bullhorn:before{content:""}.icon-bell:before{content:""}.icon-certificate:before{content:""}.icon-hand-o-right:before{content:""}.icon-hand-o-left:before{content:""}.icon-hand-o-up:before{content:""}.icon-hand-o-down:before{content:""}.icon-arrow-circle-left:before{content:""}.icon-arrow-circle-right:before{content:""}.icon-arrow-circle-up:before{content:""}.icon-arrow-circle-down:before{content:""}.icon-globe:before{content:""}.icon-wrench:before{content:""}.icon-tasks:before{content:""}.icon-filter:before{content:""}.icon-briefcase:before{content:""}.icon-arrows-alt:before{content:""}.icon-group:before,.icon-users:before{content:""}.icon-chain:before,.icon-link:before{content:""}.icon-cloud:before{content:""}.icon-flask:before{content:""}.icon-cut:before,.icon-scissors:before{content:""}.icon-copy:before,.icon-files-o:before{content:""}.icon-paperclip:before{content:""}.icon-save:before,.icon-floppy-o:before{content:""}.icon-square:before{content:""}.icon-navicon:before,.icon-reorder:before,.icon-bars:before{content:""}.icon-list-ul:before{content:""}.icon-list-ol:before{content:""}.icon-strikethrough:before{content:""}.icon-underline:before{content:""}.icon-table:before{content:""}.icon-magic:before{content:""}.icon-truck:before{content:""}.icon-pinterest:before{content:""}.icon-pinterest-square:before{content:""}.icon-google-plus-square:before{content:""}.icon-google-plus:before{content:""}.icon-money:before{content:""}.icon-caret-down:before{content:""}.icon-caret-up:before{content:""}.icon-caret-left:before{content:""}.icon-caret-right:before{content:""}.icon-columns:before{content:""}.icon-unsorted:before,.icon-sort:before{content:""}.icon-sort-down:before,.icon-sort-desc:before{content:""}.icon-sort-up:before,.icon-sort-asc:before{content:""}.icon-envelope:before{content:""}.icon-linkedin:before{content:""}.icon-rotate-left:before,.icon-undo:before{content:""}.icon-legal:before,.icon-gavel:before{content:""}.icon-dashboard:before,.icon-tachometer:before{content:""}.icon-comment-o:before{content:""}.icon-comments-o:before{content:""}.icon-flash:before,.icon-bolt:before{content:""}.icon-sitemap:before{content:""}.icon-umbrella:before{content:""}.icon-paste:before,.icon-clipboard:before{content:""}.icon-lightbulb-o:before{content:""}.icon-exchange:before{content:""}.icon-cloud-download:before{content:""}.icon-cloud-upload:before{content:""}.icon-user-md:before{content:""}.icon-stethoscope:before{content:""}.icon-suitcase:before{content:""}.icon-bell-o:before{content:""}.icon-coffee:before{content:""}.icon-cutlery:before{content:""}.icon-file-text-o:before{content:""}.icon-building-o:before{content:""}.icon-hospital-o:before{content:""}.icon-ambulance:before{content:""}.icon-medkit:before{content:""}.icon-fighter-jet:before{content:""}.icon-beer:before{content:""}.icon-h-square:before{content:""}.icon-plus-square:before{content:""}.icon-angle-double-left:before{content:""}.icon-angle-double-right:before{content:""}.icon-angle-double-up:before{content:""}.icon-angle-double-down:before{content:""}.icon-angle-left:before{content:""}.icon-angle-right:before{content:""}.icon-angle-up:before{content:""}.icon-angle-down:before{content:""}.icon-desktop:before{content:""}.icon-laptop:before{content:""}.icon-tablet:before{content:""}.icon-mobile-phone:before,.icon-mobile:before{content:""}.icon-circle-o:before{content:""}.icon-quote-left:before{content:""}.icon-quote-right:before{content:""}.icon-spinner:before{content:""}.icon-circle:before{content:""}.icon-mail-reply:before,.icon-reply:before{content:""}.icon-github-alt:before{content:""}.icon-folder-o:before{content:""}.icon-folder-open-o:before{content:""}.icon-smile-o:before{content:""}.icon-frown-o:before{content:""}.icon-meh-o:before{content:""}.icon-gamepad:before{content:""}.icon-keyboard-o:before{content:""}.icon-flag-o:before{content:""}.icon-flag-checkered:before{content:""}.icon-terminal:before{content:""}.icon-code:before{content:""}.icon-mail-reply-all:before,.icon-reply-all:before{content:""}.icon-star-half-empty:before,.icon-star-half-full:before,.icon-star-half-o:before{content:""}.icon-location-arrow:before{content:""}.icon-crop:before{content:""}.icon-code-fork:before{content:""}.icon-unlink:before,.icon-chain-broken:before{content:""}.icon-question:before{content:""}.icon-info:before{content:""}.icon-exclamation:before{content:""}.icon-superscript:before{content:""}.icon-subscript:before{content:""}.icon-eraser:before{content:""}.icon-puzzle-piece:before{content:""}.icon-microphone:before{content:""}.icon-microphone-slash:before{content:""}.icon-shield:before{content:""}.icon-calendar-o:before{content:""}.icon-fire-extinguisher:before{content:""}.icon-rocket:before{content:""}.icon-maxcdn:before{content:""}.icon-chevron-circle-left:before{content:""}.icon-chevron-circle-right:before{content:""}.icon-chevron-circle-up:before{content:""}.icon-chevron-circle-down:before{content:""}.icon-html5:before{content:""}.icon-css3:before{content:""}.icon-anchor:before{content:""}.icon-unlock-alt:before{content:""}.icon-bullseye:before{content:""}.icon-ellipsis-h:before{content:""}.icon-ellipsis-v:before{content:""}.icon-rss-square:before{content:""}.icon-play-circle:before{content:""}.icon-ticket:before{content:""}.icon-minus-square:before{content:""}.icon-minus-square-o:before{content:""}.icon-level-up:before{content:""}.icon-level-down:before{content:""}.icon-check-square:before{content:""}.icon-pencil-square:before{content:""}.icon-external-link-square:before{content:""}.icon-share-square:before{content:""}.icon-compass:before{content:""}.icon-toggle-down:before,.icon-caret-square-o-down:before{content:""}.icon-toggle-up:before,.icon-caret-square-o-up:before{content:""}.icon-toggle-right:before,.icon-caret-square-o-right:before{content:""}.icon-euro:before,.icon-eur:before{content:""}.icon-gbp:before{content:""}.icon-dollar:before,.icon-usd:before{content:""}.icon-rupee:before,.icon-inr:before{content:""}.icon-cny:before,.icon-rmb:before,.icon-yen:before,.icon-jpy:before{content:""}.icon-ruble:before,.icon-rouble:before,.icon-rub:before{content:""}.icon-won:before,.icon-krw:before{content:""}.icon-bitcoin:before,.icon-btc:before{content:""}.icon-file:before{content:""}.icon-file-text:before{content:""}.icon-sort-alpha-asc:before{content:""}.icon-sort-alpha-desc:before{content:""}.icon-sort-amount-asc:before{content:""}.icon-sort-amount-desc:before{content:""}.icon-sort-numeric-asc:before{content:""}.icon-sort-numeric-desc:before{content:""}.icon-thumbs-up:before{content:""}.icon-thumbs-down:before{content:""}.icon-youtube-square:before{content:""}.icon-youtube:before{content:""}.icon-xing:before{content:""}.icon-xing-square:before{content:""}.icon-youtube-play:before{content:""}.icon-dropbox:before{content:""}.icon-stack-overflow:before{content:""}.icon-instagram:before{content:""}.icon-flickr:before{content:""}.icon-adn:before{content:""}.icon-bitbucket:before{content:""}.icon-bitbucket-square:before{content:""}.icon-tumblr:before{content:""}.icon-tumblr-square:before{content:""}.icon-long-arrow-down:before{content:""}.icon-long-arrow-up:before{content:""}.icon-long-arrow-left:before{content:""}.icon-long-arrow-right:before{content:""}.icon-apple:before{content:""}.icon-windows:before{content:""}.icon-android:before{content:""}.icon-linux:before{content:""}.icon-dribbble:before{content:""}.icon-skype:before{content:""}.icon-foursquare:before{content:""}.icon-trello:before{content:""}.icon-female:before{content:""}.icon-male:before{content:""}.icon-gittip:before{content:""}.icon-sun-o:before{content:""}.icon-moon-o:before{content:""}.icon-archive:before{content:""}.icon-bug:before{content:""}.icon-vk:before{content:""}.icon-weibo:before{content:""}.icon-renren:before{content:""}.icon-pagelines:before{content:""}.icon-stack-exchange:before{content:""}.icon-arrow-circle-o-right:before{content:""}.icon-arrow-circle-o-left:before{content:""}.icon-toggle-left:before,.icon-caret-square-o-left:before{content:""}.icon-dot-circle-o:before{content:""}.icon-wheelchair:before{content:""}.icon-vimeo-square:before{content:""}.icon-turkish-lira:before,.icon-try:before{content:""}.icon-plus-square-o:before{content:""}.icon-space-shuttle:before{content:""}.icon-slack:before{content:""}.icon-envelope-square:before{content:""}.icon-wordpress:before{content:""}.icon-openid:before{content:""}.icon-institution:before,.icon-bank:before,.icon-university:before{content:""}.icon-mortar-board:before,.icon-graduation-cap:before{content:""}.icon-yahoo:before{content:""}.icon-google:before{content:""}.icon-reddit:before{content:""}.icon-reddit-square:before{content:""}.icon-stumbleupon-circle:before{content:""}.icon-stumbleupon:before{content:""}.icon-delicious:before{content:""}.icon-digg:before{content:""}.icon-pied-piper:before{content:""}.icon-pied-piper-alt:before{content:""}.icon-drupal:before{content:""}.icon-joomla:before{content:""}.icon-language:before{content:""}.icon-fax:before{content:""}.icon-building:before{content:""}.icon-child:before{content:""}.icon-paw:before{content:""}.icon-spoon:before{content:""}.icon-cube:before{content:""}.icon-cubes:before{content:""}.icon-behance:before{content:""}.icon-behance-square:before{content:""}.icon-steam:before{content:""}.icon-steam-square:before{content:""}.icon-recycle:before{content:""}.icon-automobile:before,.icon-car:before{content:""}.icon-cab:before,.icon-taxi:before{content:""}.icon-tree:before{content:""}.icon-spotify:before{content:""}.icon-deviantart:before{content:""}.icon-soundcloud:before{content:""}.icon-database:before{content:""}.icon-file-pdf-o:before{content:""}.icon-file-word-o:before{content:""}.icon-file-excel-o:before{content:""}.icon-file-powerpoint-o:before{content:""}.icon-file-photo-o:before,.icon-file-picture-o:before,.icon-file-image-o:before{content:""}.icon-file-zip-o:before,.icon-file-archive-o:before{content:""}.icon-file-sound-o:before,.icon-file-audio-o:before{content:""}.icon-file-movie-o:before,.icon-file-video-o:before{content:""}.icon-file-code-o:before{content:""}.icon-vine:before{content:""}.icon-codepen:before{content:""}.icon-jsfiddle:before{content:""}.icon-life-bouy:before,.icon-life-buoy:before,.icon-life-saver:before,.icon-support:before,.icon-life-ring:before{content:""}.icon-circle-o-notch:before{content:""}.icon-ra:before,.icon-rebel:before{content:""}.icon-ge:before,.icon-empire:before{content:""}.icon-git-square:before{content:""}.icon-git:before{content:""}.icon-hacker-news:before{content:""}.icon-tencent-weibo:before{content:""}.icon-qq:before{content:""}.icon-wechat:before,.icon-weixin:before{content:""}.icon-send:before,.icon-paper-plane:before{content:""}.icon-send-o:before,.icon-paper-plane-o:before{content:""}.icon-history:before{content:""}.icon-circle-thin:before{content:""}.icon-header:before{content:""}.icon-paragraph:before{content:""}.icon-sliders:before{content:""}.icon-share-alt:before{content:""}.icon-share-alt-square:before{content:""}.icon-bomb:before{content:""}.icon-soccer-ball-o:before,.icon-futbol-o:before{content:""}.icon-tty:before{content:""}.icon-binoculars:before{content:""}.icon-plug:before{content:""}.icon-slideshare:before{content:""}.icon-twitch:before{content:""}.icon-yelp:before{content:""}.icon-newspaper-o:before{content:""}.icon-wifi:before{content:""}.icon-calculator:before{content:""}.icon-paypal:before{content:""}.icon-google-wallet:before{content:""}.icon-cc-visa:before{content:""}.icon-cc-mastercard:before{content:""}.icon-cc-discover:before{content:""}.icon-cc-amex:before{content:""}.icon-cc-paypal:before{content:""}.icon-cc-stripe:before{content:""}.icon-bell-slash:before{content:""}.icon-bell-slash-o:before{content:""}.icon-trash:before{content:""}.icon-copyright:before{content:""}.icon-at:before{content:""}.icon-eyedropper:before{content:""}.icon-paint-brush:before{content:""}.icon-birthday-cake:before{content:""}.icon-area-chart:before{content:""}.icon-pie-chart:before{content:""}.icon-line-chart:before{content:""}.icon-lastfm:before{content:""}.icon-lastfm-square:before{content:""}.icon-toggle-off:before{content:""}.icon-toggle-on:before{content:""}.icon-bicycle:before{content:""}.icon-bus:before{content:""}.icon-ioxhost:before{content:""}.icon-angellist:before{content:""}.icon-cc:before{content:""}.icon-shekel:before,.icon-sheqel:before,.icon-ils:before{content:""}.icon-meanpath:before{content:""}body{font-family:Palatino, "Palatino Linotype", "Book Antiqua", "Hoefler Text", Georgia, "Lucida Bright", Cambria, Times, "Times New Roman", serif;font-size:18px;font-size:1.8rem;background:#f8f8f8;color:#0C3C26;line-height:1.6;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}body .layout-secondary,body [role="complementary"]{font-family:"Roboto", Corbel, Avenir, "Lucida Grande", "Lucida Sans", sans-serif}@media screen and (min-width: 768px){body{min-width:768px}}a{color:#0C3C26;text-decoration:underline;-webkit-tap-highlight-color:transparent}a:visited{color:#156641}a:active,a:focus,a:hover{color:#1d915c}::selection{background:#C9F0DD}::-moz-selection{background:#C9F0DD}ol li,ul li{margin-top:10px}dl{margin:20px 0 10px}dl dt{font-family:"Roboto", Corbel, Avenir, "Lucida Grande", "Lucida Sans", sans-serif;font-weight:400}dl dd{margin:0.2em 0 1.2em;padding:0}dl dd:last-of-type{margin-bottom:0}h1,h2,h3,h4,h5,h6{font-family:"Roboto", Corbel, Avenir, "Lucida Grande", "Lucida Sans", sans-serif;font-weight:400}h1 a,h2 a,h3 a,h4 a,h5 a,h6 a{text-decoration:none;color:#20AA76;text-decoration:none}h1 a:visited,h2 a:visited,h3 a:visited,h4 a:visited,h5 a:visited,h6 a:visited{color:#20AA76}h1 a:hover,h1 a:active,h1 a:focus,h2 a:hover,h2 a:active,h2 a:focus,h3 a:hover,h3 a:active,h3 a:focus,h4 a:hover,h4 a:active,h4 a:focus,h5 a:hover,h5 a:active,h5 a:focus,h6 a:hover,h6 a:active,h6 a:focus{color:#25c488;text-decoration:none}h1{font-size:28px;font-size:2.8rem;color:#fff;letter-spacing:-1px;line-height:1.1}@media screen and (min-width: 768px){h1{font-size:32px;font-size:3.2rem}}.layout-secondary h1{color:#0C3C26}[role="main"] h1{font-size:32px;font-size:3.2rem;margin:40px 0px 30px;color:#0C3C26}[role="complementary"] h1{font-size:28px;font-size:2.8rem}h2{font-size:24px;font-size:2.4rem}[role="complementary"] h2,.layout-secondary h2{font-size:20px;font-size:2rem;border-bottom:1px solid #CFE3DC;font-weight:400;padding-bottom:15px;margin-top:30px}[role="complementary"] h2:first-of-type,.layout-secondary h2:first-of-type{margin-top:inherit}@media screen and (min-width: 768px){[role="complementary"] h2:first-child,.layout-secondary h2:first-child{margin-top:20px}}.full-width [role="complementary"] h2,.full-width .layout-secondary h2{font-size:24px;font-size:2.4rem}[role="main"] h2{margin-top:40px;margin-bottom:15px}h3{font-size:20px;font-size:2rem;font-weight:700;color:#0C3C26;line-height:1.2;margin:35px 0 20px}[role="complementary"] h3,.layout-secondary h3{font-size:18px;font-size:1.8rem;font-weight:400;padding-bottom:15px}@media screen and (min-width: 768px){[role="complementary"] h3:first-child,.layout-secondary h3:first-child{margin-top:12px}}[role="complementary"] h3{font-size:18px;font-size:1.8rem;border-bottom:1px solid #CFE3DC}h4{font-size:16px;font-size:1.6rem;color:#0C3C26;line-height:1.2;margin:35px 0 20px;font-weight:700}tt,code,kbd,pre,samp{font-family:"Fira Mono", Consolas, Menlo, Monaco, "Courier New", Courier, monospace;font-variant-ligatures:no-common-ligatures;text-rendering:optimizeSpeed;color:#0C4B33;font-size:1em}tt{font-weight:700}span.pre{font-family:"Fira Mono", Consolas, Menlo, Monaco, "Courier New", Courier, monospace;font-variant-ligatures:no-common-ligatures;text-rendering:optimizeSpeed}a:hover tt,a:active tt,a:focus tt{color:#1d915c}[role="main"]>p:first-child{margin-top:30px}ul{padding-left:20px}blockquote{background:#F1FFF7;padding:15px 20px 15px 70px;border:1px solid #C9F0DD;-moz-border-radius:4px;-webkit-border-radius:4px;border-radius:4px;margin:25px 0;position:relative}blockquote p:first-child{margin-top:0}blockquote::before{display:inline-block;font:normal normal normal 14px/1 FontAwesome;font-size:inherit;text-rendering:auto;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;position:absolute;top:20px;left:20px;font-size:30px;width:34px;text-align:center;content:"";opacity:0.5}[role="main"]{background:#fff;padding:1px 10px 40px}@media screen and (min-width: 768px){[role="main"]{min-height:800px;border:1px solid #ddd;float:right;margin:0;padding-bottom:80px;padding-left:3%;padding-right:3%;width:60%}.mdzr-boxshadow [role="main"]{border:none;-moz-box-shadow:0px 0px 0 0 #fff,0px 0px 0 0 #fff,460px 0 0 0 #fff,1000px 0 0 0 #fff,0px 600px 0 0px #fff,460px 600px 0 0px #fff;-webkit-box-shadow:0px 0px 0 0 #fff,0px 0px 0 0 #fff,460px 0 0 0 #fff,1000px 0 0 0 #fff,0px 600px 0 0px #fff,460px 600px 0 0px #fff;box-shadow:0px 0px 0 0 #fff,0px 0px 0 0 #fff,460px 0 0 0 #fff,1000px 0 0 0 #fff,0px 600px 0 0px #fff,460px 600px 0 0px #fff;padding-left:4%;padding-right:0;width:63%}.sidebar-right [role="main"]{float:left}.mdzr-boxshadow .sidebar-right [role="main"]{border:none;-moz-box-shadow:0px 0px 0 0 #fff,0px 0px 0 0 #fff,-460px 0 0 0 #fff,-1000px 0 0 0 #fff,0px 600px 0 0px #fff,-460px 600px 0 0px #fff;-webkit-box-shadow:0px 0px 0 0 #fff,0px 0px 0 0 #fff,-460px 0 0 0 #fff,-1000px 0 0 0 #fff,0px 600px 0 0px #fff,-460px 600px 0 0px #fff;box-shadow:0px 0px 0 0 #fff,0px 0px 0 0 #fff,-460px 0 0 0 #fff,-1000px 0 0 0 #fff,0px 600px 0 0px #fff,-460px 600px 0 0px #fff;padding-left:0;width:62%;padding-right:4%}}.full-width [role="main"]{border:none;-moz-box-shadow:none;-webkit-box-shadow:none;box-shadow:none;float:none;margin:0 auto;padding:0 10px 40px;width:auto}@media screen and (min-width: 768px){.full-width [role="main"]{max-width:740px;padding:20px 0 40px;width:91.66667%}}[role="main"] .section{padding-bottom:40px;border-bottom:1px solid #CFE3DC}[role="main"] .section:last-of-type{padding-bottom:0;border-bottom:0}[role="main"] .section dd.last-child{padding-bottom:0}.full-width.container{width:100%;padding:0;max-width:none;border-bottom:1px solid #ddd;background:#fff}.full-width:before,.full-width:after{content:"";display:table}.full-width:after{clear:both}.mdzr-boxshadow .full-width{border:none;-moz-box-shadow:0 4px 8px rgba(12,60,38,0.07);-webkit-box-shadow:0 4px 8px rgba(12,60,38,0.07);box-shadow:0 4px 8px rgba(12,60,38,0.07)}[role="complementary"]{padding:0 10px 20px;font-size:14px;font-size:1.4rem}@media screen and (min-width: 768px){[role="complementary"]{float:right;margin:20px 0;margin-right:3%;padding:0 0 40px 0;width:30%}.sidebar-right [role="complementary"]{margin-left:3%;margin-right:0}}[role="complementary"] span.form-controls{display:none}[role="complementary"] .list-collapsing{margin-top:0;border-bottom:0}[role="complementary"] .list-collapsing.active li{border-top:0}[role="complementary"] .list-collapsing.active h2{padding:10px 40px 10px 0;font-size:18px;border-bottom:0;color:#44B78B}[role="complementary"] .list-collapsing.active h2 .collapsing-icon{font-size:10px}[role="secondary"]{margin:0 10px;padding:40px 0 60px}@media screen and (min-width: 768px){.full-width [role="secondary"]{max-width:700px;margin:0 auto}}.layout-secondary{padding:20px 10px 50px}.layout-tertiary{background:#fff;border-top:1px solid #ddd;padding:20px 10px 50px}.mdzr-boxshadow .layout-tertiary{border:none;-moz-box-shadow:0 -4px 8px rgba(12,60,38,0.07);-webkit-box-shadow:0 -4px 8px rgba(12,60,38,0.07);box-shadow:0 -4px 8px rgba(12,60,38,0.07)}.container:before,.container:after{content:"";display:table}.container:after{clear:both}@media screen and (min-width: 768px){.container{margin:0 auto;max-width:1400px;padding:0 4.16667%}}.mdzr-boxshadow .container.sidebar-right{-moz-box-shadow:-1200px 0 0 0px #fff;-webkit-box-shadow:-1200px 0 0 0px #fff;box-shadow:-1200px 0 0 0px #fff}[role="banner"]{background:#0C4B33;overflow:hidden;margin:0;padding:10px 0 6px;position:relative;z-index:0}[role="banner"]:before,[role="banner"]:after{content:"";display:table}[role="banner"]:after{clear:both}@media screen and (min-width: 768px){[role="banner"] .container{position:relative}}[role="banner"] .meta,[role="banner"] .list-links dd,.list-links [role="banner"] dd{font-size:13px;font-size:1.3rem;color:#44B78B;font-weight:700;width:auto;float:left;margin:8px 0 0 10px;display:none}@media screen and (min-width: 1150px){[role="banner"] .meta,[role="banner"] .list-links dd,.list-links [role="banner"] dd{float:left;width:200px;display:block}}[role="banner"] .logo{font-size:40px;font-size:4rem;font-family:"Roboto", Corbel, Avenir, "Lucida Grande", "Lucida Sans", sans-serif;background:url(../img/logo-django.png) 0 0 no-repeat;color:#FFF;display:block;float:left;font-weight:700;margin:10px;overflow:hidden;text-decoration:none;text-indent:100%;width:104px;height:36px}.mdzr-svg [role="banner"] .logo{background:url(../img/logo-django.svg) center center no-repeat}@media screen and (min-width: 768px){[role="banner"] .logo{margin-left:0}}[role="banner"] .menu-button{font-size:20px;font-size:2rem;background:#0C4B33;-moz-border-radius:23px;-webkit-border-radius:23px;border-radius:23px;color:#fff;cursor:pointer;display:block;float:right;height:45px;line-height:48px;margin:4px 10px;text-align:center;text-decoration:none;width:45px}@media screen and (min-width: 768px){[role="banner"] .menu-button{display:none}}[role="banner"] .menu-button:active{color:#44B78B}[role="banner"] .menu-button span{border:0;clip:rect(0 0 0 0);height:1px;margin:-1px;overflow:hidden;padding:0;position:absolute;width:1px}[role="banner"] .menu-button.active{opacity:0.5}[role="banner"] .nav-menu-on{max-height:0;overflow:hidden;-moz-transition:all 0.3s ease-out;-o-transition:all 0.3s ease-out;-webkit-transition:all 0.3s ease-out;transition:all 0.3s ease-out}@media screen and (min-width: 768px){[role="banner"] .nav-menu-on{max-height:none;-moz-transition:none;-o-transition:none;-webkit-transition:none;transition:none}}[role="banner"] .nav-menu-on.active{max-height:500px}[role="banner"] [role="navigation"]{background:#0C4B33;width:100%}@media screen and (min-width: 768px){[role="banner"] [role="navigation"]{width:auto;float:right}}[role="banner"] [role="navigation"] ul{margin:10px 0 0;padding:0}@media screen and (min-width: 768px){[role="banner"] [role="navigation"] ul{margin:0}}[role="banner"] [role="navigation"] li{font-family:"Roboto", Corbel, Avenir, "Lucida Grande", "Lucida Sans", sans-serif;font-size:13px;font-size:1.3rem;display:block;font-weight:700;line-height:16px;text-align:left;text-transform:uppercase;margin:0 10px;border-top:1px solid #106142}@media screen and (min-width: 768px){[role="banner"] [role="navigation"] li{margin:0;border:0;float:left;text-align:left}}[role="banner"] [role="navigation"] li.active a{color:#44B78B}[role="banner"] [role="navigation"] a{color:#fff;display:block;padding:20px 0px;text-decoration:none}[role="banner"] [role="navigation"] a:active,[role="banner"] [role="navigation"] a:hover{color:#C9F0DD}@media screen and (min-width: 768px){[role="banner"] [role="navigation"] a{padding:20px 10px}}@media screen and (min-width: 768px){[role="banner"] [role="navigation"] .nav-primary{position:absolute;right:0;top:45px}}.header{font-family:"Roboto", Corbel, Avenir, "Lucida Grande", "Lucida Sans", sans-serif;background:#0C4B33;margin:0;padding:11px 0px 8px;position:relative}.header h1{margin:0.4em 0}.header p{font-family:Palatino, "Palatino Linotype", "Book Antiqua", "Hoefler Text", Georgia, "Lucida Bright", Cambria, Times, "Times New Roman", serif;font-size:18px;font-size:1.8rem;color:#fff;left:-9999px;line-height:1.5;padding:0 0 10px;position:absolute;top:0;max-width:660px}@media screen and (min-width: 768px){.header p{position:static}}.copy-banner{background:#44B78B;padding:1px 10px}@media screen and (min-width: 768px){.copy-banner{padding:1px 0}}.copy-banner p,.copy-banner h1{font-family:"Roboto", Corbel, Avenir, "Lucida Grande", "Lucida Sans", sans-serif;font-size:24px;font-size:2.4rem;color:#C9F0DD;font-weight:300;line-height:1.3;padding:1px 0 6px;margin:.45em 0 .35em}.copy-banner p em,.copy-banner h1 em{font-style:normal;color:white}@media screen and (min-width: 768px){.copy-banner p,.copy-banner h1{font-size:32px;font-size:3.2rem;margin:.35em 0 .35em;color:#C9F0DD;padding:1px 0 6px}}.copy-banner p a,.copy-banner h1 a{font-weight:300;color:#C9F0DD}.copy-banner p a.cta,.copy-banner p .cta,.copy-banner h1 a.cta,.copy-banner h1 .cta{margin:0;font-size:18px;font-size:1.8rem}.copy-banner a.cta,.copy-banner .cta{margin:15px 0;padding:0.4em 1.5em 0.5em;background:#2B8C67;background:none;border:1px solid #C9F0DD;color:#C9F0DD;font-weight:400}.copy-banner a.cta:hover,.copy-banner .cta:hover{background:#309c72}.copy-banner a.cta em,.copy-banner .cta em{color:white}.copy-banner a.cta:hover,.copy-banner .cta:hover{background:#F1FFF7;color:#20AA76;border-color:#F1FFF7}.copy-banner a.cta:hover em,.copy-banner .cta:hover em{color:#20AA76}.copy-banner a.cta.white,.copy-banner .cta.white{background:#F1FFF7;color:#20AA76;font-weight:700;border:0}.copy-banner a.cta.white:hover,.copy-banner .cta.white:hover{background:#fff}.homepage .copy-banner{padding:50px 0;background:white;text-align:center;border-bottom:1px solid #CFE3DC}.homepage .copy-banner p{max-width:700px;margin-left:auto;margin-right:auto;margin:1em auto .5em;color:#0C3C26;font-size:36px;font-size:3.6rem}.homepage .copy-banner p.small{color:#798780;margin:2em auto 1em;font-size:14px;font-size:1.4rem}.homepage .copy-banner p em{color:#0C3C26}.homepage .copy-banner a.cta,.homepage .copy-banner .cta{display:inline-block;padding:1em 50px 1.1em;margin-bottom:40px;background:#44B78B;color:white;border:0;font-weight:700}.homepage .copy-banner a.cta:hover,.homepage .copy-banner .cta:hover{background:#51be95}.homepage .copy-banner a.cta:active,.homepage .copy-banner .cta:active{background:#41b085}.homepage .copy-banner .django-companies{max-width:750px;margin:0 auto;list-style:none;padding:0 0 0 30px}.homepage .copy-banner .django-companies li{width:144px;display:inline-block;text-indent:-1000px;overflow:hidden;margin:0}.homepage .copy-banner .django-companies li a{display:block;height:46px;background-position:center;background-repeat:no-repeat}.homepage .copy-banner .django-companies li a.company-mozilla{background-image:url("../img/company-mozilla.png");background-position:center 7px}.mdzr-svg .homepage .copy-banner .django-companies li a.company-mozilla{background-image:url("../img/company-mozilla.svg")}.homepage .copy-banner .django-companies li a.company-pinterest{background-image:url("../img/company-pinterest.png");background-position:center 7px}.mdzr-svg .homepage .copy-banner .django-companies li a.company-pinterest{background-image:url("../img/company-pinterest.svg")}.homepage .copy-banner .django-companies li a.company-theguardian{background-image:url("../img/company-theguardian.png");background-position:center 14px}.mdzr-svg .homepage .copy-banner .django-companies li a.company-theguardian{background-image:url("../img/company-theguardian.svg")}.homepage .copy-banner .django-companies li a.company-instagram{background-image:url("../img/company-instagram.png");background-position:center 10px}.mdzr-svg .homepage .copy-banner .django-companies li a.company-instagram{background-image:url("../img/company-instagram.svg")}.homepage .copy-banner .django-companies li a.company-rdio{background-image:url("../img/company-rdio.png");background-position:15px 6px}.mdzr-svg .homepage .copy-banner .django-companies li a.company-rdio{background-image:url("../img/company-rdio.svg")}[role="alert"]{clear:both;background:#F1FFF7;position:relative;-moz-box-shadow:0 -2px 8px 0 rgba(0,0,0,0.05);-webkit-box-shadow:0 -2px 8px 0 rgba(0,0,0,0.05);box-shadow:0 -2px 8px 0 rgba(0,0,0,0.05)}[role="alert"] a{color:#20AA76;text-decoration:none}[role="alert"] a:visited{color:#20AA76}[role="alert"] a:hover,[role="alert"] a:active,[role="alert"] a:focus{color:#25c488;text-decoration:none}[role="alert"] a.link-readmore{margin:0}[role="alert"] dl{margin:0 10px;padding:25px 0}[role="alert"] dl:before,[role="alert"] dl:after{content:"";display:table}[role="alert"] dl:after{clear:both}[role="alert"] dl dt i.icon{color:#20AA76;margin-right:8px}[role="alert"] dl dd{float:left;width:60%;margin-top:0;font-family:"Roboto", Corbel, Avenir, "Lucida Grande", "Lucida Sans", sans-serif}[role="alert"] dl .link-readmore{margin-left:10px}@media screen and (min-width: 768px){[role="alert"] dt{float:left;width:31%;padding-right:2%}[role="alert"] dl{margin:0}}[role="contentinfo"]{font-family:"Roboto", Corbel, Avenir, "Lucida Grande", "Lucida Sans", sans-serif;position:relative;background:#44B78B;clear:both;margin-top:0px}[role="contentinfo"]:before,[role="contentinfo"]:after{content:"";display:table}[role="contentinfo"]:after{clear:both}[role="contentinfo"] .container{overflow:hidden}[role="contentinfo"] .subfooter{padding:0 10px}[role="contentinfo"] .subfooter:before,[role="contentinfo"] .subfooter:after{content:"";display:table}[role="contentinfo"] .subfooter:after{clear:both}@media screen and (min-width: 768px){[role="contentinfo"] .subfooter{padding:0}}[role="contentinfo"] .subfooter .col:first-child h2{border-top:0}@media screen and (min-width: 768px){[role="contentinfo"] .subfooter .col{float:left;margin-bottom:-999px;padding:0 3% 999px 0;width:30%}}[role="contentinfo"] .subfooter .col:first-child{margin-left:0;padding-left:0}[role="contentinfo"] .subfooter .col.last-child{margin-right:0;padding-right:0}[role="contentinfo"] h2{font-size:16px;font-size:1.6rem;border-top:1px solid #CFE3DC;color:#fff;font-weight:700;margin-top:20px;padding:30px 0 10px}@media screen and (min-width: 768px){[role="contentinfo"] h2{border:none;margin-top:0}}[role="contentinfo"] ul{font-size:14px;font-size:1.4rem;font-weight:400;list-style:none;margin:15px 0 0 0;padding:0 0 30px}[role="contentinfo"] ul li{margin:10px 0 0;padding:0}[role="contentinfo"] ul a{color:#F1FFF7;text-decoration:none}[role="contentinfo"] ul a:hover,[role="contentinfo"] ul a:active,[role="contentinfo"] ul a:focus{text-decoration:underline}[role="contentinfo"] .footer{background:#0C4B33;margin-top:20px;padding:10px 0 30px;color:#2B8C67}[role="contentinfo"] .footer .footer-logo{float:left;width:33%}[role="contentinfo"] .footer .logo{margin-right:0;margin-top:28px}.mdzr-svg [role="contentinfo"] .footer .logo{background-position:left center;margin-right:0;margin-top:20px}[role="contentinfo"] .logo{font-size:40px;font-size:4rem;font-family:"Roboto", Corbel, Avenir, "Lucida Grande", "Lucida Sans", sans-serif;background:url(../img/logo-django.png) 0 0 no-repeat;color:#0C4B33;display:block;font-weight:700;height:50px;margin:10px;overflow:hidden;text-decoration:none;text-indent:100%;width:142px}@media screen and (min-width: 768px){[role="contentinfo"] .logo{float:left;margin:20px 90px 0 0}}.mdzr-svg [role="contentinfo"] .logo{background:url(../img/logo-django.svg) center center no-repeat;height:39px;width:109px}[role="contentinfo"] .thanks{font-size:12px;font-size:1.2rem;color:#2B8C67;margin:0;padding:0}@media screen and (min-width: 768px){[role="contentinfo"] .thanks{border:none}}[role="contentinfo"] .thanks li{margin:0;padding:17px 10px 11px;display:block;clear:both}[role="contentinfo"] .thanks li:before,[role="contentinfo"] .thanks li:after{content:"";display:table}[role="contentinfo"] .thanks li:after{clear:both}@media screen and (min-width: 768px){[role="contentinfo"] .thanks li{background:none;float:left;clear:none;padding:0 3% 0 0;width:30%}[role="contentinfo"] .thanks li.design span.ampersand,[role="contentinfo"] .thanks li.design a{display:inline-block;vertical-align:top}[role="contentinfo"] .thanks li.design span.ampersand.threespot,[role="contentinfo"] .thanks li.design a.threespot{clear:both}[role="contentinfo"] .thanks li.design span.ampersand.ampersand,[role="contentinfo"] .thanks li.design a.ampersand{position:relative;top:6px;margin:0 6px;line-height:36px}}[role="contentinfo"] .thanks li span.ampersand{line-height:24px}[role="contentinfo"] .thanks span{display:block;height:24px;line-height:36px;padding-right:12px;white-space:nowrap}[role="contentinfo"] .thanks a{display:block;height:33px;overflow:hidden;text-indent:-200px;width:94px}@media screen and (min-width: 768px){[role="contentinfo"] .thanks a{clear:both;margin-top:5px}}[role="contentinfo"] .thanks a.rackspace{background:url(../img/logo-rackspace.png) no-repeat left center}.mdzr-svg [role="contentinfo"] .thanks a.rackspace{background:url(../img/logo-rackspace.svg) no-repeat left center}[role="contentinfo"] .thanks a.threespot{background:url(../img/logo-threespot.png) no-repeat left center}.mdzr-svg [role="contentinfo"] .thanks a.threespot{background:url(../img/logo-threespot.svg) no-repeat left center}[role="contentinfo"] .thanks a.andrevv{background:url(../img/logo-andrevv.png) no-repeat left center}.mdzr-svg [role="contentinfo"] .thanks a.andrevv{background:url(../img/logo-andrevv.svg) no-repeat left center}[role="contentinfo"] .copyright{font-size:12px;font-size:1.2rem;clear:both;margin:20px 0 0 10px}@media screen and (min-width: 768px){[role="contentinfo"] .copyright{max-width:80%;padding-top:30px;margin:0}}[role="contentinfo"] .copyright a{color:#2B8C67}.backtotop{color:#20AA76;text-decoration:none;font-family:"Roboto", Corbel, Avenir, "Lucida Grande", "Lucida Sans", sans-serif;font-size:14px;font-size:1.4rem;display:block;font-weight:700;margin:10px 0;padding:10px 0;text-align:center;text-transform:uppercase}.backtotop:visited{color:#20AA76}.backtotop:hover,.backtotop:active,.backtotop:focus{color:#25c488;text-decoration:none}@media screen and (min-width: 768px){.backtotop{display:none}}.cta,a.cta{font-family:"Roboto", Corbel, Avenir, "Lucida Grande", "Lucida Sans", sans-serif;font-weight:700;-webkit-appearance:none;-moz-appearance:none;background:#44B78B;border:none;-moz-border-radius:5px;-webkit-border-radius:5px;border-radius:5px;color:#fff;display:block;-webkit-font-smoothing:subpixel-antialiased;-moz-osx-font-smoothing:auto;margin:30px auto 0;padding:1em 1.5em;text-align:center;text-decoration:none}@media screen and (min-width: 768px){.cta,a.cta{margin:20px auto;max-width:400px}}.cta em,a.cta em{color:#C9F0DD;font-style:normal}.cta:hover,.cta:focus,a.cta:hover,a.cta:focus{background:#51be95}.cta:active,a.cta:active{background:#41b085}[role="complementary"] .cta,[role="complementary"] a.cta{font-size:16px;font-size:1.6rem}[role="complementary"] .cta+.link-readmore,[role="complementary"] a.cta+.link-readmore{display:block;text-align:center}@media screen and (min-width: 768px){[role="complementary"] .cta+.link-readmore,[role="complementary"] a.cta+.link-readmore{margin-top:-10px}}.cta.outline,a.cta.outline{background:none;border:1px solid #CFE3DC;color:#859D94;font-weight:400}.cta.outline em,a.cta.outline em{color:#0C4B33}.cta.outline:hover,a.cta.outline:hover{border-color:#20AA76;color:#20AA76}.cta.outline:hover em,a.cta.outline:hover em{color:#20AA76}.cta.outline:active,a.cta.outline:active{border-color:#44B78B;color:#44B78B}.cta.outline:active em,a.cta.outline:active em{color:#44B78B}.link-green{color:#20AA76;text-decoration:none}.link-green:visited{color:#20AA76}.link-green:hover,.link-green:active,.link-green:focus{color:#25c488;text-decoration:none}.link-readmore{color:#20AA76;text-decoration:none;font-family:"Roboto", Corbel, Avenir, "Lucida Grande", "Lucida Sans", sans-serif;font-size:14px;font-size:1.4rem;display:inline-block;margin:10px 0;font-weight:700;text-transform:uppercase}.link-readmore:visited{color:#20AA76}.link-readmore:hover,.link-readmore:active,.link-readmore:focus{color:#25c488;text-decoration:none}.link-readmore:after{content:" ›";font-size:1.2em}.link-readmore.back-link:after{content:""}.link-readmore.back-link:before{content:"‹ ";font-size:1.2em}[role="complementary"] .link-readmore{font-size:12px;font-size:1.2rem}.meta,.list-links dd{font-family:"Roboto", Corbel, Avenir, "Lucida Grande", "Lucida Sans", sans-serif;font-size:14px;font-size:1.4rem;display:block;line-height:1.3;margin:25px 0 20px}.meta a,.list-links dd a{color:#20AA76;text-decoration:none}.meta a:visited,.list-links dd a:visited{color:#20AA76}.meta a:hover,.list-links dd a:hover,.meta a:active,.list-links dd a:active,.meta a:focus,.list-links dd a:focus{color:#25c488;text-decoration:none}.layout-2col{margin:20px 0}@media screen and (min-width: 768px){.layout-2col{margin:0}.layout-2col:before,.layout-2col:after{content:"";display:table}.layout-2col:after{clear:both}.layout-2col .col{float:left;width:46%;margin:0 4%}.layout-2col .col:first-child{margin-left:0}.layout-2col .col.last-child{margin-right:0}.layout-2col .one-third{width:29%}.layout-2col .two-third{width:62%}}.blue{color:#20AA76}.label{font-family:"Roboto", Corbel, Avenir, "Lucida Grande", "Lucida Sans", sans-serif;font-size:16px;font-size:1.6rem;color:#cacfcc;display:block;font-weight:700;margin:20px 0 10px;text-transform:uppercase}.label.form-controls{font-size:14px;font-size:1.4rem;display:block;margin:0;position:relative;text-align:left}.label.form-controls span{cursor:pointer}.label.form-controls span:hover,.label.form-controls span:active,.label.form-controls span:focus{color:#afb7b3}@media screen and (min-width: 768px){.callout-right{float:right;margin:26px 0 0 35px;width:33%}.callout-right.two-thirds{width:60%}}.callout-right img{display:block;max-width:100%}@media screen and (min-width: 768px){.callout-left{float:left;margin:26px 35px 0 0;width:33%}.callout-left.two-thirds{width:60%}}.callout-left img{display:block;max-width:100%}.codedump{background:#f8f8f8;border:1px solid #CFE3DC;padding:10px;-moz-border-radius:4px;-webkit-border-radius:4px;border-radius:4px;font-family:"Roboto", Corbel, Avenir, "Lucida Grande", "Lucida Sans", sans-serif;font-size:14px;font-size:1.4rem;line-height:1.6em}.list-events{font-family:"Roboto", Corbel, Avenir, "Lucida Grande", "Lucida Sans", sans-serif;list-style:none;margin:0;padding:0}.list-events li{font-size:18px;font-size:1.8rem;border-top:1px solid #CFE3DC;display:block;line-height:1.3;margin:0;padding:20px 0 0 30px;position:relative}.list-events li i{font-size:16px;font-size:1.6rem;color:#93D7B7;display:block;left:0;line-height:20px;height:30px;position:absolute;text-align:center;top:20px;width:24px}.list-events li:first-child{border-top:0;padding-top:0}.list-events li:first-child i{top:0px}.list-events .meta,.list-events .list-links dd,.list-links .list-events dd{font-family:"Roboto", Corbel, Avenir, "Lucida Grande", "Lucida Sans", sans-serif;font-size:14px;font-size:1.4rem;display:block;margin-top:10px}.list-events a{color:#20AA76;text-decoration:none}.list-events a:visited{color:#20AA76}.list-events a:hover,.list-events a:active,.list-events a:focus{color:#25c488;text-decoration:none}.list-tags{font-family:"Roboto", Corbel, Avenir, "Lucida Grande", "Lucida Sans", sans-serif;font-size:12px;font-size:1.2rem;font-weight:700;list-style:none;margin:0;padding:0;text-transform:uppercase}.list-tags li{margin-top:10px}.list-tags a{background:#93D7B7;color:#F1FFF7;display:inline-block;line-height:1.2;margin:0;padding:8px 10px 5px;text-decoration:none}.list-tags a:hover,.list-tags a:active,.list-tags a:focus{background-color:#44B78B;color:#fff}.list-news{list-style:none;margin:0;padding:0}.list-news h2{font-weight:400;margin-bottom:5px}.list-news li{border-top:1px solid #CFE3DC;margin-top:35px;padding-top:10px}.list-news li:first-child{border:none;margin-top:0;padding-top:0}.list-news .meta,.list-news .list-links dd,.list-links .list-news dd{margin-top:10px;color:#859D94}.list-news .meta a:link,.list-news .list-links dd a:link,.list-links .list-news dd a:link{color:#798780;text-decoration:underline}.list-case-study{list-style:none;margin:0;padding:0 0 10px}.list-case-study:before,.list-case-study:after{content:"";display:table}.list-case-study:after{clear:both}.list-case-study p{font-size:14px;font-size:1.4rem;margin:10px 0 5px}.list-case-study li{border-top:1px solid #CFE3DC;margin-top:20px;padding-top:20px}@media screen and (min-width: 1024px){.list-case-study li{border:none;float:left;padding-right:5%;padding-top:0;width:28%}.list-case-study li:nth-child(3):after{clear:both;content:"";display:block;margin-bottom:30px}}.list-case-study li>a{font-family:"Roboto", Corbel, Avenir, "Lucida Grande", "Lucida Sans", sans-serif;color:#20AA76;text-decoration:none;font-size:12px;font-size:1.2rem;font-weight:700;margin-top:10px;text-transform:uppercase}.list-case-study li>a:visited{color:#20AA76}.list-case-study li>a:hover,.list-case-study li>a:active,.list-case-study li>a:focus{color:#25c488;text-decoration:none}.list-case-study li>a:after{content:" ›";font-size:1.2em}.list-case-study h3{margin:10px 0 20px;padding:0}.list-case-study h3.logo{text-indent:-1000%;overflow:hidden}.list-case-study [title="Knight Foundation"]{background:url(../img/logo-knight.png) no-repeat bottom left;height:25px;padding-top:7px;width:190px}.mdzr-svg .list-case-study [title="Knight Foundation"]{background:url(../img/logo-knight.svg) no-repeat bottom left}.list-case-study [title="Mozilla"]{background:url(../img/logo-mozilla.png) no-repeat bottom left;height:32px;width:120px}.mdzr-svg .list-case-study [title="Mozilla"]{background:url(../img/logo-mozilla.svg) no-repeat bottom left}.list-case-study [title="Disqus"]{background:url(../img/logo-disqus.png) no-repeat bottom left;height:28px;padding-top:4px;width:140px}.mdzr-svg .list-case-study [title="Disqus"]{background:url(../img/logo-disqus.svg) no-repeat bottom left}.list-case-study.single-col li{margin-top:0;margin-bottom:30px;width:auto}.list-case-study.single-col li p{font-size:18px;font-size:1.8rem;margin-right:40px}.list-case-study.single-col li h3{margin-top:20px}.case-study-logo{max-width:50%;max-height:70px;height:auto;margin:40px 0 0}.list-link-soup{font-family:"Roboto", Corbel, Avenir, "Lucida Grande", "Lucida Sans", sans-serif;font-size:16px;font-size:1.6rem;border-top:1px solid #CFE3DC;list-style:none;margin:20px 0 0;padding:20px 0 10px}.list-link-soup:before,.list-link-soup:after{content:"";display:table}.list-link-soup:after{clear:both}.list-link-soup li{float:left;margin:10px 5% 0 0;width:45%}@media screen and (min-width: 768px){.list-link-soup li{margin-right:3%;width:30%}}.list-link-soup a{color:#20AA76;text-decoration:none}.list-link-soup a:visited{color:#20AA76}.list-link-soup a:hover,.list-link-soup a:active,.list-link-soup a:focus{color:#25c488;text-decoration:none}[role="complementary"] .list-link-soup li{float:none}h2+.list-link-soup{border-top:0}.list-features{margin:50px 0 40px}.list-features dt{font-size:24px;font-size:2.4rem;border-top:1px solid #CFE3DC;padding-top:25px}.list-features i{color:#F1FFF7;margin-right:10px;width:40px;height:40px;-moz-border-radius:25px;-webkit-border-radius:25px;border-radius:25px;background:#20AA76;line-height:1.68em;display:inline-block;text-align:center}.list-features i.icon-briefcase{line-height:1.7em}.list-features i.icon-dashboard{line-height:1.5em}@media screen and (min-width: 768px){.list-features{padding-bottom:40px}.list-features dt{margin-top:60px;padding:60px 0 0 245px;position:relative}.list-features dt:first-child{margin-top:20px}.list-features dt.even{padding-left:0;padding-right:245px}.list-features dt.even i{left:auto !important;right:0}.list-features dd{padding-left:245px;min-height:140px}.list-features dd.even{padding-left:0;padding-right:245px}.list-features i{font-size:120px;font-size:12rem;display:block;height:200px;left:0;position:absolute;text-align:center;top:60px;width:200px;margin-right:0}.mdzr-borderradius .list-features i{background:#44B78B;-moz-border-radius:100px;-webkit-border-radius:100px;border-radius:100px;color:#fff}.mdzr-svg .list-features i{background:url(../img/bg-features.svg) no-repeat center center}.mdzr-svg .list-features i.icon-bolt{background-position:-150px -269px}.mdzr-svg .list-features i.icon-briefcase{background-position:-354px -7px}.mdzr-svg .list-features i.icon-lock{background-position:-36px -96px}.mdzr-svg .list-features i.icon-dashboard{background-position:-270px -9px}.mdzr-svg .list-features i.icon-cogs{background-position:-334px -12px}.mdzr-svg.mdzr-borderradius.mdzr-cssanimations .list-features i{-moz-transition:all 0.3s ease-out;-o-transition:all 0.3s ease-out;-webkit-transition:all 0.3s ease-out;transition:all 0.3s ease-out;-moz-transform:rotate(0.5turn);-ms-transform:rotate(0.5turn);-webkit-transform:rotate(0.5turn);transform:rotate(0.5turn)}.mdzr-svg.mdzr-borderradius.mdzr-cssanimations .list-features i.inview{-moz-transform:rotate(0turn);-ms-transform:rotate(0turn);-webkit-transform:rotate(0turn);transform:rotate(0turn)}.mdzr-svg.mdzr-borderradius.mdzr-cssanimations .list-features i.icon-bolt{background-position:40px -369px}.mdzr-svg.mdzr-borderradius.mdzr-cssanimations .list-features i.icon-bolt.inview{background-position:-150px -269px}.mdzr-svg.mdzr-borderradius.mdzr-cssanimations .list-features i.icon-briefcase{background-position:-494px 207px}.mdzr-svg.mdzr-borderradius.mdzr-cssanimations .list-features i.icon-briefcase.inview{background-position:-354px -7px}.mdzr-svg.mdzr-borderradius.mdzr-cssanimations .list-features i.icon-lock{background-position:144px -206px}.mdzr-svg.mdzr-borderradius.mdzr-cssanimations .list-features i.icon-lock.inview{background-position:-36px -96px}.mdzr-svg.mdzr-borderradius.mdzr-cssanimations .list-features i.icon-dashboard{background-position:-360px 201px}.mdzr-svg.mdzr-borderradius.mdzr-cssanimations .list-features i.icon-dashboard.inview{background-position:-270px -9px}.mdzr-svg.mdzr-borderradius.mdzr-cssanimations .list-features i.icon-cogs{background-position:-500px -180px}.mdzr-svg.mdzr-borderradius.mdzr-cssanimations .list-features i.icon-cogs.inview{background-position:-334px -12px}.list-features i :-o-prefocus,.list-features i{background:#44B78B !important;-moz-transition:none !important;-o-transition:none !important;-webkit-transition:none !important;transition:none !important;-moz-transform:none !important;-ms-transform:none !important;-webkit-transform:none !important;transform:none !important}}.homepage .list-features{padding-bottom:0}.homepage .list-features dl{padding-top:0}.homepage .list-features i{color:#F1FFF7;margin-right:10px;width:40px;height:40px;top:10px;-moz-border-radius:20px;-webkit-border-radius:20px;border-radius:20px;background:#20AA76;display:inline-block;text-align:center;font-size:24px;font-size:2.4rem}.mdzr-svg.mdzr-borderradius.mdzr-cssanimations .homepage .list-features i{-moz-transition:none;-o-transition:none;-webkit-transition:none;transition:none;-moz-transform:rotate(0);-ms-transform:rotate(0);-webkit-transform:rotate(0);transform:rotate(0)}@media screen and (min-width: 768px){.homepage .list-features dt{padding:20px 0 0px 110px;font-size:18px;font-size:1.8rem;border-top:0;margin-top:0}.homepage .list-features dd{padding:0 60px 20px 110px;min-height:0}.homepage .list-features dd p{margin-top:0;font-size:18px;font-size:1.8rem}.homepage .list-features i{margin-right:10px;width:80px;height:80px;top:20px;-moz-border-radius:40px;-webkit-border-radius:40px;border-radius:40px;font-size:46px;font-size:4.6rem}}@media screen and (min-width: 768px){.list-collapsing-header{float:left}.section .list-collapsing-header h2{margin:40px 0 20px}.list-collapsing-header+.form-controls.label{margin:50px 0 0;text-align:right}.form-controls.label{float:right}}.list-collapsing{border-bottom:1px solid #CFE3DC;list-style:none;margin:30px 0;padding:0;clear:both}.list-collapsing.active>li{border-top:1px solid #CFE3DC;margin:0;padding:0}.list-collapsing.active>li.active h2 .collapsing-icon:before{content:"\f068"}.list-collapsing.active h2{font-size:18px;font-size:1.8rem;cursor:pointer;margin:0;padding:18px 40px 18px 0;position:relative}.list-collapsing.active h2:hover,.list-collapsing.active h2:focus,.list-collapsing.active h2:active{color:#1d915c;outline:none}.list-collapsing.active h2 .collapsing-icon{position:absolute;right:0;top:24px}.list-collapsing.active h2 .collapsing-icon:before{content:"\f067"}.list-collapsing.active h2.bullet-icon{padding-left:1.5em}.list-collapsing.active h2.bullet-icon>i:first-child{position:absolute;top:24px;left:0}.list-collapsing.active .collapsing-content{overflow:hidden;max-height:0px;-moz-transition:all 0.5s ease-out;-o-transition:all 0.5s ease-out;-webkit-transition:all 0.5s ease-out;transition:all 0.5s ease-out}.list-collapsing.active li.active .collapsing-content{max-height:1000px;overflow:auto}.list-image{list-style:none;margin:0;padding:0}.list-image li{border-top:1px solid #CFE3DC;margin-top:20px;padding-top:40px}@media screen and (min-width: 768px){.list-image li{margin-top:20px;padding-top:40px}.list-image li:before,.list-image li:after{content:"";display:table}.list-image li:after{clear:both}}.list-image li:first-child{border:none;padding-top:0}.list-image a{text-decoration:none}.list-image a:hover,.list-image a:active,.list-image a:focus{color:#798780}.list-image a.link-readmore{margin:0}.list-image img{display:block;margin:0 auto 25px;max-width:100%}@media screen and (min-width: 768px){.list-image img{float:left;margin:0 40px 0 0;max-width:200px;max-height:200px}.list-image h2,.list-image h3,.list-image h4,.list-image p{padding-left:240px}.list-image h3{margin-top:10px}}.layout-secondary .list-image img,[role="complementary"] .list-image img{float:left;max-width:40%;max-height:120px;margin:0 20px 10px 0}.layout-secondary .list-image h2,.layout-secondary .list-image h3,.layout-secondary .list-image h4,.layout-secondary .list-image p,[role="complementary"] .list-image h2,[role="complementary"] .list-image h3,[role="complementary"] .list-image h4,[role="complementary"] .list-image p{padding-left:0px;border:0}.layout-secondary .list-image h3,[role="complementary"] .list-image h3{font-size:14px;font-size:1.4rem;margin-top:0;margin-bottom:1em;padding-bottom:0;font-family:"Roboto", Corbel, Avenir, "Lucida Grande", "Lucida Sans", sans-serif;font-weight:700}#s-django-documentation,#s-feed{font-family:"Roboto", Corbel, Avenir, "Lucida Grande", "Lucida Sans", sans-serif}.list-outline{font-size:16px;font-size:1.6rem;line-height:1.3;list-style:none;margin:0;padding:0}.list-outline a{text-decoration:none}.list-outline>li>ul>li{margin-top:12px}.list-outline>li>ul>li:first-child{margin-top:6px}.list-outline>li>a{text-transform:uppercase;font-weight:700;color:#20AA76;text-decoration:none}.list-outline>li>a:visited{color:#20AA76}.list-outline>li>a:hover,.list-outline>li>a:active,.list-outline>li>a:focus{color:#25c488;text-decoration:none}.list-outline>li>ul{list-style:none;font-size:12px;font-size:1.2rem;padding:5px 0 0 10px}.list-outline>li>ul>li>a{font-weight:700;text-transform:uppercase;color:#20AA76;text-decoration:none}.list-outline>li>ul>li>a:visited{color:#20AA76}.list-outline>li>ul>li>a:hover,.list-outline>li>ul>li>a:active,.list-outline>li>ul>li>a:focus{color:#25c488;text-decoration:none}.list-outline>li>ul>li ul{font-size:14px;font-size:1.4rem;padding:0 0 0 20px}.section h2{margin:50px 0 30px}.section h3{margin:40px 0 20px}.headerlink{opacity:0;padding-left:10px;font-size:0.8em;position:relative;top:-0.17em;font-weight:700;text-decoration:none;-moz-transition:opacity 200ms ease-in-out;-o-transition:opacity 200ms ease-in-out;-webkit-transition:opacity 200ms ease-in-out;transition:opacity 200ms ease-in-out}h1:hover>.headerlink,h2:hover>.headerlink,h3:hover>.headerlink,h4:hover>.headerlink,h5:hover>.headerlink,h6:hover>.headerlink,dl:hover>.headerlink,dt:hover>.headerlink{opacity:1}.note,.admonition,.help-block{background:#F1FFF7;padding:15px 20px 15px 70px;border:1px solid #C9F0DD;-moz-border-radius:4px;-webkit-border-radius:4px;border-radius:4px;margin:25px 0;position:relative}.note h1,.note h2,.note h3,.note h4,.admonition h1,.admonition h2,.admonition h3,.admonition h4,.help-block h1,.help-block h2,.help-block h3,.help-block h4{margin-top:20px}.note p,.admonition p,.help-block p{margin:0.8em 0}.note .first,.admonition .first,.help-block .first{margin-top:0}.note .admonition-title,.admonition .admonition-title,.help-block .admonition-title{font-weight:bold}.note .admonition-title::before,.admonition .admonition-title::before,.help-block .admonition-title::before{display:inline-block;font:normal normal normal 14px/1 FontAwesome;font-size:inherit;text-rendering:auto;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;position:absolute;top:20px;left:20px;font-size:30px;width:34px;text-align:center;content:"";opacity:0.5}.note.warning,.admonition.warning,.help-block.warning{background-color:#FFFDF1;border-color:#F5F1C7}.note.warning .admonition-title::before,.admonition.warning .admonition-title::before,.help-block.warning .admonition-title::before{color:#E9BD46;content:""}.note.admonition-philosophy .admonition-title::before,.admonition.admonition-philosophy .admonition-title::before,.help-block.admonition-philosophy .admonition-title::before{content:""}.note.admonition-behind-the-scenes .admonition-title::before,.admonition.admonition-behind-the-scenes .admonition-title::before,.help-block.admonition-behind-the-scenes .admonition-title::before{content:""}.note .last,.note .highlight,.admonition .last,.admonition .highlight,.help-block .last,.help-block .highlight{margin-bottom:0px}.browse-horizontal{font-family:"Roboto", Corbel, Avenir, "Lucida Grande", "Lucida Sans", sans-serif;font-size:14px;font-size:1.4rem;font-weight:700;border-top:1px solid #CFE3DC;border-bottom:1px solid #CFE3DC;padding:20px 0;margin-top:2em}.browse-horizontal:before,.browse-horizontal:after{content:"";display:table}.browse-horizontal:after{clear:both}.browse-horizontal .left{float:left}.browse-horizontal .left .icon{margin-right:4px;font-size:12px;font-size:1.2rem}.browse-horizontal .right{float:right}.browse-horizontal .right .icon{margin-left:4px;font-size:12px;font-size:1.2rem}.browse-horizontal a{text-decoration:none}#doc-versions{position:fixed;right:15px;bottom:15px;margin:0;padding:0;z-index:1;list-style:none}#doc-versions .icon{margin-right:4px}#doc-versions li{display:none;background:#F1FFF7;margin:0 3px;font-family:"Roboto", Corbel, Avenir, "Lucida Grande", "Lucida Sans", sans-serif;color:#0C3C26;font-size:12px;font-size:1.2rem}#doc-versions li.current{display:inline-block;padding:8px 15px;border:1px solid #CFE3DC;-moz-border-radius:4px;-webkit-border-radius:4px;border-radius:4px}#doc-versions li a{display:inline-block;color:#44B78B;text-decoration:none;font-weight:700;padding:8px 15px;border:1px solid #CFE3DC;-moz-border-radius:4px;-webkit-border-radius:4px;border-radius:4px}#doc-versions li a:hover{color:#20AA76;border:1px solid #93D7B7}#doc-versions:hover li,#doc-versions .hover-on li{display:inline-block}#dev-warning,#outdated-warning{position:absolute;top:0;width:100%;padding:8px 20px 8px;-moz-box-sizing:border-box;-webkit-box-sizing:border-box;box-sizing:border-box;background-image:-webkit-linear-gradient(-45deg, rgba(0,0,0,0.04) 25%, transparent 25%, transparent 50%, rgba(0,0,0,0.04) 50%, rgba(0,0,0,0.04) 75%, transparent 75%, transparent);background-image:-moz-linear-gradient(-45deg, rgba(0,0,0,0.04) 25%, transparent 25%, transparent 50%, rgba(0,0,0,0.04) 50%, rgba(0,0,0,0.04) 75%, transparent 75%, transparent);background-image:linear-gradient(135deg, rgba(0,0,0,0.04) 25%,rgba(0,0,0,0) 25%,rgba(0,0,0,0) 50%,rgba(0,0,0,0.04) 50%,rgba(0,0,0,0.04) 75%,rgba(0,0,0,0) 75%,rgba(0,0,0,0));font-family:"Roboto", Corbel, Avenir, "Lucida Grande", "Lucida Sans", sans-serif;font-size:14px;text-align:center;background-color:#ffe761}@media screen and (min-width: 768px){#dev-warning,#outdated-warning{position:fixed;min-width:768px}}#outdated-warning{background-color:#ffbaba;color:#6A0E0E}#s-getting-help{display:none}#docs-content{font-family:"Roboto", Corbel, Avenir, "Lucida Grande", "Lucida Sans", sans-serif;font-size:14px;font-size:1.4rem;line-height:1.5em}#docs-content h1,#docs-content h2,#docs-content h3,#docs-content h4,#docs-content h5,#docs-content h6{margin:0.6em 0;line-height:1.1em}#docs-content .section{padding:10px 0 20px}#docs-content img{display:block;max-width:100%}#docs-content a.reference{color:#6A0E0E;text-decoration:none;border-bottom:1px dotted #798780}#docs-content a.reference:visited{border-color:#971414}#docs-content a.reference:active,#docs-content a.reference:focus,#docs-content a.reference:hover{background:#F1FFF7;color:#BA2121}#docs-content a.reference em{font-style:normal}.versionadded,.versionchanged,.deprecated,.versionmodified{font-family:"Roboto", Corbel, Avenir, "Lucida Grande", "Lucida Sans", sans-serif;font-weight:bold;margin-bottom:20px;padding:10px 13px;border:1px solid #C9F0DD;-moz-border-radius:4px;-webkit-border-radius:4px;border-radius:4px}.versionadded p,.versionchanged p,.deprecated p,.versionmodified p{font-weight:normal;margin-top:0.3em}.versionadded p:last-child,.versionchanged p:last-child,.deprecated p:last-child,.versionmodified p:last-child{margin-bottom:0}.deprecated .versionadded,.deprecated .versionchanged,.deprecated .deprecated,.deprecated .versionmodified{border:none;padding:0;margin-bottom:0;display:block}.deprecated p{font-weight:normal;margin-top:0}.deprecated p:last-child{margin-bottom:0}dl.function dt,dl.class dt,dl.method dt,dl.attribute dt{font-weight:700}dl.function dd,dl.class dd,dl.method dd,dl.attribute dd{padding-left:1.4em}table.docutils td,table.docutils th{border-bottom:1px solid #CFE3DC}#search-results span.highlighted{font-weight:700;color:#0C3C26}.list-links{font-family:"Roboto", Corbel, Avenir, "Lucida Grande", "Lucida Sans", sans-serif;list-style:none;margin:0;padding:10px 0 0}.list-links a{color:#20AA76;text-decoration:none}.list-links a:visited{color:#20AA76}.list-links a:hover,.list-links a:active,.list-links a:focus{color:#25c488;text-decoration:none}.list-links dt,.list-links li{font-size:16px;font-size:1.6rem;margin-top:15px;font-weight:400}.list-links dt:first-child,.list-links li:first-child{margin-top:0}.list-links dd{margin-top:0;margin-bottom:30px}[role="complementary"] .list-links{padding:0}[role="complementary"] .list-links dt,[role="complementary"] .list-links li{font-size:16px;font-size:1.6rem;border-top:1px solid #CFE3DC;margin-top:0;padding-top:20px}[role="complementary"] .list-links dt:first-child,[role="complementary"] .list-links li:first-child{border:none;padding-top:0}[role="complementary"] .list-links li{padding:14px 0 10px}[role="complementary"] .list-links dd{font-size:14px;font-size:1.4rem;margin-bottom:16px}.list-links+h2{margin-top:34px}.list-links-small{padding-left:0;list-style:none}.list-links-small a{color:#20AA76;text-decoration:none;text-decoration:none}.list-links-small a:visited{color:#20AA76}.list-links-small a:hover,.list-links-small a:active,.list-links-small a:focus{color:#25c488;text-decoration:none}.list-links-small li>a:before,.list-links-small dt>a:before{font-family:FontAwesome;font-weight:normal;font-style:normal;float:left;width:23px;height:20px}.list-links-small dt{font-weight:400}.list-links-small dd{color:#798780;padding-top:2px}.list-links-small.docs-list{list-style:none}.list-links-small.docs-list li>a:before,.list-links-small.docs-list dt>a:before{content:"\f0f6"}.list-links-small.docs-list dd{padding-left:24px}.list-links-small.news-list{list-style:none}.list-links-small.news-list li>a:before,.list-links-small.news-list dt>a:before{content:"\f0a1"}.list-links-small.news-list dt.event>a:before{content:"\f133"}.list-links-small.news-list dd{padding-left:24px}.list-links-small.resource-list{list-style:none}.list-links-small.resource-list li>a:before,.list-links-small.resource-list dt>a:before{content:"\f0c1"}.list-links-small.resource-list dd{padding-left:24px}.list-links-small.rss-list{list-style:none}.list-links-small.rss-list li>a:before,.list-links-small.rss-list dt>a:before{content:"\f09e"}.list-links-small.rss-list dd{padding-left:24px}form{font-family:"Roboto", Corbel, Avenir, "Lucida Grande", "Lucida Sans", sans-serif;font-size:16px;font-size:1.6rem}form input[type="search"],form input[type="text"],form input[type="email"],form input[type="password"],form input[type="url"],form textarea{font-family:"Roboto", Corbel, Avenir, "Lucida Grande", "Lucida Sans", sans-serif;font-size:16px;font-size:1.6rem;-webkit-appearance:none;-moz-appearance:none;background:#fff;border:1px solid #CFE3DC;-moz-border-radius:4px;-webkit-border-radius:4px;border-radius:4px;cursor:auto;display:block;font-weight:400;height:30px;margin:10px 0px;padding:6px 14% 8px 10px;text-indent:0;vertical-align:middle;width:82%}@media screen and (min-width: 768px){form input[type="search"],form input[type="text"],form input[type="email"],form input[type="password"],form input[type="url"],form textarea{padding:6px 18% 8px 10px;width:80%}}form input[type="search"]::-ms-clear,form input[type="text"]::-ms-clear,form input[type="email"]::-ms-clear,form input[type="password"]::-ms-clear,form input[type="url"]::-ms-clear,form textarea::-ms-clear{display:none}form input[type="search"]:active,form input[type="search"]:focus,form input[type="text"]:active,form input[type="text"]:focus,form input[type="email"]:active,form input[type="email"]:focus,form input[type="password"]:active,form input[type="password"]:focus,form input[type="url"]:active,form input[type="url"]:focus,form textarea:active,form textarea:focus{outline:none;border-color:#20AA76}form textarea{height:auto}form input[type=checkbox],form input[type=radio]{margin-right:6px}form select{border:1px solid #CFE3DC;background:white;height:46px;padding:0 10px;-moz-border-radius:4px;-webkit-border-radius:4px;border-radius:4px;font-size:16px;font-size:1.6rem}[role="complementary"] form select{height:36px;font-size:14px;font-size:1.4rem}form button{-moz-appearance:none;-webkit-appearance:none;background:#20AA76;-moz-border-radius:4px;-webkit-border-radius:4px;border-radius:4px;color:white;border:0;height:46px;padding:0 15px;font-family:"Roboto", Corbel, Avenir, "Lucida Grande", "Lucida Sans", sans-serif;font-size:16px;font-size:1.6rem}form button:hover{background:#44B78B}.form-general fieldset{max-width:700px;border:0;padding:0;margin:15px 0}.form-general fieldset input[type="search"],.form-general fieldset input[type="text"],.form-general fieldset input[type="email"],.form-general fieldset input[type="password"],.form-general fieldset input[type="url"]{margin:10px 0}.form-input{min-height:40px;margin:30px 0 20px;position:relative}.form-input:focus{background:#000}.form-input button{background:none;border:none;color:#44B78B;height:40px;padding:0;position:absolute;right:2%;top:6%;width:40px}@media screen and (min-width: 768px){.form-input button{right:1%}}.form-input button i{font-size:20px;font-size:2rem;line-height:1}.form-input button:hover,.form-input button:focus,.form-input button:active{background:none;color:#0C4B33;outline:none}[role="complementary"] .form-input{min-height:30px;margin:20px 0 30px}[role="complementary"] .form-input input[type="search"],[role="complementary"] .form-input input[type="text"],[role="complementary"] .form-input input[type="email"]{height:20px;font-size:14px;font-size:1.4rem}[role="complementary"] .form-input button{height:30px;width:30px;top:3px}@media screen and (min-width: 768px){[role="complementary"] .form-input button{right:0}}[role="complementary"] .form-input button i{font-size:20px;font-size:2rem}form.donate{max-width:150px}form.donate label{position:absolute;left:0px;color:#2B8C67;padding-top:0.3em;padding-left:0.5em}form.donate input[type=text]{padding-left:20px;padding-right:9px}div[role=main] form.donate label{padding-top:0.7em}::-webkit-input-placeholder,:-moz-placeholder,::-moz-placeholder,:-ms-input-placeholder{color:#859D94}.form-email h3{font-size:18px;font-size:1.8rem;margin:10px 0}.form-email .meta,.form-email .list-links dd,.list-links .form-email dd{margin:0}.form-email form{margin:10px 0 30px}.nav-pagination{font-family:"Roboto", Corbel, Avenir, "Lucida Grande", "Lucida Sans", sans-serif;font-size:14px;font-size:1.4rem;border-top:1px solid #CFE3DC;font-weight:700;line-height:31px;list-style:none;margin:30px 0;padding:30px 0 0;text-align:center}.nav-pagination li{display:inline-block}.nav-pagination a{border:none;color:#798780;height:auto;width:auto;margin:0 5px;-moz-border-radius:15px;-webkit-border-radius:15px;border-radius:15px;display:block;text-decoration:none}@media screen and (min-device-width: 320px){.nav-pagination a{background:#798780;color:#fff;height:30px;margin:0 2px;width:30px}}.nav-pagination a.previous,.nav-pagination a.next{font-size:16px;font-size:1.6rem}@media screen and (min-device-width: 320px){.nav-pagination a.previous,.nav-pagination a.next{-moz-border-radius:20px;-webkit-border-radius:20px;border-radius:20px;height:40px;line-height:43px;width:40px}}.nav-pagination a.previous{margin-right:10px}@media screen and (min-width: 768px){.nav-pagination a.previous{margin-right:70px}}.nav-pagination a.next{margin-left:10px;text-indent:1px}@media screen and (min-width: 768px){.nav-pagination a.next{margin-left:70px}}.nav-pagination a:hover,.nav-pagination a:focus,.nav-pagination a:active,.nav-pagination a.active{background:none;color:#20AA76}@media screen and (min-device-width: 320px){.nav-pagination a:hover,.nav-pagination a:focus,.nav-pagination a:active,.nav-pagination a.active{background:#20AA76;color:white}}.mdzr-no-borderradius .nav-pagination a{display:inline;background:none;color:#798780;height:auto;width:auto;margin:0 5px !important}.mdzr-no-borderradius .nav-pagination a:hover,.mdzr-no-borderradius .nav-pagination a:active,.mdzr-no-borderradius .nav-pagination a:focus,.mdzr-no-borderradius .nav-pagination a.active{background:none;color:#20AA76}hr{border:0;border-top:1px solid #CFE3DC}.badge{-moz-border-radius:4px;-webkit-border-radius:4px;border-radius:4px;font-size:12px;padding:2px 6px;margin:0 5px;letter-spacing:0px;position:relative;bottom:0.3em;color:#F1FFF7;background-color:#20AA76}.user-info .avatar{padding:20px;border:1px solid #CFE3DC;-moz-border-radius:4px;-webkit-border-radius:4px;border-radius:4px;float:right}.visuallyhidden{border:0;clip:rect(0 0 0 0);height:1px;margin:-1px;overflow:hidden;padding:0;position:absolute;width:1px}.clearfix:before,.clearfix:after,.group-container:before,.group-container:after{content:"";display:table}.clearfix:after,.group-container:after{clear:both}.fundraising-index{margin-top:50px}.fundraising-index .fundraising-heart{width:100%;float:none}.fundraising-index .fundraising-heart img,.fundraising-index .fundraising-heart svg{width:100%;height:auto}.fundraising-index .fundraising-heart rect{-moz-transition:opacity 250ms ease-out;-o-transition:opacity 250ms ease-out;-webkit-transition:opacity 250ms ease-out;transition:opacity 250ms ease-out}.fundraising-index .fundraising-heart rect.faded{opacity:0.05}.fundraising-index .fundraising-heart rect.faded:hover{opacity:0.9}@media screen and (min-width: 768px){.fundraising-index .fundraising-heart{width:55%;float:left}}.fundraising-index .description{margin-left:5%;width:100%;float:none;margin-top:30px}@media screen and (min-width: 768px){.fundraising-index .description{width:40%;float:left;margin-top:0px}}.fundraising-index .description h2{margin-top:0;font-size:28px;font-size:2.8rem}.fundraising-index .donate select{width:80%}@media screen and (min-width: 768px){.fundraising-index .donate select{width:100%}}.fundraising-index .donate .cta{margin:10px 0}.fundraising-index .donate .custom-donation{display:none}.fundraising-index .donate .prefix{float:left;font-size:19px;font-size:1.9rem;margin:8px 9px 0 3px}.fundraising-index .donate input[type="text"]{width:70%}.fundraising-index .cls{clear:both}.fundraising-donation select{width:100%}.fundraising-donation input.error{border:1px solid #BA2121 !important}.fundraising-donation p.validation-errors{color:#BA2121}.fundraising-donation .custom-donation{display:none;margin-top:10px}.fundraising-sidebar{clear:both}.fundraising-sidebar .small-heart{margin-top:20px;width:20%;float:left;margin-bottom:20px}.fundraising-sidebar .small-heart img{width:100%;max-width:64px}.fundraising-sidebar .small-cta{width:70%;float:left;vertical-align:top;margin-left:5%;margin-right:5%;margin-bottom:20px}.footnote{color:#859D94;font-size:14px;font-size:1.4rem;margin-top:20px;text-align:center}form .footnote{margin-top:10px;text-align:left}.heros-section{overflow:hidden}.heros-section .heros{clear:both}.heros-section .heros .hero{width:25%;position:relative;height:auto}.heros-section .heros .hero div{width:100%}.heros-section .heros .hero-logo{height:170px;line-height:170px}.heros-section .heros .hero-logo img{vertical-align:middle}.heros-section .heros .hero-name{height:92px;vertical-align:top}.heros-section .heros .no-logo-hero{min-height:60px;margin-right:24px}.heros-section .heros div{float:left;text-align:center;margin:15px 0}.heros-section .heros div img{max-width:90%;max-height:170px}.heros-section .pagination{clear:both}pre.literal-block,.literal-block{font-size:14px;font-size:1.4rem;border:1px solid #EAEAEA;background:#F4F4F4;background:#f8f8f8;overflow:auto;border-radius:4px;margin:25px 0;padding:10px 20px;color:#0C4B33}.snippet-filename{background:#C9F0DD;color:#0C4B33;font-family:"Fira Mono", Consolas, Menlo, Monaco, "Courier New", Courier, monospace;font-variant-ligatures:no-common-ligatures;text-rendering:optimizeSpeed;font-size:1em;padding:5px 20px;-moz-border-radius:4px 4px 0 0;-webkit-border-radius:4px;border-radius:4px 4px 0 0}.snippet-filename+.highlight{margin-top:0;-moz-border-radius:0 0 4px 4px;-webkit-border-radius:0;border-radius:0 0 4px 4px;border-top:0}.highlight{font-size:14px;font-size:1.4rem;border:1px solid #EAEAEA;background:#F4F4F4;background:#f8f8f8;overflow:auto;border-radius:4px;margin:25px 0}.highlight pre{margin:15px 20px}.highlight li{margin-top:0;border-left:1px solid #EAEAEA;padding:0 0 2px 15px}.highlight li:first-child{padding-top:2px}.highlight .hll{background-color:#ffc}.highlight .c{color:#408080;font-style:italic}.highlight .err{border:1px solid red}.highlight .k{color:#008000;font-weight:bold}.highlight .o{color:#666}.highlight .cm{color:#408080;font-style:italic}.highlight .cp{color:#BC7A00}.highlight .c1{color:#408080;font-style:italic}.highlight .cs{color:#408080;font-style:italic}.highlight .gd{color:#A00000}.highlight .ge{font-style:italic}.highlight .gr{color:red}.highlight .gh{color:#000080;font-weight:bold}.highlight .gi{color:#00A000}.highlight .go{color:gray}.highlight .gp{color:#000080;font-weight:bold}.highlight .gs{font-weight:bold}.highlight .gu{color:#800080;font-weight:bold}.highlight .gt{color:#0040D0}.highlight .kc{color:#008000;font-weight:bold}.highlight .kd{color:#008000;font-weight:bold}.highlight .kn{color:#008000;font-weight:bold}.highlight .kp{color:green}.highlight .kr{color:#008000;font-weight:bold}.highlight .kt{color:#B00040}.highlight .m{color:#666}.highlight .s{color:#BA2121}.highlight .na{color:#7D9029}.highlight .nb{color:green}.highlight .nc{color:#0000FF;font-weight:bold}.highlight .no{color:#800}.highlight .nd{color:#a2f}.highlight .ni{color:#999999;font-weight:bold}.highlight .ne{color:#D2413A;font-weight:bold}.highlight .nf{color:blue}.highlight .nl{color:#A0A000}.highlight .nn{color:#0000FF;font-weight:bold}.highlight .nt{color:#008000;font-weight:bold}.highlight .nv{color:#19177C}.highlight .ow{color:#AA22FF;font-weight:bold}.highlight .w{color:#bbb}.highlight .mf{color:#666}.highlight .mh{color:#666}.highlight .mi{color:#666}.highlight .mo{color:#666}.highlight .sb{color:#BA2121}.highlight .sc{color:#BA2121}.highlight .sd{color:#BA2121;font-style:italic}.highlight .s2{color:#BA2121}.highlight .se{color:#BB6622;font-weight:bold}.highlight .sh{color:#BA2121}.highlight .si{color:#BB6688;font-weight:bold}.highlight .sx{color:green}.highlight .sr{color:#b68}.highlight .s1{color:#BA2121}.highlight .ss{color:#19177C}.highlight .bp{color:green}.highlight .vc{color:#19177C}.highlight .vg{color:#19177C}.highlight .vi{color:#19177C}.highlight .il{color:#666}.highlight .lineno{color:#000000;background-color:#dddddd}.styleguide .example{padding:0 20px 20px;border:1px solid #CFE3DC;-moz-border-radius:4px;-webkit-border-radius:4px;border-radius:4px;margin-top:20px;margin-bottom:64px}.styleguide .example:before{content:"Example";font-size:16px;font-weight:700;display:block;color:#CFE3DC;font-family:"Roboto", Corbel, Avenir, "Lucida Grande", "Lucida Sans", sans-serif;text-align:left;padding:10px 0}.styleguide .example [role="complementary"]{float:none;width:auto;padding:0;margin:0}.styleguide .iframe{display:block;height:400px;cursor:zoom-in;border:1px solid #CFE3DC;overflow:hidden}.styleguide .iframe iframe{pointer-events:none;position:relative;width:200%;border:0;height:800px;-moz-transform:scale(0.5) translate(-50%, -50%);-ms-transform:scale(0.5) translate(-50%, -50%);-webkit-transform:scale(0.5) translate(-50%, -50%);transform:scale(0.5) translate(-50%, -50%);top:0;left:0;overflow:hidden}.styleguide .swatches{margin:0;padding:0;list-style:none;margin:30px 0}.styleguide .swatches:before,.styleguide .swatches:after{content:"";display:table}.styleguide .swatches:after{clear:both}.styleguide .swatches li{width:30%;height:30px;margin-right:2%;-moz-box-sizing:border-box;-webkit-box-sizing:border-box;box-sizing:border-box;float:left}.styleguide .swatches li.text{background:#0C3C26}.styleguide .swatches li.green-dark{background:#0C4B33}.styleguide .swatches li.green{background:#20AA76}.styleguide .swatches li.green-light{background:#93D7B7}.styleguide .swatches li.white{background:#F1FFF7;border:1px solid #CFE3DC}.styleguide .swatches li.red-dark{background:#6A0E0E}.styleguide .swatches li.text-light{background:#798780}.styleguide .swatches li.green-medium-dark{background:#2B8C67}.styleguide .swatches li.green-medium{background:#44B78B}.styleguide .swatches li.green-very-light{background:#C9F0DD}.styleguide .swatches li.gray-line{background:#CFE3DC}.styleguide .swatches li.red{background:#BA2121}.styleguide #layout{overflow:hidden}.styleguide #icons .icon{font-size:32px;font-size:3.2rem;color:#20AA76;padding:0 .2em}@media print{*{background:transparent !important;color:#000 !important;box-shadow:none !important;text-shadow:none !important}a,a:visited{text-decoration:underline}pre,blockquote{border:1px solid #999;page-break-inside:avoid}thead{display:table-header-group}tr,img{page-break-inside:avoid}img{max-width:100% !important}@page{margin:0.5cm}p,h2,h3{orphans:3;widows:3}h2,h3{page-break-after:avoid}html{font-size:40%}.menu-button,.news-search,.backtotop,.nav-pagination,[role="contentinfo"]>.container,[role="contentinfo"] .logo,.thanks,[role="complementary"],[role="navigation"],.form-input{display:none !important}.logo{text-indent:0 !important}[role="contentinfo"],[role="contentinfo"] .copyright{margin:0 !important;padding:0 !important}.internal-container{float:none;width:auto}.list-news li{margin-top:0}}
+ */@font-face{font-family:'FontAwesome';src:url("//netdna.bootstrapcdn.com/font-awesome/4.2.0/fonts/fontawesome-webfont.eot?v=4.2.0");src:url("//netdna.bootstrapcdn.com/font-awesome/4.2.0/fonts/fontawesome-webfont.eot?#iefix&v=4.2.0") format("embedded-opentype"),url("//netdna.bootstrapcdn.com/font-awesome/4.2.0/fonts/fontawesome-webfont.woff?v=4.2.0") format("woff"),url("//netdna.bootstrapcdn.com/font-awesome/4.2.0/fonts/fontawesome-webfont.ttf?v=4.2.0") format("truetype"),url("//netdna.bootstrapcdn.com/font-awesome/4.2.0/fonts/fontawesome-webfont.svg?v=4.2.0#fontawesomeregular") format("svg");font-weight:normal;font-style:normal}.icon{display:inline-block;font:normal normal normal 14px/1 FontAwesome;font-size:inherit;text-rendering:auto;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.icon-lg{font-size:1.33333em;line-height:0.75em;vertical-align:-15%}.icon-2x{font-size:2em}.icon-3x{font-size:3em}.icon-4x{font-size:4em}.icon-5x{font-size:5em}.icon-fw{width:1.28571em;text-align:center}.icon-ul{padding-left:0;margin-left:2.14286em;list-style-type:none}.icon-ul>li{position:relative}.icon-li{position:absolute;left:-2.14286em;width:2.14286em;top:0.14286em;text-align:center}.icon-li.icon-lg{left:-1.85714em}.icon-border{padding:.2em .25em .15em;border:solid 0.08em #eee;border-radius:.1em}.pull-right{float:right}.pull-left{float:left}.icon.pull-left{margin-right:.3em}.icon.pull-right{margin-left:.3em}.icon-spin{-webkit-animation:fa-spin 2s infinite linear;animation:fa-spin 2s infinite linear}@-webkit-keyframes fa-spin{0%{-webkit-transform:rotate(0deg);transform:rotate(0deg)}100%{-webkit-transform:rotate(359deg);transform:rotate(359deg)}}@keyframes fa-spin{0%{-webkit-transform:rotate(0deg);transform:rotate(0deg)}100%{-webkit-transform:rotate(359deg);transform:rotate(359deg)}}.icon-rotate-90{filter:progid:DXImageTransform.Microsoft.BasicImage(rotation=1);-webkit-transform:rotate(90deg);-ms-transform:rotate(90deg);transform:rotate(90deg)}.icon-rotate-180{filter:progid:DXImageTransform.Microsoft.BasicImage(rotation=2);-webkit-transform:rotate(180deg);-ms-transform:rotate(180deg);transform:rotate(180deg)}.icon-rotate-270{filter:progid:DXImageTransform.Microsoft.BasicImage(rotation=3);-webkit-transform:rotate(270deg);-ms-transform:rotate(270deg);transform:rotate(270deg)}.icon-flip-horizontal{filter:progid:DXImageTransform.Microsoft.BasicImage(rotation=0);-webkit-transform:scale(-1, 1);-ms-transform:scale(-1, 1);transform:scale(-1, 1)}.icon-flip-vertical{filter:progid:DXImageTransform.Microsoft.BasicImage(rotation=2);-webkit-transform:scale(1, -1);-ms-transform:scale(1, -1);transform:scale(1, -1)}:root .icon-rotate-90,:root .icon-rotate-180,:root .icon-rotate-270,:root .icon-flip-horizontal,:root .icon-flip-vertical{filter:none}.icon-stack{position:relative;display:inline-block;width:2em;height:2em;line-height:2em;vertical-align:middle}.icon-stack-1x,.icon-stack-2x{position:absolute;left:0;width:100%;text-align:center}.icon-stack-1x{line-height:inherit}.icon-stack-2x{font-size:2em}.icon-inverse{color:#fff}.icon-glass:before{content:""}.icon-music:before{content:""}.icon-search:before{content:""}.icon-envelope-o:before{content:""}.icon-heart:before{content:""}.icon-star:before{content:""}.icon-star-o:before{content:""}.icon-user:before{content:""}.icon-film:before{content:""}.icon-th-large:before{content:""}.icon-th:before{content:""}.icon-th-list:before{content:""}.icon-check:before{content:""}.icon-remove:before,.icon-close:before,.icon-times:before{content:""}.icon-search-plus:before{content:""}.icon-search-minus:before{content:""}.icon-power-off:before{content:""}.icon-signal:before{content:""}.icon-gear:before,.icon-cog:before{content:""}.icon-trash-o:before{content:""}.icon-home:before{content:""}.icon-file-o:before{content:""}.icon-clock-o:before{content:""}.icon-road:before{content:""}.icon-download:before{content:""}.icon-arrow-circle-o-down:before{content:""}.icon-arrow-circle-o-up:before{content:""}.icon-inbox:before{content:""}.icon-play-circle-o:before{content:""}.icon-rotate-right:before,.icon-repeat:before{content:""}.icon-refresh:before{content:""}.icon-list-alt:before{content:""}.icon-lock:before{content:""}.icon-flag:before{content:""}.icon-headphones:before{content:""}.icon-volume-off:before{content:""}.icon-volume-down:before{content:""}.icon-volume-up:before{content:""}.icon-qrcode:before{content:""}.icon-barcode:before{content:""}.icon-tag:before{content:""}.icon-tags:before{content:""}.icon-book:before{content:""}.icon-bookmark:before{content:""}.icon-print:before{content:""}.icon-camera:before{content:""}.icon-font:before{content:""}.icon-bold:before{content:""}.icon-italic:before{content:""}.icon-text-height:before{content:""}.icon-text-width:before{content:""}.icon-align-left:before{content:""}.icon-align-center:before{content:""}.icon-align-right:before{content:""}.icon-align-justify:before{content:""}.icon-list:before{content:""}.icon-dedent:before,.icon-outdent:before{content:""}.icon-indent:before{content:""}.icon-video-camera:before{content:""}.icon-photo:before,.icon-image:before,.icon-picture-o:before{content:""}.icon-pencil:before{content:""}.icon-map-marker:before{content:""}.icon-adjust:before{content:""}.icon-tint:before{content:""}.icon-edit:before,.icon-pencil-square-o:before{content:""}.icon-share-square-o:before{content:""}.icon-check-square-o:before{content:""}.icon-arrows:before{content:""}.icon-step-backward:before{content:""}.icon-fast-backward:before{content:""}.icon-backward:before{content:""}.icon-play:before{content:""}.icon-pause:before{content:""}.icon-stop:before{content:""}.icon-forward:before{content:""}.icon-fast-forward:before{content:""}.icon-step-forward:before{content:""}.icon-eject:before{content:""}.icon-chevron-left:before{content:""}.icon-chevron-right:before{content:""}.icon-plus-circle:before{content:""}.icon-minus-circle:before{content:""}.icon-times-circle:before{content:""}.icon-check-circle:before{content:""}.icon-question-circle:before{content:""}.icon-info-circle:before{content:""}.icon-crosshairs:before{content:""}.icon-times-circle-o:before{content:""}.icon-check-circle-o:before{content:""}.icon-ban:before{content:""}.icon-arrow-left:before{content:""}.icon-arrow-right:before{content:""}.icon-arrow-up:before{content:""}.icon-arrow-down:before{content:""}.icon-mail-forward:before,.icon-share:before{content:""}.icon-expand:before{content:""}.icon-compress:before{content:""}.icon-plus:before{content:""}.icon-minus:before{content:""}.icon-asterisk:before{content:""}.icon-exclamation-circle:before{content:""}.icon-gift:before{content:""}.icon-leaf:before{content:""}.icon-fire:before{content:""}.icon-eye:before{content:""}.icon-eye-slash:before{content:""}.icon-warning:before,.icon-exclamation-triangle:before{content:""}.icon-plane:before{content:""}.icon-calendar:before{content:""}.icon-random:before{content:""}.icon-comment:before{content:""}.icon-magnet:before{content:""}.icon-chevron-up:before{content:""}.icon-chevron-down:before{content:""}.icon-retweet:before{content:""}.icon-shopping-cart:before{content:""}.icon-folder:before{content:""}.icon-folder-open:before{content:""}.icon-arrows-v:before{content:""}.icon-arrows-h:before{content:""}.icon-bar-chart-o:before,.icon-bar-chart:before{content:""}.icon-twitter-square:before{content:""}.icon-facebook-square:before{content:""}.icon-camera-retro:before{content:""}.icon-key:before{content:""}.icon-gears:before,.icon-cogs:before{content:""}.icon-comments:before{content:""}.icon-thumbs-o-up:before{content:""}.icon-thumbs-o-down:before{content:""}.icon-star-half:before{content:""}.icon-heart-o:before{content:""}.icon-sign-out:before{content:""}.icon-linkedin-square:before{content:""}.icon-thumb-tack:before{content:""}.icon-external-link:before{content:""}.icon-sign-in:before{content:""}.icon-trophy:before{content:""}.icon-github-square:before{content:""}.icon-upload:before{content:""}.icon-lemon-o:before{content:""}.icon-phone:before{content:""}.icon-square-o:before{content:""}.icon-bookmark-o:before{content:""}.icon-phone-square:before{content:""}.icon-twitter:before{content:""}.icon-facebook:before{content:""}.icon-github:before{content:""}.icon-unlock:before{content:""}.icon-credit-card:before{content:""}.icon-rss:before{content:""}.icon-hdd-o:before{content:""}.icon-bullhorn:before{content:""}.icon-bell:before{content:""}.icon-certificate:before{content:""}.icon-hand-o-right:before{content:""}.icon-hand-o-left:before{content:""}.icon-hand-o-up:before{content:""}.icon-hand-o-down:before{content:""}.icon-arrow-circle-left:before{content:""}.icon-arrow-circle-right:before{content:""}.icon-arrow-circle-up:before{content:""}.icon-arrow-circle-down:before{content:""}.icon-globe:before{content:""}.icon-wrench:before{content:""}.icon-tasks:before{content:""}.icon-filter:before{content:""}.icon-briefcase:before{content:""}.icon-arrows-alt:before{content:""}.icon-group:before,.icon-users:before{content:""}.icon-chain:before,.icon-link:before{content:""}.icon-cloud:before{content:""}.icon-flask:before{content:""}.icon-cut:before,.icon-scissors:before{content:""}.icon-copy:before,.icon-files-o:before{content:""}.icon-paperclip:before{content:""}.icon-save:before,.icon-floppy-o:before{content:""}.icon-square:before{content:""}.icon-navicon:before,.icon-reorder:before,.icon-bars:before{content:""}.icon-list-ul:before{content:""}.icon-list-ol:before{content:""}.icon-strikethrough:before{content:""}.icon-underline:before{content:""}.icon-table:before{content:""}.icon-magic:before{content:""}.icon-truck:before{content:""}.icon-pinterest:before{content:""}.icon-pinterest-square:before{content:""}.icon-google-plus-square:before{content:""}.icon-google-plus:before{content:""}.icon-money:before{content:""}.icon-caret-down:before{content:""}.icon-caret-up:before{content:""}.icon-caret-left:before{content:""}.icon-caret-right:before{content:""}.icon-columns:before{content:""}.icon-unsorted:before,.icon-sort:before{content:""}.icon-sort-down:before,.icon-sort-desc:before{content:""}.icon-sort-up:before,.icon-sort-asc:before{content:""}.icon-envelope:before{content:""}.icon-linkedin:before{content:""}.icon-rotate-left:before,.icon-undo:before{content:""}.icon-legal:before,.icon-gavel:before{content:""}.icon-dashboard:before,.icon-tachometer:before{content:""}.icon-comment-o:before{content:""}.icon-comments-o:before{content:""}.icon-flash:before,.icon-bolt:before{content:""}.icon-sitemap:before{content:""}.icon-umbrella:before{content:""}.icon-paste:before,.icon-clipboard:before{content:""}.icon-lightbulb-o:before{content:""}.icon-exchange:before{content:""}.icon-cloud-download:before{content:""}.icon-cloud-upload:before{content:""}.icon-user-md:before{content:""}.icon-stethoscope:before{content:""}.icon-suitcase:before{content:""}.icon-bell-o:before{content:""}.icon-coffee:before{content:""}.icon-cutlery:before{content:""}.icon-file-text-o:before{content:""}.icon-building-o:before{content:""}.icon-hospital-o:before{content:""}.icon-ambulance:before{content:""}.icon-medkit:before{content:""}.icon-fighter-jet:before{content:""}.icon-beer:before{content:""}.icon-h-square:before{content:""}.icon-plus-square:before{content:""}.icon-angle-double-left:before{content:""}.icon-angle-double-right:before{content:""}.icon-angle-double-up:before{content:""}.icon-angle-double-down:before{content:""}.icon-angle-left:before{content:""}.icon-angle-right:before{content:""}.icon-angle-up:before{content:""}.icon-angle-down:before{content:""}.icon-desktop:before{content:""}.icon-laptop:before{content:""}.icon-tablet:before{content:""}.icon-mobile-phone:before,.icon-mobile:before{content:""}.icon-circle-o:before{content:""}.icon-quote-left:before{content:""}.icon-quote-right:before{content:""}.icon-spinner:before{content:""}.icon-circle:before{content:""}.icon-mail-reply:before,.icon-reply:before{content:""}.icon-github-alt:before{content:""}.icon-folder-o:before{content:""}.icon-folder-open-o:before{content:""}.icon-smile-o:before{content:""}.icon-frown-o:before{content:""}.icon-meh-o:before{content:""}.icon-gamepad:before{content:""}.icon-keyboard-o:before{content:""}.icon-flag-o:before{content:""}.icon-flag-checkered:before{content:""}.icon-terminal:before{content:""}.icon-code:before{content:""}.icon-mail-reply-all:before,.icon-reply-all:before{content:""}.icon-star-half-empty:before,.icon-star-half-full:before,.icon-star-half-o:before{content:""}.icon-location-arrow:before{content:""}.icon-crop:before{content:""}.icon-code-fork:before{content:""}.icon-unlink:before,.icon-chain-broken:before{content:""}.icon-question:before{content:""}.icon-info:before{content:""}.icon-exclamation:before{content:""}.icon-superscript:before{content:""}.icon-subscript:before{content:""}.icon-eraser:before{content:""}.icon-puzzle-piece:before{content:""}.icon-microphone:before{content:""}.icon-microphone-slash:before{content:""}.icon-shield:before{content:""}.icon-calendar-o:before{content:""}.icon-fire-extinguisher:before{content:""}.icon-rocket:before{content:""}.icon-maxcdn:before{content:""}.icon-chevron-circle-left:before{content:""}.icon-chevron-circle-right:before{content:""}.icon-chevron-circle-up:before{content:""}.icon-chevron-circle-down:before{content:""}.icon-html5:before{content:""}.icon-css3:before{content:""}.icon-anchor:before{content:""}.icon-unlock-alt:before{content:""}.icon-bullseye:before{content:""}.icon-ellipsis-h:before{content:""}.icon-ellipsis-v:before{content:""}.icon-rss-square:before{content:""}.icon-play-circle:before{content:""}.icon-ticket:before{content:""}.icon-minus-square:before{content:""}.icon-minus-square-o:before{content:""}.icon-level-up:before{content:""}.icon-level-down:before{content:""}.icon-check-square:before{content:""}.icon-pencil-square:before{content:""}.icon-external-link-square:before{content:""}.icon-share-square:before{content:""}.icon-compass:before{content:""}.icon-toggle-down:before,.icon-caret-square-o-down:before{content:""}.icon-toggle-up:before,.icon-caret-square-o-up:before{content:""}.icon-toggle-right:before,.icon-caret-square-o-right:before{content:""}.icon-euro:before,.icon-eur:before{content:""}.icon-gbp:before{content:""}.icon-dollar:before,.icon-usd:before{content:""}.icon-rupee:before,.icon-inr:before{content:""}.icon-cny:before,.icon-rmb:before,.icon-yen:before,.icon-jpy:before{content:""}.icon-ruble:before,.icon-rouble:before,.icon-rub:before{content:""}.icon-won:before,.icon-krw:before{content:""}.icon-bitcoin:before,.icon-btc:before{content:""}.icon-file:before{content:""}.icon-file-text:before{content:""}.icon-sort-alpha-asc:before{content:""}.icon-sort-alpha-desc:before{content:""}.icon-sort-amount-asc:before{content:""}.icon-sort-amount-desc:before{content:""}.icon-sort-numeric-asc:before{content:""}.icon-sort-numeric-desc:before{content:""}.icon-thumbs-up:before{content:""}.icon-thumbs-down:before{content:""}.icon-youtube-square:before{content:""}.icon-youtube:before{content:""}.icon-xing:before{content:""}.icon-xing-square:before{content:""}.icon-youtube-play:before{content:""}.icon-dropbox:before{content:""}.icon-stack-overflow:before{content:""}.icon-instagram:before{content:""}.icon-flickr:before{content:""}.icon-adn:before{content:""}.icon-bitbucket:before{content:""}.icon-bitbucket-square:before{content:""}.icon-tumblr:before{content:""}.icon-tumblr-square:before{content:""}.icon-long-arrow-down:before{content:""}.icon-long-arrow-up:before{content:""}.icon-long-arrow-left:before{content:""}.icon-long-arrow-right:before{content:""}.icon-apple:before{content:""}.icon-windows:before{content:""}.icon-android:before{content:""}.icon-linux:before{content:""}.icon-dribbble:before{content:""}.icon-skype:before{content:""}.icon-foursquare:before{content:""}.icon-trello:before{content:""}.icon-female:before{content:""}.icon-male:before{content:""}.icon-gittip:before{content:""}.icon-sun-o:before{content:""}.icon-moon-o:before{content:""}.icon-archive:before{content:""}.icon-bug:before{content:""}.icon-vk:before{content:""}.icon-weibo:before{content:""}.icon-renren:before{content:""}.icon-pagelines:before{content:""}.icon-stack-exchange:before{content:""}.icon-arrow-circle-o-right:before{content:""}.icon-arrow-circle-o-left:before{content:""}.icon-toggle-left:before,.icon-caret-square-o-left:before{content:""}.icon-dot-circle-o:before{content:""}.icon-wheelchair:before{content:""}.icon-vimeo-square:before{content:""}.icon-turkish-lira:before,.icon-try:before{content:""}.icon-plus-square-o:before{content:""}.icon-space-shuttle:before{content:""}.icon-slack:before{content:""}.icon-envelope-square:before{content:""}.icon-wordpress:before{content:""}.icon-openid:before{content:""}.icon-institution:before,.icon-bank:before,.icon-university:before{content:""}.icon-mortar-board:before,.icon-graduation-cap:before{content:""}.icon-yahoo:before{content:""}.icon-google:before{content:""}.icon-reddit:before{content:""}.icon-reddit-square:before{content:""}.icon-stumbleupon-circle:before{content:""}.icon-stumbleupon:before{content:""}.icon-delicious:before{content:""}.icon-digg:before{content:""}.icon-pied-piper:before{content:""}.icon-pied-piper-alt:before{content:""}.icon-drupal:before{content:""}.icon-joomla:before{content:""}.icon-language:before{content:""}.icon-fax:before{content:""}.icon-building:before{content:""}.icon-child:before{content:""}.icon-paw:before{content:""}.icon-spoon:before{content:""}.icon-cube:before{content:""}.icon-cubes:before{content:""}.icon-behance:before{content:""}.icon-behance-square:before{content:""}.icon-steam:before{content:""}.icon-steam-square:before{content:""}.icon-recycle:before{content:""}.icon-automobile:before,.icon-car:before{content:""}.icon-cab:before,.icon-taxi:before{content:""}.icon-tree:before{content:""}.icon-spotify:before{content:""}.icon-deviantart:before{content:""}.icon-soundcloud:before{content:""}.icon-database:before{content:""}.icon-file-pdf-o:before{content:""}.icon-file-word-o:before{content:""}.icon-file-excel-o:before{content:""}.icon-file-powerpoint-o:before{content:""}.icon-file-photo-o:before,.icon-file-picture-o:before,.icon-file-image-o:before{content:""}.icon-file-zip-o:before,.icon-file-archive-o:before{content:""}.icon-file-sound-o:before,.icon-file-audio-o:before{content:""}.icon-file-movie-o:before,.icon-file-video-o:before{content:""}.icon-file-code-o:before{content:""}.icon-vine:before{content:""}.icon-codepen:before{content:""}.icon-jsfiddle:before{content:""}.icon-life-bouy:before,.icon-life-buoy:before,.icon-life-saver:before,.icon-support:before,.icon-life-ring:before{content:""}.icon-circle-o-notch:before{content:""}.icon-ra:before,.icon-rebel:before{content:""}.icon-ge:before,.icon-empire:before{content:""}.icon-git-square:before{content:""}.icon-git:before{content:""}.icon-hacker-news:before{content:""}.icon-tencent-weibo:before{content:""}.icon-qq:before{content:""}.icon-wechat:before,.icon-weixin:before{content:""}.icon-send:before,.icon-paper-plane:before{content:""}.icon-send-o:before,.icon-paper-plane-o:before{content:""}.icon-history:before{content:""}.icon-circle-thin:before{content:""}.icon-header:before{content:""}.icon-paragraph:before{content:""}.icon-sliders:before{content:""}.icon-share-alt:before{content:""}.icon-share-alt-square:before{content:""}.icon-bomb:before{content:""}.icon-soccer-ball-o:before,.icon-futbol-o:before{content:""}.icon-tty:before{content:""}.icon-binoculars:before{content:""}.icon-plug:before{content:""}.icon-slideshare:before{content:""}.icon-twitch:before{content:""}.icon-yelp:before{content:""}.icon-newspaper-o:before{content:""}.icon-wifi:before{content:""}.icon-calculator:before{content:""}.icon-paypal:before{content:""}.icon-google-wallet:before{content:""}.icon-cc-visa:before{content:""}.icon-cc-mastercard:before{content:""}.icon-cc-discover:before{content:""}.icon-cc-amex:before{content:""}.icon-cc-paypal:before{content:""}.icon-cc-stripe:before{content:""}.icon-bell-slash:before{content:""}.icon-bell-slash-o:before{content:""}.icon-trash:before{content:""}.icon-copyright:before{content:""}.icon-at:before{content:""}.icon-eyedropper:before{content:""}.icon-paint-brush:before{content:""}.icon-birthday-cake:before{content:""}.icon-area-chart:before{content:""}.icon-pie-chart:before{content:""}.icon-line-chart:before{content:""}.icon-lastfm:before{content:""}.icon-lastfm-square:before{content:""}.icon-toggle-off:before{content:""}.icon-toggle-on:before{content:""}.icon-bicycle:before{content:""}.icon-bus:before{content:""}.icon-ioxhost:before{content:""}.icon-angellist:before{content:""}.icon-cc:before{content:""}.icon-shekel:before,.icon-sheqel:before,.icon-ils:before{content:""}.icon-meanpath:before{content:""}body{font-family:Palatino, "Palatino Linotype", "Book Antiqua", "Hoefler Text", Georgia, "Lucida Bright", Cambria, Times, "Times New Roman", serif;font-size:18px;font-size:1.8rem;background:#f8f8f8;color:#0C3C26;line-height:1.6;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}body .layout-secondary,body [role="complementary"]{font-family:"Roboto", Corbel, Avenir, "Lucida Grande", "Lucida Sans", sans-serif}@media screen and (min-width: 768px){body{min-width:768px}}a{color:#0C3C26;text-decoration:underline;-webkit-tap-highlight-color:transparent}a:visited{color:#156641}a:active,a:focus,a:hover{color:#1d915c}::selection{background:#C9F0DD}::-moz-selection{background:#C9F0DD}ol li,ul li{margin-top:10px}dl{margin:20px 0 10px}dl dt{font-family:"Roboto", Corbel, Avenir, "Lucida Grande", "Lucida Sans", sans-serif;font-weight:400}dl dd{margin:0.2em 0 1.2em;padding:0}dl dd:last-of-type{margin-bottom:0}h1,h2,h3,h4,h5,h6{font-family:"Roboto", Corbel, Avenir, "Lucida Grande", "Lucida Sans", sans-serif;font-weight:400}h1 a,h2 a,h3 a,h4 a,h5 a,h6 a{text-decoration:none;color:#20AA76;text-decoration:none}h1 a:visited,h2 a:visited,h3 a:visited,h4 a:visited,h5 a:visited,h6 a:visited{color:#20AA76}h1 a:hover,h1 a:active,h1 a:focus,h2 a:hover,h2 a:active,h2 a:focus,h3 a:hover,h3 a:active,h3 a:focus,h4 a:hover,h4 a:active,h4 a:focus,h5 a:hover,h5 a:active,h5 a:focus,h6 a:hover,h6 a:active,h6 a:focus{color:#25c488;text-decoration:none}h1{font-size:28px;font-size:2.8rem;color:#fff;letter-spacing:-1px;line-height:1.1}@media screen and (min-width: 768px){h1{font-size:32px;font-size:3.2rem}}.layout-secondary h1{color:#0C3C26}[role="main"] h1{font-size:32px;font-size:3.2rem;margin:40px 0px 30px;color:#0C3C26}[role="complementary"] h1{font-size:28px;font-size:2.8rem}h2{font-size:24px;font-size:2.4rem}[role="complementary"] h2,.layout-secondary h2{font-size:20px;font-size:2rem;border-bottom:1px solid #CFE3DC;font-weight:400;padding-bottom:15px;margin-top:30px}[role="complementary"] h2:first-of-type,.layout-secondary h2:first-of-type{margin-top:inherit}@media screen and (min-width: 768px){[role="complementary"] h2:first-child,.layout-secondary h2:first-child{margin-top:20px}}.full-width [role="complementary"] h2,.full-width .layout-secondary h2{font-size:24px;font-size:2.4rem}[role="main"] h2{margin-top:40px;margin-bottom:15px}h3{font-size:20px;font-size:2rem;font-weight:700;color:#0C3C26;line-height:1.2;margin:35px 0 20px}[role="complementary"] h3,.layout-secondary h3{font-size:18px;font-size:1.8rem;font-weight:400;padding-bottom:15px}@media screen and (min-width: 768px){[role="complementary"] h3:first-child,.layout-secondary h3:first-child{margin-top:12px}}[role="complementary"] h3{font-size:18px;font-size:1.8rem;border-bottom:1px solid #CFE3DC}h4{font-size:16px;font-size:1.6rem;color:#0C3C26;line-height:1.2;margin:35px 0 20px;font-weight:700}tt,code,kbd,pre,samp{font-family:"Fira Mono", Consolas, Menlo, Monaco, "Courier New", Courier, monospace;font-variant-ligatures:no-common-ligatures;text-rendering:optimizeSpeed;color:#0C4B33;font-size:1em}tt{font-weight:700}span.pre{font-family:"Fira Mono", Consolas, Menlo, Monaco, "Courier New", Courier, monospace;font-variant-ligatures:no-common-ligatures;text-rendering:optimizeSpeed}a:hover tt,a:active tt,a:focus tt{color:#1d915c}[role="main"]>p:first-child{margin-top:30px}ul{padding-left:20px}blockquote{background:#F1FFF7;padding:15px 20px 15px 70px;border:1px solid #C9F0DD;-moz-border-radius:4px;-webkit-border-radius:4px;border-radius:4px;margin:25px 0;position:relative}blockquote p:first-child{margin-top:0}blockquote::before{display:inline-block;font:normal normal normal 14px/1 FontAwesome;font-size:inherit;text-rendering:auto;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;position:absolute;top:20px;left:20px;font-size:30px;width:34px;text-align:center;content:"";opacity:0.5}[role="main"]{background:#fff;padding:1px 10px 40px}@media screen and (min-width: 768px){[role="main"]{min-height:800px;border:1px solid #ddd;float:right;margin:0;padding-bottom:80px;padding-left:3%;padding-right:3%;width:60%}.mdzr-boxshadow [role="main"]{border:none;-moz-box-shadow:0px 0px 0 0 #fff,0px 0px 0 0 #fff,460px 0 0 0 #fff,1000px 0 0 0 #fff,0px 600px 0 0px #fff,460px 600px 0 0px #fff;-webkit-box-shadow:0px 0px 0 0 #fff,0px 0px 0 0 #fff,460px 0 0 0 #fff,1000px 0 0 0 #fff,0px 600px 0 0px #fff,460px 600px 0 0px #fff;box-shadow:0px 0px 0 0 #fff,0px 0px 0 0 #fff,460px 0 0 0 #fff,1000px 0 0 0 #fff,0px 600px 0 0px #fff,460px 600px 0 0px #fff;padding-left:4%;padding-right:0;width:63%}.sidebar-right [role="main"]{float:left}.mdzr-boxshadow .sidebar-right [role="main"]{border:none;-moz-box-shadow:0px 0px 0 0 #fff,0px 0px 0 0 #fff,-460px 0 0 0 #fff,-1000px 0 0 0 #fff,0px 600px 0 0px #fff,-460px 600px 0 0px #fff;-webkit-box-shadow:0px 0px 0 0 #fff,0px 0px 0 0 #fff,-460px 0 0 0 #fff,-1000px 0 0 0 #fff,0px 600px 0 0px #fff,-460px 600px 0 0px #fff;box-shadow:0px 0px 0 0 #fff,0px 0px 0 0 #fff,-460px 0 0 0 #fff,-1000px 0 0 0 #fff,0px 600px 0 0px #fff,-460px 600px 0 0px #fff;padding-left:0;width:62%;padding-right:4%}}.full-width [role="main"]{border:none;-moz-box-shadow:none;-webkit-box-shadow:none;box-shadow:none;float:none;margin:0 auto;padding:0 10px 40px;width:auto}@media screen and (min-width: 768px){.full-width [role="main"]{max-width:740px;padding:20px 0 40px;width:91.66667%}}[role="main"] .section{padding-bottom:40px;border-bottom:1px solid #CFE3DC}[role="main"] .section:last-of-type{padding-bottom:0;border-bottom:0}[role="main"] .section dd.last-child{padding-bottom:0}.full-width.container{width:100%;padding:0;max-width:none;border-bottom:1px solid #ddd;background:#fff}.full-width:before,.full-width:after{content:"";display:table}.full-width:after{clear:both}.mdzr-boxshadow .full-width{border:none;-moz-box-shadow:0 4px 8px rgba(12,60,38,0.07);-webkit-box-shadow:0 4px 8px rgba(12,60,38,0.07);box-shadow:0 4px 8px rgba(12,60,38,0.07)}[role="complementary"]{padding:0 10px 20px;font-size:14px;font-size:1.4rem}@media screen and (min-width: 768px){[role="complementary"]{float:right;margin:20px 0;margin-right:3%;padding:0 0 40px 0;width:30%}.sidebar-right [role="complementary"]{margin-left:3%;margin-right:0}}[role="complementary"] span.form-controls{display:none}[role="complementary"] .list-collapsing{margin-top:0;border-bottom:0}[role="complementary"] .list-collapsing.active li{border-top:0}[role="complementary"] .list-collapsing.active h2{padding:10px 40px 10px 0;font-size:18px;border-bottom:0;color:#44B78B}[role="complementary"] .list-collapsing.active h2 .collapsing-icon{font-size:10px}[role="secondary"]{margin:0 10px;padding:40px 0 60px}@media screen and (min-width: 768px){.full-width [role="secondary"]{max-width:700px;margin:0 auto}}.layout-secondary{padding:20px 10px 50px}.layout-tertiary{background:#fff;border-top:1px solid #ddd;padding:20px 10px 50px}.mdzr-boxshadow .layout-tertiary{border:none;-moz-box-shadow:0 -4px 8px rgba(12,60,38,0.07);-webkit-box-shadow:0 -4px 8px rgba(12,60,38,0.07);box-shadow:0 -4px 8px rgba(12,60,38,0.07)}.container:before,.container:after{content:"";display:table}.container:after{clear:both}@media screen and (min-width: 768px){.container{margin:0 auto;max-width:1400px;padding:0 4.16667%}}.mdzr-boxshadow .container.sidebar-right{-moz-box-shadow:-1200px 0 0 0px #fff;-webkit-box-shadow:-1200px 0 0 0px #fff;box-shadow:-1200px 0 0 0px #fff}[role="banner"]{background:#0C4B33;overflow:hidden;margin:0;padding:10px 0 6px;position:relative;z-index:0}[role="banner"]:before,[role="banner"]:after{content:"";display:table}[role="banner"]:after{clear:both}@media screen and (min-width: 768px){[role="banner"] .container{position:relative}}[role="banner"] .meta,[role="banner"] .list-links dd,.list-links [role="banner"] dd{font-size:13px;font-size:1.3rem;color:#44B78B;font-weight:700;width:auto;float:left;margin:8px 0 0 10px;display:none}@media screen and (min-width: 1150px){[role="banner"] .meta,[role="banner"] .list-links dd,.list-links [role="banner"] dd{float:left;width:200px;display:block}}[role="banner"] .logo{font-size:40px;font-size:4rem;font-family:"Roboto", Corbel, Avenir, "Lucida Grande", "Lucida Sans", sans-serif;background:url(../img/logo-django.png) 0 0 no-repeat;color:#FFF;display:block;float:left;font-weight:700;margin:10px;overflow:hidden;text-decoration:none;text-indent:100%;width:104px;height:36px}.mdzr-svg [role="banner"] .logo{background:url(../img/logo-django.svg) center center no-repeat}@media screen and (min-width: 768px){[role="banner"] .logo{margin-left:0}}[role="banner"] .menu-button{font-size:20px;font-size:2rem;background:#0C4B33;-moz-border-radius:23px;-webkit-border-radius:23px;border-radius:23px;color:#fff;cursor:pointer;display:block;float:right;height:45px;line-height:48px;margin:4px 10px;text-align:center;text-decoration:none;width:45px}@media screen and (min-width: 768px){[role="banner"] .menu-button{display:none}}[role="banner"] .menu-button:active{color:#44B78B}[role="banner"] .menu-button span{border:0;clip:rect(0 0 0 0);height:1px;margin:-1px;overflow:hidden;padding:0;position:absolute;width:1px}[role="banner"] .menu-button.active{opacity:0.5}[role="banner"] .nav-menu-on{max-height:0;overflow:hidden;-moz-transition:all 0.3s ease-out;-o-transition:all 0.3s ease-out;-webkit-transition:all 0.3s ease-out;transition:all 0.3s ease-out}@media screen and (min-width: 768px){[role="banner"] .nav-menu-on{max-height:none;-moz-transition:none;-o-transition:none;-webkit-transition:none;transition:none}}[role="banner"] .nav-menu-on.active{max-height:500px}[role="banner"] [role="navigation"]{background:#0C4B33;width:100%}@media screen and (min-width: 768px){[role="banner"] [role="navigation"]{width:auto;float:right}}[role="banner"] [role="navigation"] ul{margin:10px 0 0;padding:0}@media screen and (min-width: 768px){[role="banner"] [role="navigation"] ul{margin:0}}[role="banner"] [role="navigation"] li{font-family:"Roboto", Corbel, Avenir, "Lucida Grande", "Lucida Sans", sans-serif;font-size:13px;font-size:1.3rem;display:block;font-weight:700;line-height:16px;text-align:left;text-transform:uppercase;margin:0 10px;border-top:1px solid #106142}@media screen and (min-width: 768px){[role="banner"] [role="navigation"] li{margin:0;border:0;float:left;text-align:left}}[role="banner"] [role="navigation"] li.active a{color:#44B78B}[role="banner"] [role="navigation"] a{color:#fff;display:block;padding:20px 0px;text-decoration:none}[role="banner"] [role="navigation"] a:active,[role="banner"] [role="navigation"] a:hover{color:#C9F0DD}@media screen and (min-width: 768px){[role="banner"] [role="navigation"] a{padding:20px 10px}}@media screen and (min-width: 768px){[role="banner"] [role="navigation"] .nav-primary{position:absolute;right:0;top:45px}}.header{font-family:"Roboto", Corbel, Avenir, "Lucida Grande", "Lucida Sans", sans-serif;background:#0C4B33;margin:0;padding:11px 0px 8px;position:relative}.header h1{margin:0.4em 0}.header p{font-family:Palatino, "Palatino Linotype", "Book Antiqua", "Hoefler Text", Georgia, "Lucida Bright", Cambria, Times, "Times New Roman", serif;font-size:18px;font-size:1.8rem;color:#fff;left:-9999px;line-height:1.5;padding:0 0 10px;position:absolute;top:0;max-width:660px}@media screen and (min-width: 768px){.header p{position:static}}.copy-banner{background:#44B78B;padding:1px 10px}@media screen and (min-width: 768px){.copy-banner{padding:1px 0}}.copy-banner p,.copy-banner h1{font-family:"Roboto", Corbel, Avenir, "Lucida Grande", "Lucida Sans", sans-serif;font-size:24px;font-size:2.4rem;color:#C9F0DD;font-weight:300;line-height:1.3;padding:1px 0 6px;margin:.45em 0 .35em}.copy-banner p em,.copy-banner h1 em{font-style:normal;color:white}@media screen and (min-width: 768px){.copy-banner p,.copy-banner h1{font-size:32px;font-size:3.2rem;margin:.35em 0 .35em;color:#C9F0DD;padding:1px 0 6px}}.copy-banner p a,.copy-banner h1 a{font-weight:300;color:#C9F0DD}.copy-banner p a.cta,.copy-banner p .cta,.copy-banner h1 a.cta,.copy-banner h1 .cta{margin:0;font-size:18px;font-size:1.8rem}.copy-banner a.cta,.copy-banner .cta{margin:15px 0;padding:0.4em 1.5em 0.5em;background:#2B8C67;background:none;border:1px solid #C9F0DD;color:#C9F0DD;font-weight:400}.copy-banner a.cta:hover,.copy-banner .cta:hover{background:#309c72}.copy-banner a.cta em,.copy-banner .cta em{color:white}.copy-banner a.cta:hover,.copy-banner .cta:hover{background:#F1FFF7;color:#20AA76;border-color:#F1FFF7}.copy-banner a.cta:hover em,.copy-banner .cta:hover em{color:#20AA76}.copy-banner a.cta.white,.copy-banner .cta.white{background:#F1FFF7;color:#20AA76;font-weight:700;border:0}.copy-banner a.cta.white:hover,.copy-banner .cta.white:hover{background:#fff}.homepage .copy-banner{padding:50px 0;background:white;text-align:center;border-bottom:1px solid #CFE3DC}.homepage .copy-banner p{max-width:700px;margin-left:auto;margin-right:auto;margin:1em auto .5em;color:#0C3C26;font-size:36px;font-size:3.6rem}.homepage .copy-banner p.small{color:#798780;margin:2em auto 1em;font-size:14px;font-size:1.4rem}.homepage .copy-banner p em{color:#0C3C26}.homepage .copy-banner a.cta,.homepage .copy-banner .cta{display:inline-block;padding:1em 50px 1.1em;margin-bottom:40px;background:#44B78B;color:white;border:0;font-weight:700}.homepage .copy-banner a.cta:hover,.homepage .copy-banner .cta:hover{background:#51be95}.homepage .copy-banner a.cta:active,.homepage .copy-banner .cta:active{background:#41b085}.homepage .copy-banner .django-companies{max-width:750px;margin:0 auto;list-style:none;padding:0 0 0 30px}.homepage .copy-banner .django-companies li{width:144px;display:inline-block;text-indent:-1000px;overflow:hidden;margin:0}.homepage .copy-banner .django-companies li a{display:block;height:46px;background-position:center;background-repeat:no-repeat}.homepage .copy-banner .django-companies li a.company-mozilla{background-image:url("../img/company-mozilla.png");background-position:center 7px}.mdzr-svg .homepage .copy-banner .django-companies li a.company-mozilla{background-image:url("../img/company-mozilla.svg")}.homepage .copy-banner .django-companies li a.company-pinterest{background-image:url("../img/company-pinterest.png");background-position:center 7px}.mdzr-svg .homepage .copy-banner .django-companies li a.company-pinterest{background-image:url("../img/company-pinterest.svg")}.homepage .copy-banner .django-companies li a.company-theguardian{background-image:url("../img/company-theguardian.png");background-position:center 14px}.mdzr-svg .homepage .copy-banner .django-companies li a.company-theguardian{background-image:url("../img/company-theguardian.svg")}.homepage .copy-banner .django-companies li a.company-instagram{background-image:url("../img/company-instagram.png");background-position:center 10px}.mdzr-svg .homepage .copy-banner .django-companies li a.company-instagram{background-image:url("../img/company-instagram.svg")}.homepage .copy-banner .django-companies li a.company-rdio{background-image:url("../img/company-rdio.png");background-position:15px 6px}.mdzr-svg .homepage .copy-banner .django-companies li a.company-rdio{background-image:url("../img/company-rdio.svg")}[role="alert"]{clear:both;background:#F1FFF7;position:relative;-moz-box-shadow:0 -2px 8px 0 rgba(0,0,0,0.05);-webkit-box-shadow:0 -2px 8px 0 rgba(0,0,0,0.05);box-shadow:0 -2px 8px 0 rgba(0,0,0,0.05)}[role="alert"] a{color:#20AA76;text-decoration:none}[role="alert"] a:visited{color:#20AA76}[role="alert"] a:hover,[role="alert"] a:active,[role="alert"] a:focus{color:#25c488;text-decoration:none}[role="alert"] a.link-readmore{margin:0}[role="alert"] dl{margin:0 10px;padding:25px 0}[role="alert"] dl:before,[role="alert"] dl:after{content:"";display:table}[role="alert"] dl:after{clear:both}[role="alert"] dl dt i.icon{color:#20AA76;margin-right:8px}[role="alert"] dl dd{float:left;width:60%;margin-top:0;font-family:"Roboto", Corbel, Avenir, "Lucida Grande", "Lucida Sans", sans-serif}[role="alert"] dl .link-readmore{margin-left:10px}@media screen and (min-width: 768px){[role="alert"] dt{float:left;width:31%;padding-right:2%}[role="alert"] dl{margin:0}}[role="contentinfo"]{font-family:"Roboto", Corbel, Avenir, "Lucida Grande", "Lucida Sans", sans-serif;position:relative;background:#44B78B;clear:both;margin-top:0px}[role="contentinfo"]:before,[role="contentinfo"]:after{content:"";display:table}[role="contentinfo"]:after{clear:both}[role="contentinfo"] .container{overflow:hidden}[role="contentinfo"] .subfooter{padding:0 10px}[role="contentinfo"] .subfooter:before,[role="contentinfo"] .subfooter:after{content:"";display:table}[role="contentinfo"] .subfooter:after{clear:both}@media screen and (min-width: 768px){[role="contentinfo"] .subfooter{padding:0}}[role="contentinfo"] .subfooter .col:first-child h2{border-top:0}@media screen and (min-width: 768px){[role="contentinfo"] .subfooter .col{float:left;margin-bottom:-999px;padding:0 3% 999px 0;width:30%}}[role="contentinfo"] .subfooter .col:first-child{margin-left:0;padding-left:0}[role="contentinfo"] .subfooter .col.last-child{margin-right:0;padding-right:0}[role="contentinfo"] h2{font-size:16px;font-size:1.6rem;border-top:1px solid #CFE3DC;color:#fff;font-weight:700;margin-top:20px;padding:30px 0 10px}@media screen and (min-width: 768px){[role="contentinfo"] h2{border:none;margin-top:0}}[role="contentinfo"] ul{font-size:14px;font-size:1.4rem;font-weight:400;list-style:none;margin:15px 0 0 0;padding:0 0 30px}[role="contentinfo"] ul li{margin:10px 0 0;padding:0}[role="contentinfo"] ul a{color:#F1FFF7;text-decoration:none}[role="contentinfo"] ul a:hover,[role="contentinfo"] ul a:active,[role="contentinfo"] ul a:focus{text-decoration:underline}[role="contentinfo"] .footer{background:#0C4B33;margin-top:20px;padding:10px 0 30px;color:#2B8C67}[role="contentinfo"] .footer .footer-logo{float:left;width:33%}[role="contentinfo"] .footer .logo{margin-right:0;margin-top:28px}.mdzr-svg [role="contentinfo"] .footer .logo{background-position:left center;margin-right:0;margin-top:20px}[role="contentinfo"] .logo{font-size:40px;font-size:4rem;font-family:"Roboto", Corbel, Avenir, "Lucida Grande", "Lucida Sans", sans-serif;background:url(../img/logo-django.png) 0 0 no-repeat;color:#0C4B33;display:block;font-weight:700;height:50px;margin:10px;overflow:hidden;text-decoration:none;text-indent:100%;width:142px}@media screen and (min-width: 768px){[role="contentinfo"] .logo{float:left;margin:20px 90px 0 0}}.mdzr-svg [role="contentinfo"] .logo{background:url(../img/logo-django.svg) center center no-repeat;height:39px;width:109px}[role="contentinfo"] .thanks{font-size:12px;font-size:1.2rem;color:#2B8C67;margin:0;padding:0}@media screen and (min-width: 768px){[role="contentinfo"] .thanks{border:none}}[role="contentinfo"] .thanks li{margin:0;padding:17px 10px 11px;display:block;clear:both}[role="contentinfo"] .thanks li:before,[role="contentinfo"] .thanks li:after{content:"";display:table}[role="contentinfo"] .thanks li:after{clear:both}@media screen and (min-width: 768px){[role="contentinfo"] .thanks li{background:none;float:left;clear:none;padding:0 3% 0 0;width:30%}[role="contentinfo"] .thanks li.design span.ampersand,[role="contentinfo"] .thanks li.design a{display:inline-block;vertical-align:top}[role="contentinfo"] .thanks li.design span.ampersand.threespot,[role="contentinfo"] .thanks li.design a.threespot{clear:both}[role="contentinfo"] .thanks li.design span.ampersand.ampersand,[role="contentinfo"] .thanks li.design a.ampersand{position:relative;top:6px;margin:0 6px;line-height:36px}}[role="contentinfo"] .thanks li span.ampersand{line-height:24px}[role="contentinfo"] .thanks span{display:block;height:24px;line-height:36px;padding-right:12px;white-space:nowrap}[role="contentinfo"] .thanks a{display:block;height:33px;overflow:hidden;text-indent:-200px;width:94px}@media screen and (min-width: 768px){[role="contentinfo"] .thanks a{clear:both;margin-top:5px}}[role="contentinfo"] .thanks a.rackspace{background:url(../img/logo-rackspace.png) no-repeat left center}.mdzr-svg [role="contentinfo"] .thanks a.rackspace{background:url(../img/logo-rackspace.svg) no-repeat left center}[role="contentinfo"] .thanks a.threespot{background:url(../img/logo-threespot.png) no-repeat left center}.mdzr-svg [role="contentinfo"] .thanks a.threespot{background:url(../img/logo-threespot.svg) no-repeat left center}[role="contentinfo"] .thanks a.andrevv{background:url(../img/logo-andrevv.png) no-repeat left center}.mdzr-svg [role="contentinfo"] .thanks a.andrevv{background:url(../img/logo-andrevv.svg) no-repeat left center}[role="contentinfo"] .copyright{font-size:12px;font-size:1.2rem;clear:both;margin:20px 0 0 10px}@media screen and (min-width: 768px){[role="contentinfo"] .copyright{max-width:80%;padding-top:30px;margin:0}}[role="contentinfo"] .copyright a{color:#2B8C67}.backtotop{color:#20AA76;text-decoration:none;font-family:"Roboto", Corbel, Avenir, "Lucida Grande", "Lucida Sans", sans-serif;font-size:14px;font-size:1.4rem;display:block;font-weight:700;margin:10px 0;padding:10px 0;text-align:center;text-transform:uppercase}.backtotop:visited{color:#20AA76}.backtotop:hover,.backtotop:active,.backtotop:focus{color:#25c488;text-decoration:none}@media screen and (min-width: 768px){.backtotop{display:none}}.cta,a.cta{font-family:"Roboto", Corbel, Avenir, "Lucida Grande", "Lucida Sans", sans-serif;font-weight:700;-webkit-appearance:none;-moz-appearance:none;background:#44B78B;border:none;-moz-border-radius:5px;-webkit-border-radius:5px;border-radius:5px;color:#fff;display:block;-webkit-font-smoothing:subpixel-antialiased;-moz-osx-font-smoothing:auto;margin:30px auto 0;padding:1em 1.5em;text-align:center;text-decoration:none}@media screen and (min-width: 768px){.cta,a.cta{margin:20px auto;max-width:400px}}.cta em,a.cta em{color:#C9F0DD;font-style:normal}.cta:hover,.cta:focus,a.cta:hover,a.cta:focus{background:#51be95}.cta:active,a.cta:active{background:#41b085}[role="complementary"] .cta,[role="complementary"] a.cta{font-size:16px;font-size:1.6rem}[role="complementary"] .cta+.link-readmore,[role="complementary"] a.cta+.link-readmore{display:block;text-align:center}@media screen and (min-width: 768px){[role="complementary"] .cta+.link-readmore,[role="complementary"] a.cta+.link-readmore{margin-top:-10px}}.cta.outline,a.cta.outline{background:none;border:1px solid #CFE3DC;color:#859D94;font-weight:400}.cta.outline em,a.cta.outline em{color:#0C4B33}.cta.outline:hover,a.cta.outline:hover{border-color:#20AA76;color:#20AA76}.cta.outline:hover em,a.cta.outline:hover em{color:#20AA76}.cta.outline:active,a.cta.outline:active{border-color:#44B78B;color:#44B78B}.cta.outline:active em,a.cta.outline:active em{color:#44B78B}.link-green{color:#20AA76;text-decoration:none}.link-green:visited{color:#20AA76}.link-green:hover,.link-green:active,.link-green:focus{color:#25c488;text-decoration:none}.link-readmore{color:#20AA76;text-decoration:none;font-family:"Roboto", Corbel, Avenir, "Lucida Grande", "Lucida Sans", sans-serif;font-size:14px;font-size:1.4rem;display:inline-block;margin:10px 0;font-weight:700;text-transform:uppercase}.link-readmore:visited{color:#20AA76}.link-readmore:hover,.link-readmore:active,.link-readmore:focus{color:#25c488;text-decoration:none}.link-readmore:after{content:" ›";font-size:1.2em}.link-readmore.back-link:after{content:""}.link-readmore.back-link:before{content:"‹ ";font-size:1.2em}[role="complementary"] .link-readmore{font-size:12px;font-size:1.2rem}.meta,.list-links dd{font-family:"Roboto", Corbel, Avenir, "Lucida Grande", "Lucida Sans", sans-serif;font-size:14px;font-size:1.4rem;display:block;line-height:1.3;margin:25px 0 20px}.meta a,.list-links dd a{color:#20AA76;text-decoration:none}.meta a:visited,.list-links dd a:visited{color:#20AA76}.meta a:hover,.list-links dd a:hover,.meta a:active,.list-links dd a:active,.meta a:focus,.list-links dd a:focus{color:#25c488;text-decoration:none}.layout-2col{margin:20px 0}@media screen and (min-width: 768px){.layout-2col{margin:0}.layout-2col:before,.layout-2col:after{content:"";display:table}.layout-2col:after{clear:both}.layout-2col .col{float:left;width:46%;margin:0 4%}.layout-2col .col:first-child{margin-left:0}.layout-2col .col.last-child{margin-right:0}.layout-2col .one-third{width:29%}.layout-2col .two-third{width:62%}}.blue{color:#20AA76}.label{font-family:"Roboto", Corbel, Avenir, "Lucida Grande", "Lucida Sans", sans-serif;font-size:16px;font-size:1.6rem;color:#cacfcc;display:block;font-weight:700;margin:20px 0 10px;text-transform:uppercase}.label.form-controls{font-size:14px;font-size:1.4rem;display:block;margin:0;position:relative;text-align:left}.label.form-controls span{cursor:pointer}.label.form-controls span:hover,.label.form-controls span:active,.label.form-controls span:focus{color:#afb7b3}@media screen and (min-width: 768px){.callout-right{float:right;margin:26px 0 0 35px;width:33%}.callout-right.two-thirds{width:60%}}.callout-right img{display:block;max-width:100%}@media screen and (min-width: 768px){.callout-left{float:left;margin:26px 35px 0 0;width:33%}.callout-left.two-thirds{width:60%}}.callout-left img{display:block;max-width:100%}.codedump{background:#f8f8f8;border:1px solid #CFE3DC;padding:10px;-moz-border-radius:4px;-webkit-border-radius:4px;border-radius:4px;font-family:"Roboto", Corbel, Avenir, "Lucida Grande", "Lucida Sans", sans-serif;font-size:14px;font-size:1.4rem;line-height:1.6em}.list-events{font-family:"Roboto", Corbel, Avenir, "Lucida Grande", "Lucida Sans", sans-serif;list-style:none;margin:0;padding:0}.list-events li{font-size:18px;font-size:1.8rem;border-top:1px solid #CFE3DC;display:block;line-height:1.3;margin:0;padding:20px 0 0 30px;position:relative}.list-events li i{font-size:16px;font-size:1.6rem;color:#93D7B7;display:block;left:0;line-height:20px;height:30px;position:absolute;text-align:center;top:20px;width:24px}.list-events li:first-child{border-top:0;padding-top:0}.list-events li:first-child i{top:0px}.list-events .meta,.list-events .list-links dd,.list-links .list-events dd{font-family:"Roboto", Corbel, Avenir, "Lucida Grande", "Lucida Sans", sans-serif;font-size:14px;font-size:1.4rem;display:block;margin-top:10px}.list-events a{color:#20AA76;text-decoration:none}.list-events a:visited{color:#20AA76}.list-events a:hover,.list-events a:active,.list-events a:focus{color:#25c488;text-decoration:none}.list-tags{font-family:"Roboto", Corbel, Avenir, "Lucida Grande", "Lucida Sans", sans-serif;font-size:12px;font-size:1.2rem;font-weight:700;list-style:none;margin:0;padding:0;text-transform:uppercase}.list-tags li{margin-top:10px}.list-tags a{background:#93D7B7;color:#F1FFF7;display:inline-block;line-height:1.2;margin:0;padding:8px 10px 5px;text-decoration:none}.list-tags a:hover,.list-tags a:active,.list-tags a:focus{background-color:#44B78B;color:#fff}.list-news{list-style:none;margin:0;padding:0}.list-news h2{font-weight:400;margin-bottom:5px}.list-news li{border-top:1px solid #CFE3DC;margin-top:35px;padding-top:10px}.list-news li:first-child{border:none;margin-top:0;padding-top:0}.list-news .meta,.list-news .list-links dd,.list-links .list-news dd{margin-top:10px;color:#859D94}.list-news .meta a:link,.list-news .list-links dd a:link,.list-links .list-news dd a:link{color:#798780;text-decoration:underline}.list-case-study{list-style:none;margin:0;padding:0 0 10px}.list-case-study:before,.list-case-study:after{content:"";display:table}.list-case-study:after{clear:both}.list-case-study p{font-size:14px;font-size:1.4rem;margin:10px 0 5px}.list-case-study li{border-top:1px solid #CFE3DC;margin-top:20px;padding-top:20px}@media screen and (min-width: 1024px){.list-case-study li{border:none;float:left;padding-right:5%;padding-top:0;width:28%}.list-case-study li:nth-child(3):after{clear:both;content:"";display:block;margin-bottom:30px}}.list-case-study li>a{font-family:"Roboto", Corbel, Avenir, "Lucida Grande", "Lucida Sans", sans-serif;color:#20AA76;text-decoration:none;font-size:12px;font-size:1.2rem;font-weight:700;margin-top:10px;text-transform:uppercase}.list-case-study li>a:visited{color:#20AA76}.list-case-study li>a:hover,.list-case-study li>a:active,.list-case-study li>a:focus{color:#25c488;text-decoration:none}.list-case-study li>a:after{content:" ›";font-size:1.2em}.list-case-study h3{margin:10px 0 20px;padding:0}.list-case-study h3.logo{text-indent:-1000%;overflow:hidden}.list-case-study [title="Knight Foundation"]{background:url(../img/logo-knight.png) no-repeat bottom left;height:25px;padding-top:7px;width:190px}.mdzr-svg .list-case-study [title="Knight Foundation"]{background:url(../img/logo-knight.svg) no-repeat bottom left}.list-case-study [title="Mozilla"]{background:url(../img/logo-mozilla.png) no-repeat bottom left;height:32px;width:120px}.mdzr-svg .list-case-study [title="Mozilla"]{background:url(../img/logo-mozilla.svg) no-repeat bottom left}.list-case-study [title="Disqus"]{background:url(../img/logo-disqus.png) no-repeat bottom left;height:28px;padding-top:4px;width:140px}.mdzr-svg .list-case-study [title="Disqus"]{background:url(../img/logo-disqus.svg) no-repeat bottom left}.list-case-study.single-col li{margin-top:0;margin-bottom:30px;width:auto}.list-case-study.single-col li p{font-size:18px;font-size:1.8rem;margin-right:40px}.list-case-study.single-col li h3{margin-top:20px}.case-study-logo{max-width:50%;max-height:70px;height:auto;margin:40px 0 0}.list-link-soup{font-family:"Roboto", Corbel, Avenir, "Lucida Grande", "Lucida Sans", sans-serif;font-size:16px;font-size:1.6rem;border-top:1px solid #CFE3DC;list-style:none;margin:20px 0 0;padding:20px 0 10px}.list-link-soup:before,.list-link-soup:after{content:"";display:table}.list-link-soup:after{clear:both}.list-link-soup li{float:left;margin:10px 5% 0 0;width:45%}@media screen and (min-width: 768px){.list-link-soup li{margin-right:3%;width:30%}}.list-link-soup a{color:#20AA76;text-decoration:none}.list-link-soup a:visited{color:#20AA76}.list-link-soup a:hover,.list-link-soup a:active,.list-link-soup a:focus{color:#25c488;text-decoration:none}[role="complementary"] .list-link-soup li{float:none}h2+.list-link-soup{border-top:0}.list-features{margin:50px 0 40px}.list-features dt{font-size:24px;font-size:2.4rem;border-top:1px solid #CFE3DC;padding-top:25px}.list-features i{color:#F1FFF7;margin-right:10px;width:40px;height:40px;-moz-border-radius:25px;-webkit-border-radius:25px;border-radius:25px;background:#20AA76;line-height:1.68em;display:inline-block;text-align:center}.list-features i.icon-briefcase{line-height:1.7em}.list-features i.icon-dashboard{line-height:1.5em}@media screen and (min-width: 768px){.list-features{padding-bottom:40px}.list-features dt{margin-top:60px;padding:60px 0 0 245px;position:relative}.list-features dt:first-child{margin-top:20px}.list-features dt.even{padding-left:0;padding-right:245px}.list-features dt.even i{left:auto !important;right:0}.list-features dd{padding-left:245px;min-height:140px}.list-features dd.even{padding-left:0;padding-right:245px}.list-features i{font-size:120px;font-size:12rem;display:block;height:200px;left:0;position:absolute;text-align:center;top:60px;width:200px;margin-right:0}.mdzr-borderradius .list-features i{background:#44B78B;-moz-border-radius:100px;-webkit-border-radius:100px;border-radius:100px;color:#fff}.mdzr-svg .list-features i{background:url(../img/bg-features.svg) no-repeat center center}.mdzr-svg .list-features i.icon-bolt{background-position:-150px -269px}.mdzr-svg .list-features i.icon-briefcase{background-position:-354px -7px}.mdzr-svg .list-features i.icon-lock{background-position:-36px -96px}.mdzr-svg .list-features i.icon-dashboard{background-position:-270px -9px}.mdzr-svg .list-features i.icon-cogs{background-position:-334px -12px}.mdzr-svg.mdzr-borderradius.mdzr-cssanimations .list-features i{-moz-transition:all 0.3s ease-out;-o-transition:all 0.3s ease-out;-webkit-transition:all 0.3s ease-out;transition:all 0.3s ease-out;-moz-transform:rotate(0.5turn);-ms-transform:rotate(0.5turn);-webkit-transform:rotate(0.5turn);transform:rotate(0.5turn)}.mdzr-svg.mdzr-borderradius.mdzr-cssanimations .list-features i.inview{-moz-transform:rotate(0turn);-ms-transform:rotate(0turn);-webkit-transform:rotate(0turn);transform:rotate(0turn)}.mdzr-svg.mdzr-borderradius.mdzr-cssanimations .list-features i.icon-bolt{background-position:40px -369px}.mdzr-svg.mdzr-borderradius.mdzr-cssanimations .list-features i.icon-bolt.inview{background-position:-150px -269px}.mdzr-svg.mdzr-borderradius.mdzr-cssanimations .list-features i.icon-briefcase{background-position:-494px 207px}.mdzr-svg.mdzr-borderradius.mdzr-cssanimations .list-features i.icon-briefcase.inview{background-position:-354px -7px}.mdzr-svg.mdzr-borderradius.mdzr-cssanimations .list-features i.icon-lock{background-position:144px -206px}.mdzr-svg.mdzr-borderradius.mdzr-cssanimations .list-features i.icon-lock.inview{background-position:-36px -96px}.mdzr-svg.mdzr-borderradius.mdzr-cssanimations .list-features i.icon-dashboard{background-position:-360px 201px}.mdzr-svg.mdzr-borderradius.mdzr-cssanimations .list-features i.icon-dashboard.inview{background-position:-270px -9px}.mdzr-svg.mdzr-borderradius.mdzr-cssanimations .list-features i.icon-cogs{background-position:-500px -180px}.mdzr-svg.mdzr-borderradius.mdzr-cssanimations .list-features i.icon-cogs.inview{background-position:-334px -12px}.list-features i :-o-prefocus,.list-features i{background:#44B78B !important;-moz-transition:none !important;-o-transition:none !important;-webkit-transition:none !important;transition:none !important;-moz-transform:none !important;-ms-transform:none !important;-webkit-transform:none !important;transform:none !important}}.homepage .list-features{padding-bottom:0}.homepage .list-features dl{padding-top:0}.homepage .list-features i{color:#F1FFF7;margin-right:10px;width:40px;height:40px;top:10px;-moz-border-radius:20px;-webkit-border-radius:20px;border-radius:20px;background:#20AA76;display:inline-block;text-align:center;font-size:24px;font-size:2.4rem}.mdzr-svg.mdzr-borderradius.mdzr-cssanimations .homepage .list-features i{-moz-transition:none;-o-transition:none;-webkit-transition:none;transition:none;-moz-transform:rotate(0);-ms-transform:rotate(0);-webkit-transform:rotate(0);transform:rotate(0)}@media screen and (min-width: 768px){.homepage .list-features dt{padding:20px 0 0px 110px;font-size:18px;font-size:1.8rem;border-top:0;margin-top:0}.homepage .list-features dd{padding:0 60px 20px 110px;min-height:0}.homepage .list-features dd p{margin-top:0;font-size:18px;font-size:1.8rem}.homepage .list-features i{margin-right:10px;width:80px;height:80px;top:20px;-moz-border-radius:40px;-webkit-border-radius:40px;border-radius:40px;font-size:46px;font-size:4.6rem}}@media screen and (min-width: 768px){.list-collapsing-header{float:left}.section .list-collapsing-header h2{margin:40px 0 20px}.list-collapsing-header+.form-controls.label{margin:50px 0 0;text-align:right}.form-controls.label{float:right}}.list-collapsing{border-bottom:1px solid #CFE3DC;list-style:none;margin:30px 0;padding:0;clear:both}.list-collapsing.active>li{border-top:1px solid #CFE3DC;margin:0;padding:0}.list-collapsing.active>li.active h2 .collapsing-icon:before{content:"\f068"}.list-collapsing.active h2{font-size:18px;font-size:1.8rem;cursor:pointer;margin:0;padding:18px 40px 18px 0;position:relative}.list-collapsing.active h2:hover,.list-collapsing.active h2:focus,.list-collapsing.active h2:active{color:#1d915c;outline:none}.list-collapsing.active h2 .collapsing-icon{position:absolute;right:0;top:24px}.list-collapsing.active h2 .collapsing-icon:before{content:"\f067"}.list-collapsing.active h2.bullet-icon{padding-left:1.5em}.list-collapsing.active h2.bullet-icon>i:first-child{position:absolute;top:24px;left:0}.list-collapsing.active .collapsing-content{overflow:hidden;max-height:0px;-moz-transition:all 0.5s ease-out;-o-transition:all 0.5s ease-out;-webkit-transition:all 0.5s ease-out;transition:all 0.5s ease-out}.list-collapsing.active li.active .collapsing-content{max-height:1000px;overflow:auto}.list-image{list-style:none;margin:0;padding:0}.list-image li{border-top:1px solid #CFE3DC;margin-top:20px;padding-top:40px}@media screen and (min-width: 768px){.list-image li{margin-top:20px;padding-top:40px}.list-image li:before,.list-image li:after{content:"";display:table}.list-image li:after{clear:both}}.list-image li:first-child{border:none;padding-top:0}.list-image a{text-decoration:none}.list-image a:hover,.list-image a:active,.list-image a:focus{color:#798780}.list-image a.link-readmore{margin:0}.list-image img{display:block;margin:0 auto 25px;max-width:100%}@media screen and (min-width: 768px){.list-image img{float:left;margin:0 40px 0 0;max-width:200px;max-height:200px}.list-image h2,.list-image h3,.list-image h4,.list-image p{padding-left:240px}.list-image h3{margin-top:10px}}.layout-secondary .list-image img,[role="complementary"] .list-image img{float:left;max-width:40%;max-height:120px;margin:0 20px 10px 0}.layout-secondary .list-image h2,.layout-secondary .list-image h3,.layout-secondary .list-image h4,.layout-secondary .list-image p,[role="complementary"] .list-image h2,[role="complementary"] .list-image h3,[role="complementary"] .list-image h4,[role="complementary"] .list-image p{padding-left:0px;border:0}.layout-secondary .list-image h3,[role="complementary"] .list-image h3{font-size:14px;font-size:1.4rem;margin-top:0;margin-bottom:1em;padding-bottom:0;font-family:"Roboto", Corbel, Avenir, "Lucida Grande", "Lucida Sans", sans-serif;font-weight:700}#s-django-documentation,#s-feed{font-family:"Roboto", Corbel, Avenir, "Lucida Grande", "Lucida Sans", sans-serif}.list-outline{font-size:16px;font-size:1.6rem;line-height:1.3;list-style:none;margin:0;padding:0}.list-outline a{text-decoration:none}.list-outline>li>ul>li{margin-top:12px}.list-outline>li>ul>li:first-child{margin-top:6px}.list-outline>li>a{text-transform:uppercase;font-weight:700;color:#20AA76;text-decoration:none}.list-outline>li>a:visited{color:#20AA76}.list-outline>li>a:hover,.list-outline>li>a:active,.list-outline>li>a:focus{color:#25c488;text-decoration:none}.list-outline>li>ul{list-style:none;font-size:12px;font-size:1.2rem;padding:5px 0 0 10px}.list-outline>li>ul>li>a{font-weight:700;text-transform:uppercase;color:#20AA76;text-decoration:none}.list-outline>li>ul>li>a:visited{color:#20AA76}.list-outline>li>ul>li>a:hover,.list-outline>li>ul>li>a:active,.list-outline>li>ul>li>a:focus{color:#25c488;text-decoration:none}.list-outline>li>ul>li ul{font-size:14px;font-size:1.4rem;padding:0 0 0 20px}.section h2{margin:50px 0 30px}.section h3{margin:40px 0 20px}.headerlink{opacity:0;padding-left:10px;font-size:0.8em;position:relative;top:-0.17em;font-weight:700;text-decoration:none;-moz-transition:opacity 200ms ease-in-out;-o-transition:opacity 200ms ease-in-out;-webkit-transition:opacity 200ms ease-in-out;transition:opacity 200ms ease-in-out}h1:hover>.headerlink,h2:hover>.headerlink,h3:hover>.headerlink,h4:hover>.headerlink,h5:hover>.headerlink,h6:hover>.headerlink,dl:hover>.headerlink,dt:hover>.headerlink{opacity:1}.note,.admonition,.help-block{background:#F1FFF7;padding:15px 20px 15px 70px;border:1px solid #C9F0DD;-moz-border-radius:4px;-webkit-border-radius:4px;border-radius:4px;margin:25px 0;position:relative}.note h1,.note h2,.note h3,.note h4,.admonition h1,.admonition h2,.admonition h3,.admonition h4,.help-block h1,.help-block h2,.help-block h3,.help-block h4{margin-top:20px}.note p,.admonition p,.help-block p{margin:0.8em 0}.note .first,.admonition .first,.help-block .first{margin-top:0}.note .admonition-title,.admonition .admonition-title,.help-block .admonition-title{font-weight:bold}.note .admonition-title::before,.admonition .admonition-title::before,.help-block .admonition-title::before{display:inline-block;font:normal normal normal 14px/1 FontAwesome;font-size:inherit;text-rendering:auto;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;position:absolute;top:20px;left:20px;font-size:30px;width:34px;text-align:center;content:"";opacity:0.5}.note.warning,.admonition.warning,.help-block.warning{background-color:#FFFDF1;border-color:#F5F1C7}.note.warning .admonition-title::before,.admonition.warning .admonition-title::before,.help-block.warning .admonition-title::before{color:#E9BD46;content:""}.note.admonition-philosophy .admonition-title::before,.admonition.admonition-philosophy .admonition-title::before,.help-block.admonition-philosophy .admonition-title::before{content:""}.note.admonition-behind-the-scenes .admonition-title::before,.admonition.admonition-behind-the-scenes .admonition-title::before,.help-block.admonition-behind-the-scenes .admonition-title::before{content:""}.note .last,.note .highlight,.admonition .last,.admonition .highlight,.help-block .last,.help-block .highlight{margin-bottom:0px}.browse-horizontal{font-family:"Roboto", Corbel, Avenir, "Lucida Grande", "Lucida Sans", sans-serif;font-size:14px;font-size:1.4rem;font-weight:700;border-top:1px solid #CFE3DC;border-bottom:1px solid #CFE3DC;padding:20px 0;margin-top:2em}.browse-horizontal:before,.browse-horizontal:after{content:"";display:table}.browse-horizontal:after{clear:both}.browse-horizontal .left{float:left}.browse-horizontal .left .icon{margin-right:4px;font-size:12px;font-size:1.2rem}.browse-horizontal .right{float:right}.browse-horizontal .right .icon{margin-left:4px;font-size:12px;font-size:1.2rem}.browse-horizontal a{text-decoration:none}#doc-versions{position:fixed;right:15px;bottom:15px;margin:0;padding:0;z-index:1;list-style:none}#doc-versions .icon{margin-right:4px}#doc-versions li{display:none;background:#F1FFF7;margin:0 3px;font-family:"Roboto", Corbel, Avenir, "Lucida Grande", "Lucida Sans", sans-serif;color:#0C3C26;font-size:12px;font-size:1.2rem}#doc-versions li.current{display:inline-block;padding:8px 15px;border:1px solid #CFE3DC;-moz-border-radius:4px;-webkit-border-radius:4px;border-radius:4px}#doc-versions li a{display:inline-block;color:#44B78B;text-decoration:none;font-weight:700;padding:8px 15px;border:1px solid #CFE3DC;-moz-border-radius:4px;-webkit-border-radius:4px;border-radius:4px}#doc-versions li a:hover{color:#20AA76;border:1px solid #93D7B7}#doc-versions:hover li,#doc-versions .hover-on li{display:inline-block}#dev-warning,#outdated-warning{position:absolute;top:0;width:100%;padding:8px 20px 8px;-moz-box-sizing:border-box;-webkit-box-sizing:border-box;box-sizing:border-box;background-image:-webkit-linear-gradient(-45deg, rgba(0,0,0,0.04) 25%, transparent 25%, transparent 50%, rgba(0,0,0,0.04) 50%, rgba(0,0,0,0.04) 75%, transparent 75%, transparent);background-image:-moz-linear-gradient(-45deg, rgba(0,0,0,0.04) 25%, transparent 25%, transparent 50%, rgba(0,0,0,0.04) 50%, rgba(0,0,0,0.04) 75%, transparent 75%, transparent);background-image:linear-gradient(135deg, rgba(0,0,0,0.04) 25%,rgba(0,0,0,0) 25%,rgba(0,0,0,0) 50%,rgba(0,0,0,0.04) 50%,rgba(0,0,0,0.04) 75%,rgba(0,0,0,0) 75%,rgba(0,0,0,0));font-family:"Roboto", Corbel, Avenir, "Lucida Grande", "Lucida Sans", sans-serif;font-size:14px;text-align:center;background-color:#ffe761}@media screen and (min-width: 768px){#dev-warning,#outdated-warning{position:fixed;min-width:768px}}#outdated-warning{background-color:#ffbaba;color:#6A0E0E}#s-getting-help{display:none}#docs-content{font-family:"Roboto", Corbel, Avenir, "Lucida Grande", "Lucida Sans", sans-serif;font-size:14px;font-size:1.4rem;line-height:1.5em}#docs-content h1,#docs-content h2,#docs-content h3,#docs-content h4,#docs-content h5,#docs-content h6{margin:0.6em 0;line-height:1.1em}#docs-content .section{padding:10px 0 20px}#docs-content img{display:block;max-width:100%}#docs-content a.reference{color:#6A0E0E;text-decoration:none;border-bottom:1px dotted #798780}#docs-content a.reference:visited{border-color:#971414}#docs-content a.reference:active,#docs-content a.reference:focus,#docs-content a.reference:hover{background:#F1FFF7;color:#BA2121}#docs-content a.reference em{font-style:normal}.versionadded,.versionchanged,.deprecated,.versionmodified{font-family:"Roboto", Corbel, Avenir, "Lucida Grande", "Lucida Sans", sans-serif;font-weight:bold;margin-bottom:20px;padding:10px 13px;border:1px solid #C9F0DD;-moz-border-radius:4px;-webkit-border-radius:4px;border-radius:4px}.versionadded p,.versionchanged p,.deprecated p,.versionmodified p{font-weight:normal;margin-top:0.3em}.versionadded p:last-child,.versionchanged p:last-child,.deprecated p:last-child,.versionmodified p:last-child{margin-bottom:0}.deprecated .versionadded,.deprecated .versionchanged,.deprecated .deprecated,.deprecated .versionmodified{border:none;padding:0;margin-bottom:0;display:block}.deprecated p{font-weight:normal;margin-top:0}.deprecated p:last-child{margin-bottom:0}dl.function dt,dl.class dt,dl.method dt,dl.attribute dt{font-weight:700}dl.function dd,dl.class dd,dl.method dd,dl.attribute dd{padding-left:1.4em}table.docutils td,table.docutils th{border-bottom:1px solid #CFE3DC}#search-results span.highlighted{font-weight:700;color:#0C3C26}.list-links{font-family:"Roboto", Corbel, Avenir, "Lucida Grande", "Lucida Sans", sans-serif;list-style:none;margin:0;padding:10px 0 0}.list-links a{color:#20AA76;text-decoration:none}.list-links a:visited{color:#20AA76}.list-links a:hover,.list-links a:active,.list-links a:focus{color:#25c488;text-decoration:none}.list-links dt,.list-links li{font-size:16px;font-size:1.6rem;margin-top:15px;font-weight:400}.list-links dt:first-child,.list-links li:first-child{margin-top:0}.list-links dd{margin-top:0;margin-bottom:30px}[role="complementary"] .list-links{padding:0}[role="complementary"] .list-links dt,[role="complementary"] .list-links li{font-size:16px;font-size:1.6rem;border-top:1px solid #CFE3DC;margin-top:0;padding-top:20px}[role="complementary"] .list-links dt:first-child,[role="complementary"] .list-links li:first-child{border:none;padding-top:0}[role="complementary"] .list-links li{padding:14px 0 10px}[role="complementary"] .list-links dd{font-size:14px;font-size:1.4rem;margin-bottom:16px}.list-links+h2{margin-top:34px}.list-links-small{padding-left:0;list-style:none}.list-links-small a{color:#20AA76;text-decoration:none;text-decoration:none}.list-links-small a:visited{color:#20AA76}.list-links-small a:hover,.list-links-small a:active,.list-links-small a:focus{color:#25c488;text-decoration:none}.list-links-small li>a:before,.list-links-small dt>a:before{font-family:FontAwesome;font-weight:normal;font-style:normal;float:left;width:23px;height:20px}.list-links-small dt{font-weight:400}.list-links-small dd{color:#798780;padding-top:2px}.list-links-small.docs-list{list-style:none}.list-links-small.docs-list li>a:before,.list-links-small.docs-list dt>a:before{content:"\f0f6"}.list-links-small.docs-list dd{padding-left:24px}.list-links-small.news-list{list-style:none}.list-links-small.news-list li>a:before,.list-links-small.news-list dt>a:before{content:"\f0a1"}.list-links-small.news-list dt.event>a:before{content:"\f133"}.list-links-small.news-list dd{padding-left:24px}.list-links-small.resource-list{list-style:none}.list-links-small.resource-list li>a:before,.list-links-small.resource-list dt>a:before{content:"\f0c1"}.list-links-small.resource-list dd{padding-left:24px}.list-links-small.rss-list{list-style:none}.list-links-small.rss-list li>a:before,.list-links-small.rss-list dt>a:before{content:"\f09e"}.list-links-small.rss-list dd{padding-left:24px}form{font-family:"Roboto", Corbel, Avenir, "Lucida Grande", "Lucida Sans", sans-serif;font-size:16px;font-size:1.6rem}form input[type="search"],form input[type="text"],form input[type="email"],form input[type="password"],form input[type="url"],form textarea{font-family:"Roboto", Corbel, Avenir, "Lucida Grande", "Lucida Sans", sans-serif;font-size:16px;font-size:1.6rem;-webkit-appearance:none;-moz-appearance:none;background:#fff;border:1px solid #CFE3DC;-moz-border-radius:4px;-webkit-border-radius:4px;border-radius:4px;cursor:auto;display:block;font-weight:400;height:30px;margin:10px 0px;padding:6px 14% 8px 10px;text-indent:0;vertical-align:middle;width:82%}@media screen and (min-width: 768px){form input[type="search"],form input[type="text"],form input[type="email"],form input[type="password"],form input[type="url"],form textarea{padding:6px 18% 8px 10px;width:80%}}form input[type="search"]::-ms-clear,form input[type="text"]::-ms-clear,form input[type="email"]::-ms-clear,form input[type="password"]::-ms-clear,form input[type="url"]::-ms-clear,form textarea::-ms-clear{display:none}form input[type="search"]:active,form input[type="search"]:focus,form input[type="text"]:active,form input[type="text"]:focus,form input[type="email"]:active,form input[type="email"]:focus,form input[type="password"]:active,form input[type="password"]:focus,form input[type="url"]:active,form input[type="url"]:focus,form textarea:active,form textarea:focus{outline:none;border-color:#20AA76}form textarea{height:auto}form input[type=checkbox],form input[type=radio]{margin-right:6px}form select{border:1px solid #CFE3DC;background:white;height:46px;padding:0 10px;-moz-border-radius:4px;-webkit-border-radius:4px;border-radius:4px;font-size:16px;font-size:1.6rem}[role="complementary"] form select{height:36px;font-size:14px;font-size:1.4rem}form button{-moz-appearance:none;-webkit-appearance:none;background:#20AA76;-moz-border-radius:4px;-webkit-border-radius:4px;border-radius:4px;color:white;border:0;height:46px;padding:0 15px;font-family:"Roboto", Corbel, Avenir, "Lucida Grande", "Lucida Sans", sans-serif;font-size:16px;font-size:1.6rem}form button:hover{background:#44B78B}.form-general fieldset{max-width:700px;border:0;padding:0;margin:15px 0}.form-general fieldset input[type="search"],.form-general fieldset input[type="text"],.form-general fieldset input[type="email"],.form-general fieldset input[type="password"],.form-general fieldset input[type="url"]{margin:10px 0}.form-input{min-height:40px;margin:30px 0 20px;position:relative}.form-input:focus{background:#000}.form-input button{background:none;border:none;color:#44B78B;height:40px;padding:0;position:absolute;right:2%;top:6%;width:40px}@media screen and (min-width: 768px){.form-input button{right:1%}}.form-input button i{font-size:20px;font-size:2rem;line-height:1}.form-input button:hover,.form-input button:focus,.form-input button:active{background:none;color:#0C4B33;outline:none}[role="complementary"] .form-input{min-height:30px;margin:20px 0 30px}[role="complementary"] .form-input input[type="search"],[role="complementary"] .form-input input[type="text"],[role="complementary"] .form-input input[type="email"]{height:20px;font-size:14px;font-size:1.4rem}[role="complementary"] .form-input button{height:30px;width:30px;top:3px}@media screen and (min-width: 768px){[role="complementary"] .form-input button{right:0}}[role="complementary"] .form-input button i{font-size:20px;font-size:2rem}form.donate{max-width:150px}form.donate label{position:absolute;left:0px;color:#2B8C67;padding-top:0.3em;padding-left:0.5em}form.donate input[type=text]{padding-left:20px;padding-right:9px}div[role=main] form.donate label{padding-top:0.7em}::-webkit-input-placeholder,:-moz-placeholder,::-moz-placeholder,:-ms-input-placeholder{color:#859D94}.form-email h3{font-size:18px;font-size:1.8rem;margin:10px 0}.form-email .meta,.form-email .list-links dd,.list-links .form-email dd{margin:0}.form-email form{margin:10px 0 30px}.nav-pagination{font-family:"Roboto", Corbel, Avenir, "Lucida Grande", "Lucida Sans", sans-serif;font-size:14px;font-size:1.4rem;border-top:1px solid #CFE3DC;font-weight:700;line-height:31px;list-style:none;margin:30px 0;padding:30px 0 0;text-align:center}.nav-pagination li{display:inline-block}.nav-pagination a{border:none;color:#798780;height:auto;width:auto;margin:0 5px;-moz-border-radius:15px;-webkit-border-radius:15px;border-radius:15px;display:block;text-decoration:none}@media screen and (min-device-width: 320px){.nav-pagination a{background:#798780;color:#fff;height:30px;margin:0 2px;width:30px}}.nav-pagination a.previous,.nav-pagination a.next{font-size:16px;font-size:1.6rem}@media screen and (min-device-width: 320px){.nav-pagination a.previous,.nav-pagination a.next{-moz-border-radius:20px;-webkit-border-radius:20px;border-radius:20px;height:40px;line-height:43px;width:40px}}.nav-pagination a.previous{margin-right:10px}@media screen and (min-width: 768px){.nav-pagination a.previous{margin-right:70px}}.nav-pagination a.next{margin-left:10px;text-indent:1px}@media screen and (min-width: 768px){.nav-pagination a.next{margin-left:70px}}.nav-pagination a:hover,.nav-pagination a:focus,.nav-pagination a:active,.nav-pagination a.active{background:none;color:#20AA76}@media screen and (min-device-width: 320px){.nav-pagination a:hover,.nav-pagination a:focus,.nav-pagination a:active,.nav-pagination a.active{background:#20AA76;color:white}}.mdzr-no-borderradius .nav-pagination a{display:inline;background:none;color:#798780;height:auto;width:auto;margin:0 5px !important}.mdzr-no-borderradius .nav-pagination a:hover,.mdzr-no-borderradius .nav-pagination a:active,.mdzr-no-borderradius .nav-pagination a:focus,.mdzr-no-borderradius .nav-pagination a.active{background:none;color:#20AA76}hr{border:0;border-top:1px solid #CFE3DC}.badge{-moz-border-radius:4px;-webkit-border-radius:4px;border-radius:4px;font-size:12px;padding:2px 6px;margin:0 5px;letter-spacing:0px;position:relative;bottom:0.3em;color:#F1FFF7;background-color:#20AA76}.user-info .avatar{padding:20px;border:1px solid #CFE3DC;-moz-border-radius:4px;-webkit-border-radius:4px;border-radius:4px;float:right}.visuallyhidden{border:0;clip:rect(0 0 0 0);height:1px;margin:-1px;overflow:hidden;padding:0;position:absolute;width:1px}.clearfix:before,.clearfix:after,.group-container:before,.group-container:after{content:"";display:table}.clearfix:after,.group-container:after{clear:both}.fundraising-index{margin-top:50px}.fundraising-index .fundraising-heart{width:100%;float:none}.fundraising-index .fundraising-heart img,.fundraising-index .fundraising-heart svg{width:100%;height:auto}.fundraising-index .fundraising-heart rect{-moz-transition:opacity 250ms ease-out;-o-transition:opacity 250ms ease-out;-webkit-transition:opacity 250ms ease-out;transition:opacity 250ms ease-out}.fundraising-index .fundraising-heart rect.faded{opacity:0.05}.fundraising-index .fundraising-heart rect.faded:hover{opacity:0.9}@media screen and (min-width: 768px){.fundraising-index .fundraising-heart{width:55%;float:left}}.fundraising-index .description{margin-left:5%;width:100%;float:none;margin-top:30px}@media screen and (min-width: 768px){.fundraising-index .description{width:40%;float:left;margin-top:0px}}.fundraising-index .description h2{margin-top:0;font-size:28px;font-size:2.8rem}.fundraising-index .donate select{width:80%}@media screen and (min-width: 768px){.fundraising-index .donate select{width:100%}}.fundraising-index .donate .cta{margin:10px 0}.fundraising-index .donate .custom-donation{display:none}.fundraising-index .donate .prefix{float:left;font-size:19px;font-size:1.9rem;margin:8px 9px 0 3px}.fundraising-index .donate input[type="text"]{width:70%}.fundraising-index .cls{clear:both}.fundraising-donation select{width:100%}.fundraising-donation input.error{border:1px solid #BA2121 !important}.fundraising-donation p.validation-errors{color:#BA2121}.fundraising-donation .custom-donation{display:none;margin-top:10px}.fundraising-sidebar{clear:both}.fundraising-sidebar .small-heart{margin-top:20px;width:20%;float:left;margin-bottom:20px}.fundraising-sidebar .small-heart img{width:100%;max-width:64px}.fundraising-sidebar .small-cta{width:70%;float:left;vertical-align:top;margin-left:5%;margin-right:5%;margin-bottom:20px}.footnote{color:#859D94;font-size:14px;font-size:1.4rem;margin-top:20px;text-align:center}form .footnote{margin-top:10px;text-align:left}.heros-section{overflow:hidden}.heros-section .heros{clear:both}.heros-section .heros .hero{width:25%;position:relative;height:auto}.heros-section .heros .hero div{width:100%}.heros-section .heros .hero-logo{height:170px;line-height:170px}.heros-section .heros .hero-logo img{vertical-align:middle}.heros-section .heros .hero-name{height:92px;vertical-align:top}.heros-section .heros .no-logo-hero{min-height:60px;margin-right:24px}.heros-section .heros div{float:left;text-align:center;margin:15px 0}.heros-section .heros div img{max-width:90%;max-height:170px}.heros-section .pagination{clear:both}pre.literal-block,.literal-block{font-size:14px;font-size:1.4rem;border:1px solid #EAEAEA;background:#F4F4F4;background:#f8f8f8;overflow:auto;border-radius:4px;margin:25px 0;padding:10px 20px;color:#0C4B33}.snippet-filename{background:#C9F0DD;color:#0C4B33;font-family:"Fira Mono", Consolas, Menlo, Monaco, "Courier New", Courier, monospace;font-variant-ligatures:no-common-ligatures;text-rendering:optimizeSpeed;font-size:1em;padding:5px 20px;-moz-border-radius:4px 4px 0 0;-webkit-border-radius:4px;border-radius:4px 4px 0 0}.snippet-filename+.highlight{margin-top:0;-moz-border-radius:0 0 4px 4px;-webkit-border-radius:0;border-radius:0 0 4px 4px;border-top:0}.highlight{font-size:14px;font-size:1.4rem;border:1px solid #EAEAEA;background:#F4F4F4;background:#f8f8f8;overflow:auto;border-radius:4px;margin:25px 0}.highlight pre{margin:15px 20px}.highlight li{margin-top:0;border-left:1px solid #EAEAEA;padding:0 0 2px 15px}.highlight li:first-child{padding-top:2px}.highlight .hll{background-color:#ffc}.highlight .c{color:#408080;font-style:italic}.highlight .err{border:1px solid red}.highlight .k{color:#008000;font-weight:bold}.highlight .o{color:#666}.highlight .cm{color:#408080;font-style:italic}.highlight .cp{color:#BC7A00}.highlight .c1{color:#408080;font-style:italic}.highlight .cs{color:#408080;font-style:italic}.highlight .gd{color:#A00000}.highlight .ge{font-style:italic}.highlight .gr{color:red}.highlight .gh{color:#000080;font-weight:bold}.highlight .gi{color:#00A000}.highlight .go{color:gray}.highlight .gp{color:#000080;font-weight:bold}.highlight .gs{font-weight:bold}.highlight .gu{color:#800080;font-weight:bold}.highlight .gt{color:#0040D0}.highlight .kc{color:#008000;font-weight:bold}.highlight .kd{color:#008000;font-weight:bold}.highlight .kn{color:#008000;font-weight:bold}.highlight .kp{color:green}.highlight .kr{color:#008000;font-weight:bold}.highlight .kt{color:#B00040}.highlight .m{color:#666}.highlight .s{color:#BA2121}.highlight .na{color:#7D9029}.highlight .nb{color:green}.highlight .nc{color:#0000FF;font-weight:bold}.highlight .no{color:#800}.highlight .nd{color:#a2f}.highlight .ni{color:#999999;font-weight:bold}.highlight .ne{color:#D2413A;font-weight:bold}.highlight .nf{color:blue}.highlight .nl{color:#A0A000}.highlight .nn{color:#0000FF;font-weight:bold}.highlight .nt{color:#008000;font-weight:bold}.highlight .nv{color:#19177C}.highlight .ow{color:#AA22FF;font-weight:bold}.highlight .w{color:#bbb}.highlight .mf{color:#666}.highlight .mh{color:#666}.highlight .mi{color:#666}.highlight .mo{color:#666}.highlight .sb{color:#BA2121}.highlight .sc{color:#BA2121}.highlight .sd{color:#BA2121;font-style:italic}.highlight .s2{color:#BA2121}.highlight .se{color:#BB6622;font-weight:bold}.highlight .sh{color:#BA2121}.highlight .si{color:#BB6688;font-weight:bold}.highlight .sx{color:green}.highlight .sr{color:#b68}.highlight .s1{color:#BA2121}.highlight .ss{color:#19177C}.highlight .bp{color:green}.highlight .vc{color:#19177C}.highlight .vg{color:#19177C}.highlight .vi{color:#19177C}.highlight .il{color:#666}.highlight .lineno{color:#000000;background-color:#dddddd}.styleguide .example{padding:0 20px 20px;border:1px solid #CFE3DC;-moz-border-radius:4px;-webkit-border-radius:4px;border-radius:4px;margin-top:20px;margin-bottom:64px}.styleguide .example:before{content:"Example";font-size:16px;font-weight:700;display:block;color:#CFE3DC;font-family:"Roboto", Corbel, Avenir, "Lucida Grande", "Lucida Sans", sans-serif;text-align:left;padding:10px 0}.styleguide .example [role="complementary"]{float:none;width:auto;padding:0;margin:0}.styleguide .iframe{display:block;height:400px;cursor:zoom-in;border:1px solid #CFE3DC;overflow:hidden}.styleguide .iframe iframe{pointer-events:none;position:relative;width:200%;border:0;height:800px;-moz-transform:scale(0.5) translate(-50%, -50%);-ms-transform:scale(0.5) translate(-50%, -50%);-webkit-transform:scale(0.5) translate(-50%, -50%);transform:scale(0.5) translate(-50%, -50%);top:0;left:0;overflow:hidden}.styleguide .swatches{margin:0;padding:0;list-style:none;margin:30px 0}.styleguide .swatches:before,.styleguide .swatches:after{content:"";display:table}.styleguide .swatches:after{clear:both}.styleguide .swatches li{width:30%;height:30px;margin-right:2%;-moz-box-sizing:border-box;-webkit-box-sizing:border-box;box-sizing:border-box;float:left}.styleguide .swatches li.text{background:#0C3C26}.styleguide .swatches li.green-dark{background:#0C4B33}.styleguide .swatches li.green{background:#20AA76}.styleguide .swatches li.green-light{background:#93D7B7}.styleguide .swatches li.white{background:#F1FFF7;border:1px solid #CFE3DC}.styleguide .swatches li.red-dark{background:#6A0E0E}.styleguide .swatches li.text-light{background:#798780}.styleguide .swatches li.green-medium-dark{background:#2B8C67}.styleguide .swatches li.green-medium{background:#44B78B}.styleguide .swatches li.green-very-light{background:#C9F0DD}.styleguide .swatches li.gray-line{background:#CFE3DC}.styleguide .swatches li.red{background:#BA2121}.styleguide #layout{overflow:hidden}.styleguide #icons .icon{font-size:32px;font-size:3.2rem;color:#20AA76;padding:0 .2em}@media print{*{background:transparent !important;color:#000 !important;box-shadow:none !important;text-shadow:none !important}a,a:visited{text-decoration:underline}pre,blockquote{border:1px solid #999;page-break-inside:avoid}thead{display:table-header-group}tr,img{page-break-inside:avoid}img{max-width:100% !important}@page{margin:0.5cm}p,h2,h3{orphans:3;widows:3}h2,h3{page-break-after:avoid}html{font-size:40%}.menu-button,.news-search,.backtotop,.nav-pagination,[role="contentinfo"]>.container,[role="contentinfo"] .logo,.thanks,[role="complementary"],[role="navigation"],.form-input{display:none !important}.logo{text-indent:0 !important}[role="contentinfo"],[role="contentinfo"] .copyright{margin:0 !important;padding:0 !important}.internal-container{float:none;width:auto}.list-news li{margin-top:0}}#dashboard .full-width [role="main"]{width:80%;max-width:1200px;margin:0 auto;text-align:center}.metric{height:8em;width:240px;padding:0 20px 10px 20px;margin:10px 10px 20px 10px;border:1px solid #CFE3DC;-moz-border-radius:4px;-webkit-border-radius:4px;border-radius:4px;display:inline-block;position:relative}.metric h3{padding:10px;margin:0 -20px;font-size:18px;font-size:1.8rem;font-weight:200;display:block;text-align:left;color:#C9F0DD;background:#44B78B}.metric h3 a{color:#C9F0DD}.metric .value{position:absolute;bottom:0;left:0;width:100%;height:80%;padding:0;margin:0;text-align:center;font-size:5em;line-height:1.5em;font-family:"Roboto", Corbel, Avenir, "Lucida Grande", "Lucida Sans", sans-serif;z-index:10;pointer-events:none}.metric .value a{text-decoration:none;z-index:10}.metric .value .timestamp{font-size:0.16em;line-height:1em}.metric .sparkline{position:absolute;bottom:0;left:0;width:100%;height:54px;z-index:1}.graph{width:100%;margin:0 auto;height:500px}
diff --git a/djangoproject/static/js/dashboard/detail.js b/djangoproject/static/js/dashboard/detail.js
new file mode 100644
index 000000000..fd57f69fe
--- /dev/null
+++ b/djangoproject/static/js/dashboard/detail.js
@@ -0,0 +1,78 @@
+define('dashboard/detail', ['jquery', 'jquery.flot', 'dashboard/utils'], function ($, plot, utils) {
+ $(function () {
+ var e = $("#graph");
+ var url = "/metric/" + e.data('metric') + ".json?days=365";
+ var hover = {
+ show: function (x, y, message) {
+ $('
').html(message)
+ .css({top: y, left: x})
+ .appendTo('body')
+ .show();
+ },
+ hide: function () {
+ $("#hover").remove();
+ }
+ };
+
+ $.getJSON(url, function (response) {
+ for (var i = 0; i < response.data.length; i++) {
+ response.data[i][0] = response.data[i][0] * 1000;
+ }
+ var options = {
+ xaxis: {
+ mode: "time",
+ tickColor: "rgba(0,0,0,0)",
+ minTickSize: [1, "day"]
+ },
+ yaxis: {min: 0, ticks: 4},
+ grid: {borderWidth: 0, hoverable: true, color: "#0C3C26"},
+ colors: ["#0C4B33"]
+ };
+ if (response.period == "daily") {
+ options.bars = {
+ show: true,
+ barWidth: 22 * 60 * 60 * 1000,
+ align: "center"
+ };
+ } else if (response.period == 'weekly') {
+ options.bars = {
+ show: true,
+ barWidth: 22 * 60 * 60 * 7 * 1000,
+ align: "center"
+ };
+ }
+ var plot = $.plot(e, [response.data], options);
+
+ var format_message = function (timestamp, measurement) {
+ var unit = measurement == 1 ? response.unit : response.unit_plural;
+ return utils.formatTimestamp(timestamp, response.period) + '
' + measurement + ' ' + unit;
+ };
+
+ var previousPoint = null;
+ e.bind("plothover", function (event, pos, item) {
+ if (item) {
+ if (previousPoint != item.dataIndex) {
+ previousPoint = item.dataIndex;
+ hover.hide();
+ var x, y;
+ var message = format_message.apply(null, item.datapoint);
+ if (response.period == 'instant') {
+ x = item.pageX + 10;
+ y = item.pageY + 10;
+ } else {
+ // I'd like this hover to be centered over the bar. This
+ // simple math sorta works, but it assumes a *lot* about
+ // the plot and basically won't scale. Grr.
+ x = item.pageX - 40;
+ y = item.pageY - 50;
+ }
+ hover.show(x, y, message);
+ }
+ } else {
+ hover.hide();
+ previousPoint = null;
+ }
+ });
+ });
+ });
+});
diff --git a/djangoproject/static/js/dashboard/index.js b/djangoproject/static/js/dashboard/index.js
new file mode 100644
index 000000000..43f6bb1b0
--- /dev/null
+++ b/djangoproject/static/js/dashboard/index.js
@@ -0,0 +1,48 @@
+define('dashboard/index', ['jquery', 'jquery.flot', 'dashboard/utils'], function ($, flot, utils) {
+ $(function () {
+ $(".metric .sparkline").each(function (index, elem) {
+ var element = $(elem);
+ var valueElement = element.parent().find('.value a');
+ var timestampElement = element.parent().find('.timestamp');
+ var originalValue = valueElement.html();
+ var green = '#93D7B7';
+
+ var url = "/metric/" + element.data('metric') + ".json";
+ $.getJSON(url, function (response) {
+ response.data = utils.convertSecondsToMilliseconds(response.data);
+ $.plot(e, [response.data], {
+ xaxis: {show: false, mode: "time"},
+ yaxis: {show: false, min: 0},
+ grid: {borderWidth: 0, hoverable: true},
+ colors: [green],
+ bars: {
+ show: true,
+ barWidth: (response.period == 'daily' ? 24 * 60 * 60 * 1000 : 24 * 60 * 60 * 7 * 1000),
+ fillColor: green,
+ lineWidth: 1,
+ align: "center"
+ }
+ });
+
+ element.bind('plothover', function (event, pos, item) {
+ if (item) {
+ valueElement.html(item.datapoint[1]);
+ timestampElement.html(
+ utils.formatTimestamp(
+ item.datapoint[0],
+ response.period
+ )
+ );
+ } else {
+ valueElement.html(originalValue);
+ timestampElement.html(' ');
+ }
+ });
+ });
+
+ element.on('click', function () {
+ window.location = "/metric/" + element.data('metric') + '/';
+ });
+ });
+ });
+});
diff --git a/djangoproject/static/js/dashboard/utils.js b/djangoproject/static/js/dashboard/utils.js
new file mode 100644
index 000000000..7ff6673ce
--- /dev/null
+++ b/djangoproject/static/js/dashboard/utils.js
@@ -0,0 +1,25 @@
+define('dashboard/utils', ['jquery'], function ($) {
+ return {
+ formatTimestamp: function formatTimestamp(timestamp, period) {
+ var d = new Date(timestamp);
+ if (period == 'instant') {
+ return $.plot.formatDate(d, "%b %d, %h:%M%p");
+ } else if (period == 'daily') {
+ return $.plot.formatDate(d, "%b %d");
+ } else if (period == 'weekly') {
+ // A bit more complicated than the above: the timestamp is in the
+ // middle of the week, so we have to bracket the date. This is
+ // something of a fudge here, but it works well enough.
+ var start = new Date(d.getTime() - (3 * 24 * 60 * 60 * 1000));
+ var end = new Date(d.getTime() + (3 * 24 * 60 * 60 * 1000));
+ return $.plot.formatDate(start, "%b %d") + ' - ' + $.plot.formatDate(end, "%b %d");
+ }
+ },
+ convertSecondsToMilliseconds: function convertSecondsToMilliseconds(data) {
+ for (var i = 0; i < data.length; i++) {
+ data[i][0] = data[i][0] * 1000;
+ }
+ return data;
+ }
+ };
+});
diff --git a/djangoproject/static/js/lib/jquery-flot/.bower.json b/djangoproject/static/js/lib/jquery-flot/.bower.json
new file mode 100644
index 000000000..2755dca37
--- /dev/null
+++ b/djangoproject/static/js/lib/jquery-flot/.bower.json
@@ -0,0 +1,18 @@
+{
+ "name": "Flot",
+ "version": "0.8.3",
+ "main": "jquery.flot.js",
+ "dependencies": {
+ "jquery": ">= 1.2.6"
+ },
+ "homepage": "https://github.com/flot/flot",
+ "_release": "0.8.3",
+ "_resolution": {
+ "type": "version",
+ "tag": "v0.8.3",
+ "commit": "453b017cc5acfd75e252b93e8635f57f4196d45d"
+ },
+ "_source": "git://github.com/flot/flot.git",
+ "_target": "~0.8.3",
+ "_originalSource": "jquery-flot"
+}
\ No newline at end of file
diff --git a/djangoproject/static/js/lib/jquery-flot/.gitignore b/djangoproject/static/js/lib/jquery-flot/.gitignore
new file mode 100644
index 000000000..477d588c1
--- /dev/null
+++ b/djangoproject/static/js/lib/jquery-flot/.gitignore
@@ -0,0 +1,3 @@
+*.min.js
+!excanvas.min.js
+node_modules/
diff --git a/djangoproject/static/js/lib/jquery-flot/.travis.yml b/djangoproject/static/js/lib/jquery-flot/.travis.yml
new file mode 100644
index 000000000..baa0031d5
--- /dev/null
+++ b/djangoproject/static/js/lib/jquery-flot/.travis.yml
@@ -0,0 +1,3 @@
+language: node_js
+node_js:
+ - 0.8
diff --git a/djangoproject/static/js/lib/jquery-flot/API.md b/djangoproject/static/js/lib/jquery-flot/API.md
new file mode 100644
index 000000000..e08b44cf1
--- /dev/null
+++ b/djangoproject/static/js/lib/jquery-flot/API.md
@@ -0,0 +1,1498 @@
+# Flot Reference #
+
+**Table of Contents**
+
+[Introduction](#introduction)
+| [Data Format](#data-format)
+| [Plot Options](#plot-options)
+| [Customizing the legend](#customizing-the-legend)
+| [Customizing the axes](#customizing-the-axes)
+| [Multiple axes](#multiple-axes)
+| [Time series data](#time-series-data)
+| [Customizing the data series](#customizing-the-data-series)
+| [Customizing the grid](#customizing-the-grid)
+| [Specifying gradients](#specifying-gradients)
+| [Plot Methods](#plot-methods)
+| [Hooks](#hooks)
+| [Plugins](#plugins)
+| [Version number](#version-number)
+
+---
+
+## Introduction ##
+
+Consider a call to the plot function:
+
+```js
+var plot = $.plot(placeholder, data, options)
+```
+
+The placeholder is a jQuery object or DOM element or jQuery expression
+that the plot will be put into. This placeholder needs to have its
+width and height set as explained in the [README](README.md) (go read that now if
+you haven't, it's short). The plot will modify some properties of the
+placeholder so it's recommended you simply pass in a div that you
+don't use for anything else. Make sure you check any fancy styling
+you apply to the div, e.g. background images have been reported to be a
+problem on IE 7.
+
+The plot function can also be used as a jQuery chainable property. This form
+naturally can't return the plot object directly, but you can still access it
+via the 'plot' data key, like this:
+
+```js
+var plot = $("#placeholder").plot(data, options).data("plot");
+```
+
+The format of the data is documented below, as is the available
+options. The plot object returned from the call has some methods you
+can call. These are documented separately below.
+
+Note that in general Flot gives no guarantees if you change any of the
+objects you pass in to the plot function or get out of it since
+they're not necessarily deep-copied.
+
+
+## Data Format ##
+
+The data is an array of data series:
+
+```js
+[ series1, series2, ... ]
+```
+
+A series can either be raw data or an object with properties. The raw
+data format is an array of points:
+
+```js
+[ [x1, y1], [x2, y2], ... ]
+```
+
+E.g.
+
+```js
+[ [1, 3], [2, 14.01], [3.5, 3.14] ]
+```
+
+Note that to simplify the internal logic in Flot both the x and y
+values must be numbers (even if specifying time series, see below for
+how to do this). This is a common problem because you might retrieve
+data from the database and serialize them directly to JSON without
+noticing the wrong type. If you're getting mysterious errors, double
+check that you're inputting numbers and not strings.
+
+If a null is specified as a point or if one of the coordinates is null
+or couldn't be converted to a number, the point is ignored when
+drawing. As a special case, a null value for lines is interpreted as a
+line segment end, i.e. the points before and after the null value are
+not connected.
+
+Lines and points take two coordinates. For filled lines and bars, you
+can specify a third coordinate which is the bottom of the filled
+area/bar (defaults to 0).
+
+The format of a single series object is as follows:
+
+```js
+{
+ color: color or number
+ data: rawdata
+ label: string
+ lines: specific lines options
+ bars: specific bars options
+ points: specific points options
+ xaxis: number
+ yaxis: number
+ clickable: boolean
+ hoverable: boolean
+ shadowSize: number
+ highlightColor: color or number
+}
+```
+
+You don't have to specify any of them except the data, the rest are
+options that will get default values. Typically you'd only specify
+label and data, like this:
+
+```js
+{
+ label: "y = 3",
+ data: [[0, 3], [10, 3]]
+}
+```
+
+The label is used for the legend, if you don't specify one, the series
+will not show up in the legend.
+
+If you don't specify color, the series will get a color from the
+auto-generated colors. The color is either a CSS color specification
+(like "rgb(255, 100, 123)") or an integer that specifies which of
+auto-generated colors to select, e.g. 0 will get color no. 0, etc.
+
+The latter is mostly useful if you let the user add and remove series,
+in which case you can hard-code the color index to prevent the colors
+from jumping around between the series.
+
+The "xaxis" and "yaxis" options specify which axis to use. The axes
+are numbered from 1 (default), so { yaxis: 2} means that the series
+should be plotted against the second y axis.
+
+"clickable" and "hoverable" can be set to false to disable
+interactivity for specific series if interactivity is turned on in
+the plot, see below.
+
+The rest of the options are all documented below as they are the same
+as the default options passed in via the options parameter in the plot
+commmand. When you specify them for a specific data series, they will
+override the default options for the plot for that data series.
+
+Here's a complete example of a simple data specification:
+
+```js
+[ { label: "Foo", data: [ [10, 1], [17, -14], [30, 5] ] },
+ { label: "Bar", data: [ [11, 13], [19, 11], [30, -7] ] }
+]
+```
+
+
+## Plot Options ##
+
+All options are completely optional. They are documented individually
+below, to change them you just specify them in an object, e.g.
+
+```js
+var options = {
+ series: {
+ lines: { show: true },
+ points: { show: true }
+ }
+};
+
+$.plot(placeholder, data, options);
+```
+
+
+## Customizing the legend ##
+
+```js
+legend: {
+ show: boolean
+ labelFormatter: null or (fn: string, series object -> string)
+ labelBoxBorderColor: color
+ noColumns: number
+ position: "ne" or "nw" or "se" or "sw"
+ margin: number of pixels or [x margin, y margin]
+ backgroundColor: null or color
+ backgroundOpacity: number between 0 and 1
+ container: null or jQuery object/DOM element/jQuery expression
+ sorted: null/false, true, "ascending", "descending", "reverse", or a comparator
+}
+```
+
+The legend is generated as a table with the data series labels and
+small label boxes with the color of the series. If you want to format
+the labels in some way, e.g. make them to links, you can pass in a
+function for "labelFormatter". Here's an example that makes them
+clickable:
+
+```js
+labelFormatter: function(label, series) {
+ // series is the series object for the label
+ return '
' + label + ' ';
+}
+```
+
+To prevent a series from showing up in the legend, simply have the function
+return null.
+
+"noColumns" is the number of columns to divide the legend table into.
+"position" specifies the overall placement of the legend within the
+plot (top-right, top-left, etc.) and margin the distance to the plot
+edge (this can be either a number or an array of two numbers like [x,
+y]). "backgroundColor" and "backgroundOpacity" specifies the
+background. The default is a partly transparent auto-detected
+background.
+
+If you want the legend to appear somewhere else in the DOM, you can
+specify "container" as a jQuery object/expression to put the legend
+table into. The "position" and "margin" etc. options will then be
+ignored. Note that Flot will overwrite the contents of the container.
+
+Legend entries appear in the same order as their series by default. If "sorted"
+is "reverse" then they appear in the opposite order from their series. To sort
+them alphabetically, you can specify true, "ascending" or "descending", where
+true and "ascending" are equivalent.
+
+You can also provide your own comparator function that accepts two
+objects with "label" and "color" properties, and returns zero if they
+are equal, a positive value if the first is greater than the second,
+and a negative value if the first is less than the second.
+
+```js
+sorted: function(a, b) {
+ // sort alphabetically in ascending order
+ return a.label == b.label ? 0 : (
+ a.label > b.label ? 1 : -1
+ )
+}
+```
+
+
+## Customizing the axes ##
+
+```js
+xaxis, yaxis: {
+ show: null or true/false
+ position: "bottom" or "top" or "left" or "right"
+ mode: null or "time" ("time" requires jquery.flot.time.js plugin)
+ timezone: null, "browser" or timezone (only makes sense for mode: "time")
+
+ color: null or color spec
+ tickColor: null or color spec
+ font: null or font spec object
+
+ min: null or number
+ max: null or number
+ autoscaleMargin: null or number
+
+ transform: null or fn: number -> number
+ inverseTransform: null or fn: number -> number
+
+ ticks: null or number or ticks array or (fn: axis -> ticks array)
+ tickSize: number or array
+ minTickSize: number or array
+ tickFormatter: (fn: number, object -> string) or string
+ tickDecimals: null or number
+
+ labelWidth: null or number
+ labelHeight: null or number
+ reserveSpace: null or true
+
+ tickLength: null or number
+
+ alignTicksWithAxis: null or number
+}
+```
+
+All axes have the same kind of options. The following describes how to
+configure one axis, see below for what to do if you've got more than
+one x axis or y axis.
+
+If you don't set the "show" option (i.e. it is null), visibility is
+auto-detected, i.e. the axis will show up if there's data associated
+with it. You can override this by setting the "show" option to true or
+false.
+
+The "position" option specifies where the axis is placed, bottom or
+top for x axes, left or right for y axes. The "mode" option determines
+how the data is interpreted, the default of null means as decimal
+numbers. Use "time" for time series data; see the time series data
+section. The time plugin (jquery.flot.time.js) is required for time
+series support.
+
+The "color" option determines the color of the line and ticks for the axis, and
+defaults to the grid color with transparency. For more fine-grained control you
+can also set the color of the ticks separately with "tickColor".
+
+You can customize the font and color used to draw the axis tick labels with CSS
+or directly via the "font" option. When "font" is null - the default - each
+tick label is given the 'flot-tick-label' class. For compatibility with Flot
+0.7 and earlier the labels are also given the 'tickLabel' class, but this is
+deprecated and scheduled to be removed with the release of version 1.0.0.
+
+To enable more granular control over styles, labels are divided between a set
+of text containers, with each holding the labels for one axis. These containers
+are given the classes 'flot-[x|y]-axis', and 'flot-[x|y]#-axis', where '#' is
+the number of the axis when there are multiple axes. For example, the x-axis
+labels for a simple plot with only a single x-axis might look like this:
+
+```html
+
+```
+
+For direct control over label styles you can also provide "font" as an object
+with this format:
+
+```js
+{
+ size: 11,
+ lineHeight: 13,
+ style: "italic",
+ weight: "bold",
+ family: "sans-serif",
+ variant: "small-caps",
+ color: "#545454"
+}
+```
+
+The size and lineHeight must be expressed in pixels; CSS units such as 'em'
+or 'smaller' are not allowed.
+
+The options "min"/"max" are the precise minimum/maximum value on the
+scale. If you don't specify either of them, a value will automatically
+be chosen based on the minimum/maximum data values. Note that Flot
+always examines all the data values you feed to it, even if a
+restriction on another axis may make some of them invisible (this
+makes interactive use more stable).
+
+The "autoscaleMargin" is a bit esoteric: it's the fraction of margin
+that the scaling algorithm will add to avoid that the outermost points
+ends up on the grid border. Note that this margin is only applied when
+a min or max value is not explicitly set. If a margin is specified,
+the plot will furthermore extend the axis end-point to the nearest
+whole tick. The default value is "null" for the x axes and 0.02 for y
+axes which seems appropriate for most cases.
+
+"transform" and "inverseTransform" are callbacks you can put in to
+change the way the data is drawn. You can design a function to
+compress or expand certain parts of the axis non-linearly, e.g.
+suppress weekends or compress far away points with a logarithm or some
+other means. When Flot draws the plot, each value is first put through
+the transform function. Here's an example, the x axis can be turned
+into a natural logarithm axis with the following code:
+
+```js
+xaxis: {
+ transform: function (v) { return Math.log(v); },
+ inverseTransform: function (v) { return Math.exp(v); }
+}
+```
+
+Similarly, for reversing the y axis so the values appear in inverse
+order:
+
+```js
+yaxis: {
+ transform: function (v) { return -v; },
+ inverseTransform: function (v) { return -v; }
+}
+```
+
+Note that for finding extrema, Flot assumes that the transform
+function does not reorder values (it should be monotone).
+
+The inverseTransform is simply the inverse of the transform function
+(so v == inverseTransform(transform(v)) for all relevant v). It is
+required for converting from canvas coordinates to data coordinates,
+e.g. for a mouse interaction where a certain pixel is clicked. If you
+don't use any interactive features of Flot, you may not need it.
+
+
+The rest of the options deal with the ticks.
+
+If you don't specify any ticks, a tick generator algorithm will make
+some for you. The algorithm has two passes. It first estimates how
+many ticks would be reasonable and uses this number to compute a nice
+round tick interval size. Then it generates the ticks.
+
+You can specify how many ticks the algorithm aims for by setting
+"ticks" to a number. The algorithm always tries to generate reasonably
+round tick values so even if you ask for three ticks, you might get
+five if that fits better with the rounding. If you don't want any
+ticks at all, set "ticks" to 0 or an empty array.
+
+Another option is to skip the rounding part and directly set the tick
+interval size with "tickSize". If you set it to 2, you'll get ticks at
+2, 4, 6, etc. Alternatively, you can specify that you just don't want
+ticks at a size less than a specific tick size with "minTickSize".
+Note that for time series, the format is an array like [2, "month"],
+see the next section.
+
+If you want to completely override the tick algorithm, you can specify
+an array for "ticks", either like this:
+
+```js
+ticks: [0, 1.2, 2.4]
+```
+
+Or like this where the labels are also customized:
+
+```js
+ticks: [[0, "zero"], [1.2, "one mark"], [2.4, "two marks"]]
+```
+
+You can mix the two if you like.
+
+For extra flexibility you can specify a function as the "ticks"
+parameter. The function will be called with an object with the axis
+min and max and should return a ticks array. Here's a simplistic tick
+generator that spits out intervals of pi, suitable for use on the x
+axis for trigonometric functions:
+
+```js
+function piTickGenerator(axis) {
+ var res = [], i = Math.floor(axis.min / Math.PI);
+ do {
+ var v = i * Math.PI;
+ res.push([v, i + "\u03c0"]);
+ ++i;
+ } while (v < axis.max);
+ return res;
+}
+```
+
+You can control how the ticks look like with "tickDecimals", the
+number of decimals to display (default is auto-detected).
+
+Alternatively, for ultimate control over how ticks are formatted you can
+provide a function to "tickFormatter". The function is passed two
+parameters, the tick value and an axis object with information, and
+should return a string. The default formatter looks like this:
+
+```js
+function formatter(val, axis) {
+ return val.toFixed(axis.tickDecimals);
+}
+```
+
+The axis object has "min" and "max" with the range of the axis,
+"tickDecimals" with the number of decimals to round the value to and
+"tickSize" with the size of the interval between ticks as calculated
+by the automatic axis scaling algorithm (or specified by you). Here's
+an example of a custom formatter:
+
+```js
+function suffixFormatter(val, axis) {
+ if (val > 1000000)
+ return (val / 1000000).toFixed(axis.tickDecimals) + " MB";
+ else if (val > 1000)
+ return (val / 1000).toFixed(axis.tickDecimals) + " kB";
+ else
+ return val.toFixed(axis.tickDecimals) + " B";
+}
+```
+
+"labelWidth" and "labelHeight" specifies a fixed size of the tick
+labels in pixels. They're useful in case you need to align several
+plots. "reserveSpace" means that even if an axis isn't shown, Flot
+should reserve space for it - it is useful in combination with
+labelWidth and labelHeight for aligning multi-axis charts.
+
+"tickLength" is the length of the tick lines in pixels. By default, the
+innermost axes will have ticks that extend all across the plot, while
+any extra axes use small ticks. A value of null means use the default,
+while a number means small ticks of that length - set it to 0 to hide
+the lines completely.
+
+If you set "alignTicksWithAxis" to the number of another axis, e.g.
+alignTicksWithAxis: 1, Flot will ensure that the autogenerated ticks
+of this axis are aligned with the ticks of the other axis. This may
+improve the looks, e.g. if you have one y axis to the left and one to
+the right, because the grid lines will then match the ticks in both
+ends. The trade-off is that the forced ticks won't necessarily be at
+natural places.
+
+
+## Multiple axes ##
+
+If you need more than one x axis or y axis, you need to specify for
+each data series which axis they are to use, as described under the
+format of the data series, e.g. { data: [...], yaxis: 2 } specifies
+that a series should be plotted against the second y axis.
+
+To actually configure that axis, you can't use the xaxis/yaxis options
+directly - instead there are two arrays in the options:
+
+```js
+xaxes: []
+yaxes: []
+```
+
+Here's an example of configuring a single x axis and two y axes (we
+can leave options of the first y axis empty as the defaults are fine):
+
+```js
+{
+ xaxes: [ { position: "top" } ],
+ yaxes: [ { }, { position: "right", min: 20 } ]
+}
+```
+
+The arrays get their default values from the xaxis/yaxis settings, so
+say you want to have all y axes start at zero, you can simply specify
+yaxis: { min: 0 } instead of adding a min parameter to all the axes.
+
+Generally, the various interfaces in Flot dealing with data points
+either accept an xaxis/yaxis parameter to specify which axis number to
+use (starting from 1), or lets you specify the coordinate directly as
+x2/x3/... or x2axis/x3axis/... instead of "x" or "xaxis".
+
+
+## Time series data ##
+
+Please note that it is now required to include the time plugin,
+jquery.flot.time.js, for time series support.
+
+Time series are a bit more difficult than scalar data because
+calendars don't follow a simple base 10 system. For many cases, Flot
+abstracts most of this away, but it can still be a bit difficult to
+get the data into Flot. So we'll first discuss the data format.
+
+The time series support in Flot is based on Javascript timestamps,
+i.e. everywhere a time value is expected or handed over, a Javascript
+timestamp number is used. This is a number, not a Date object. A
+Javascript timestamp is the number of milliseconds since January 1,
+1970 00:00:00 UTC. This is almost the same as Unix timestamps, except it's
+in milliseconds, so remember to multiply by 1000!
+
+You can see a timestamp like this
+
+```js
+alert((new Date()).getTime())
+```
+
+There are different schools of thought when it comes to display of
+timestamps. Many will want the timestamps to be displayed according to
+a certain time zone, usually the time zone in which the data has been
+produced. Some want the localized experience, where the timestamps are
+displayed according to the local time of the visitor. Flot supports
+both. Optionally you can include a third-party library to get
+additional timezone support.
+
+Default behavior is that Flot always displays timestamps according to
+UTC. The reason being that the core Javascript Date object does not
+support other fixed time zones. Often your data is at another time
+zone, so it may take a little bit of tweaking to work around this
+limitation.
+
+The easiest way to think about it is to pretend that the data
+production time zone is UTC, even if it isn't. So if you have a
+datapoint at 2002-02-20 08:00, you can generate a timestamp for eight
+o'clock UTC even if it really happened eight o'clock UTC+0200.
+
+In PHP you can get an appropriate timestamp with:
+
+```php
+strtotime("2002-02-20 UTC") * 1000
+```
+
+In Python you can get it with something like:
+
+```python
+calendar.timegm(datetime_object.timetuple()) * 1000
+```
+In Ruby you can get it using the `#to_i` method on the
+[`Time`](http://apidock.com/ruby/Time/to_i) object. If you're using the
+`active_support` gem (default for Ruby on Rails applications) `#to_i` is also
+available on the `DateTime` and `ActiveSupport::TimeWithZone` objects. You
+simply need to multiply the result by 1000:
+
+```ruby
+Time.now.to_i * 1000 # => 1383582043000
+# ActiveSupport examples:
+DateTime.now.to_i * 1000 # => 1383582043000
+ActiveSupport::TimeZone.new('Asia/Shanghai').now.to_i * 1000
+# => 1383582043000
+```
+
+In .NET you can get it with something like:
+
+```aspx
+public static int GetJavascriptTimestamp(System.DateTime input)
+{
+ System.TimeSpan span = new System.TimeSpan(System.DateTime.Parse("1/1/1970").Ticks);
+ System.DateTime time = input.Subtract(span);
+ return (long)(time.Ticks / 10000);
+}
+```
+
+Javascript also has some support for parsing date strings, so it is
+possible to generate the timestamps manually client-side.
+
+If you've already got the real UTC timestamp, it's too late to use the
+pretend trick described above. But you can fix up the timestamps by
+adding the time zone offset, e.g. for UTC+0200 you would add 2 hours
+to the UTC timestamp you got. Then it'll look right on the plot. Most
+programming environments have some means of getting the timezone
+offset for a specific date (note that you need to get the offset for
+each individual timestamp to account for daylight savings).
+
+The alternative with core Javascript is to interpret the timestamps
+according to the time zone that the visitor is in, which means that
+the ticks will shift with the time zone and daylight savings of each
+visitor. This behavior is enabled by setting the axis option
+"timezone" to the value "browser".
+
+If you need more time zone functionality than this, there is still
+another option. If you include the "timezone-js" library
+
in the page and set axis.timezone
+to a value recognized by said library, Flot will use timezone-js to
+interpret the timestamps according to that time zone.
+
+Once you've gotten the timestamps into the data and specified "time"
+as the axis mode, Flot will automatically generate relevant ticks and
+format them. As always, you can tweak the ticks via the "ticks" option
+- just remember that the values should be timestamps (numbers), not
+Date objects.
+
+Tick generation and formatting can also be controlled separately
+through the following axis options:
+
+```js
+minTickSize: array
+timeformat: null or format string
+monthNames: null or array of size 12 of strings
+dayNames: null or array of size 7 of strings
+twelveHourClock: boolean
+```
+
+Here "timeformat" is a format string to use. You might use it like
+this:
+
+```js
+xaxis: {
+ mode: "time",
+ timeformat: "%Y/%m/%d"
+}
+```
+
+This will result in tick labels like "2000/12/24". A subset of the
+standard strftime specifiers are supported (plus the nonstandard %q):
+
+```js
+%a: weekday name (customizable)
+%b: month name (customizable)
+%d: day of month, zero-padded (01-31)
+%e: day of month, space-padded ( 1-31)
+%H: hours, 24-hour time, zero-padded (00-23)
+%I: hours, 12-hour time, zero-padded (01-12)
+%m: month, zero-padded (01-12)
+%M: minutes, zero-padded (00-59)
+%q: quarter (1-4)
+%S: seconds, zero-padded (00-59)
+%y: year (two digits)
+%Y: year (four digits)
+%p: am/pm
+%P: AM/PM (uppercase version of %p)
+%w: weekday as number (0-6, 0 being Sunday)
+```
+
+Flot 0.8 switched from %h to the standard %H hours specifier. The %h specifier
+is still available, for backwards-compatibility, but is deprecated and
+scheduled to be removed permanently with the release of version 1.0.
+
+You can customize the month names with the "monthNames" option. For
+instance, for Danish you might specify:
+
+```js
+monthNames: ["jan", "feb", "mar", "apr", "maj", "jun", "jul", "aug", "sep", "okt", "nov", "dec"]
+```
+
+Similarly you can customize the weekday names with the "dayNames"
+option. An example in French:
+
+```js
+dayNames: ["dim", "lun", "mar", "mer", "jeu", "ven", "sam"]
+```
+
+If you set "twelveHourClock" to true, the autogenerated timestamps
+will use 12 hour AM/PM timestamps instead of 24 hour. This only
+applies if you have not set "timeformat". Use the "%I" and "%p" or
+"%P" options if you want to build your own format string with 12-hour
+times.
+
+If the Date object has a strftime property (and it is a function), it
+will be used instead of the built-in formatter. Thus you can include
+a strftime library such as http://hacks.bluesmoon.info/strftime/ for
+more powerful date/time formatting.
+
+If everything else fails, you can control the formatting by specifying
+a custom tick formatter function as usual. Here's a simple example
+which will format December 24 as 24/12:
+
+```js
+tickFormatter: function (val, axis) {
+ var d = new Date(val);
+ return d.getUTCDate() + "/" + (d.getUTCMonth() + 1);
+}
+```
+
+Note that for the time mode "tickSize" and "minTickSize" are a bit
+special in that they are arrays on the form "[value, unit]" where unit
+is one of "second", "minute", "hour", "day", "month" and "year". So
+you can specify
+
+```js
+minTickSize: [1, "month"]
+```
+
+to get a tick interval size of at least 1 month and correspondingly,
+if axis.tickSize is [2, "day"] in the tick formatter, the ticks have
+been produced with two days in-between.
+
+
+## Customizing the data series ##
+
+```js
+series: {
+ lines, points, bars: {
+ show: boolean
+ lineWidth: number
+ fill: boolean or number
+ fillColor: null or color/gradient
+ }
+
+ lines, bars: {
+ zero: boolean
+ }
+
+ points: {
+ radius: number
+ symbol: "circle" or function
+ }
+
+ bars: {
+ barWidth: number
+ align: "left", "right" or "center"
+ horizontal: boolean
+ }
+
+ lines: {
+ steps: boolean
+ }
+
+ shadowSize: number
+ highlightColor: color or number
+}
+
+colors: [ color1, color2, ... ]
+```
+
+The options inside "series: {}" are copied to each of the series. So
+you can specify that all series should have bars by putting it in the
+global options, or override it for individual series by specifying
+bars in a particular the series object in the array of data.
+
+The most important options are "lines", "points" and "bars" that
+specify whether and how lines, points and bars should be shown for
+each data series. In case you don't specify anything at all, Flot will
+default to showing lines (you can turn this off with
+lines: { show: false }). You can specify the various types
+independently of each other, and Flot will happily draw each of them
+in turn (this is probably only useful for lines and points), e.g.
+
+```js
+var options = {
+ series: {
+ lines: { show: true, fill: true, fillColor: "rgba(255, 255, 255, 0.8)" },
+ points: { show: true, fill: false }
+ }
+};
+```
+
+"lineWidth" is the thickness of the line or outline in pixels. You can
+set it to 0 to prevent a line or outline from being drawn; this will
+also hide the shadow.
+
+"fill" is whether the shape should be filled. For lines, this produces
+area graphs. You can use "fillColor" to specify the color of the fill.
+If "fillColor" evaluates to false (default for everything except
+points which are filled with white), the fill color is auto-set to the
+color of the data series. You can adjust the opacity of the fill by
+setting fill to a number between 0 (fully transparent) and 1 (fully
+opaque).
+
+For bars, fillColor can be a gradient, see the gradient documentation
+below. "barWidth" is the width of the bars in units of the x axis (or
+the y axis if "horizontal" is true), contrary to most other measures
+that are specified in pixels. For instance, for time series the unit
+is milliseconds so 24 * 60 * 60 * 1000 produces bars with the width of
+a day. "align" specifies whether a bar should be left-aligned
+(default), right-aligned or centered on top of the value it represents.
+When "horizontal" is on, the bars are drawn horizontally, i.e. from the
+y axis instead of the x axis; note that the bar end points are still
+defined in the same way so you'll probably want to swap the
+coordinates if you've been plotting vertical bars first.
+
+Area and bar charts normally start from zero, regardless of the data's range.
+This is because they convey information through size, and starting from a
+different value would distort their meaning. In cases where the fill is purely
+for decorative purposes, however, "zero" allows you to override this behavior.
+It defaults to true for filled lines and bars; setting it to false tells the
+series to use the same automatic scaling as an un-filled line.
+
+For lines, "steps" specifies whether two adjacent data points are
+connected with a straight (possibly diagonal) line or with first a
+horizontal and then a vertical line. Note that this transforms the
+data by adding extra points.
+
+For points, you can specify the radius and the symbol. The only
+built-in symbol type is circles, for other types you can use a plugin
+or define them yourself by specifying a callback:
+
+```js
+function cross(ctx, x, y, radius, shadow) {
+ var size = radius * Math.sqrt(Math.PI) / 2;
+ ctx.moveTo(x - size, y - size);
+ ctx.lineTo(x + size, y + size);
+ ctx.moveTo(x - size, y + size);
+ ctx.lineTo(x + size, y - size);
+}
+```
+
+The parameters are the drawing context, x and y coordinates of the
+center of the point, a radius which corresponds to what the circle
+would have used and whether the call is to draw a shadow (due to
+limited canvas support, shadows are currently faked through extra
+draws). It's good practice to ensure that the area covered by the
+symbol is the same as for the circle with the given radius, this
+ensures that all symbols have approximately the same visual weight.
+
+"shadowSize" is the default size of shadows in pixels. Set it to 0 to
+remove shadows.
+
+"highlightColor" is the default color of the translucent overlay used
+to highlight the series when the mouse hovers over it.
+
+The "colors" array specifies a default color theme to get colors for
+the data series from. You can specify as many colors as you like, like
+this:
+
+```js
+colors: ["#d18b2c", "#dba255", "#919733"]
+```
+
+If there are more data series than colors, Flot will try to generate
+extra colors by lightening and darkening colors in the theme.
+
+
+## Customizing the grid ##
+
+```js
+grid: {
+ show: boolean
+ aboveData: boolean
+ color: color
+ backgroundColor: color/gradient or null
+ margin: number or margin object
+ labelMargin: number
+ axisMargin: number
+ markings: array of markings or (fn: axes -> array of markings)
+ borderWidth: number or object with "top", "right", "bottom" and "left" properties with different widths
+ borderColor: color or null or object with "top", "right", "bottom" and "left" properties with different colors
+ minBorderMargin: number or null
+ clickable: boolean
+ hoverable: boolean
+ autoHighlight: boolean
+ mouseActiveRadius: number
+}
+
+interaction: {
+ redrawOverlayInterval: number or -1
+}
+```
+
+The grid is the thing with the axes and a number of ticks. Many of the
+things in the grid are configured under the individual axes, but not
+all. "color" is the color of the grid itself whereas "backgroundColor"
+specifies the background color inside the grid area, here null means
+that the background is transparent. You can also set a gradient, see
+the gradient documentation below.
+
+You can turn off the whole grid including tick labels by setting
+"show" to false. "aboveData" determines whether the grid is drawn
+above the data or below (below is default).
+
+"margin" is the space in pixels between the canvas edge and the grid,
+which can be either a number or an object with individual margins for
+each side, in the form:
+
+```js
+margin: {
+ top: top margin in pixels
+ left: left margin in pixels
+ bottom: bottom margin in pixels
+ right: right margin in pixels
+}
+```
+
+"labelMargin" is the space in pixels between tick labels and axis
+line, and "axisMargin" is the space in pixels between axes when there
+are two next to each other.
+
+"borderWidth" is the width of the border around the plot. Set it to 0
+to disable the border. Set it to an object with "top", "right",
+"bottom" and "left" properties to use different widths. You can
+also set "borderColor" if you want the border to have a different color
+than the grid lines. Set it to an object with "top", "right", "bottom"
+and "left" properties to use different colors. "minBorderMargin" controls
+the default minimum margin around the border - it's used to make sure
+that points aren't accidentally clipped by the canvas edge so by default
+the value is computed from the point radius.
+
+"markings" is used to draw simple lines and rectangular areas in the
+background of the plot. You can either specify an array of ranges on
+the form { xaxis: { from, to }, yaxis: { from, to } } (with multiple
+axes, you can specify coordinates for other axes instead, e.g. as
+x2axis/x3axis/...) or with a function that returns such an array given
+the axes for the plot in an object as the first parameter.
+
+You can set the color of markings by specifying "color" in the ranges
+object. Here's an example array:
+
+```js
+markings: [ { xaxis: { from: 0, to: 2 }, yaxis: { from: 10, to: 10 }, color: "#bb0000" }, ... ]
+```
+
+If you leave out one of the values, that value is assumed to go to the
+border of the plot. So for example if you only specify { xaxis: {
+from: 0, to: 2 } } it means an area that extends from the top to the
+bottom of the plot in the x range 0-2.
+
+A line is drawn if from and to are the same, e.g.
+
+```js
+markings: [ { yaxis: { from: 1, to: 1 } }, ... ]
+```
+
+would draw a line parallel to the x axis at y = 1. You can control the
+line width with "lineWidth" in the range object.
+
+An example function that makes vertical stripes might look like this:
+
+```js
+markings: function (axes) {
+ var markings = [];
+ for (var x = Math.floor(axes.xaxis.min); x < axes.xaxis.max; x += 2)
+ markings.push({ xaxis: { from: x, to: x + 1 } });
+ return markings;
+}
+```
+
+If you set "clickable" to true, the plot will listen for click events
+on the plot area and fire a "plotclick" event on the placeholder with
+a position and a nearby data item object as parameters. The coordinates
+are available both in the unit of the axes (not in pixels) and in
+global screen coordinates.
+
+Likewise, if you set "hoverable" to true, the plot will listen for
+mouse move events on the plot area and fire a "plothover" event with
+the same parameters as the "plotclick" event. If "autoHighlight" is
+true (the default), nearby data items are highlighted automatically.
+If needed, you can disable highlighting and control it yourself with
+the highlight/unhighlight plot methods described elsewhere.
+
+You can use "plotclick" and "plothover" events like this:
+
+```js
+$.plot($("#placeholder"), [ d ], { grid: { clickable: true } });
+
+$("#placeholder").bind("plotclick", function (event, pos, item) {
+ alert("You clicked at " + pos.x + ", " + pos.y);
+ // axis coordinates for other axes, if present, are in pos.x2, pos.x3, ...
+ // if you need global screen coordinates, they are pos.pageX, pos.pageY
+
+ if (item) {
+ highlight(item.series, item.datapoint);
+ alert("You clicked a point!");
+ }
+});
+```
+
+The item object in this example is either null or a nearby object on the form:
+
+```js
+item: {
+ datapoint: the point, e.g. [0, 2]
+ dataIndex: the index of the point in the data array
+ series: the series object
+ seriesIndex: the index of the series
+ pageX, pageY: the global screen coordinates of the point
+}
+```
+
+For instance, if you have specified the data like this
+
+```js
+$.plot($("#placeholder"), [ { label: "Foo", data: [[0, 10], [7, 3]] } ], ...);
+```
+
+and the mouse is near the point (7, 3), "datapoint" is [7, 3],
+"dataIndex" will be 1, "series" is a normalized series object with
+among other things the "Foo" label in series.label and the color in
+series.color, and "seriesIndex" is 0. Note that plugins and options
+that transform the data can shift the indexes from what you specified
+in the original data array.
+
+If you use the above events to update some other information and want
+to clear out that info in case the mouse goes away, you'll probably
+also need to listen to "mouseout" events on the placeholder div.
+
+"mouseActiveRadius" specifies how far the mouse can be from an item
+and still activate it. If there are two or more points within this
+radius, Flot chooses the closest item. For bars, the top-most bar
+(from the latest specified data series) is chosen.
+
+If you want to disable interactivity for a specific data series, you
+can set "hoverable" and "clickable" to false in the options for that
+series, like this:
+
+```js
+{ data: [...], label: "Foo", clickable: false }
+```
+
+"redrawOverlayInterval" specifies the maximum time to delay a redraw
+of interactive things (this works as a rate limiting device). The
+default is capped to 60 frames per second. You can set it to -1 to
+disable the rate limiting.
+
+
+## Specifying gradients ##
+
+A gradient is specified like this:
+
+```js
+{ colors: [ color1, color2, ... ] }
+```
+
+For instance, you might specify a background on the grid going from
+black to gray like this:
+
+```js
+grid: {
+ backgroundColor: { colors: ["#000", "#999"] }
+}
+```
+
+For the series you can specify the gradient as an object that
+specifies the scaling of the brightness and the opacity of the series
+color, e.g.
+
+```js
+{ colors: [{ opacity: 0.8 }, { brightness: 0.6, opacity: 0.8 } ] }
+```
+
+where the first color simply has its alpha scaled, whereas the second
+is also darkened. For instance, for bars the following makes the bars
+gradually disappear, without outline:
+
+```js
+bars: {
+ show: true,
+ lineWidth: 0,
+ fill: true,
+ fillColor: { colors: [ { opacity: 0.8 }, { opacity: 0.1 } ] }
+}
+```
+
+Flot currently only supports vertical gradients drawn from top to
+bottom because that's what works with IE.
+
+
+## Plot Methods ##
+
+The Plot object returned from the plot function has some methods you
+can call:
+
+ - highlight(series, datapoint)
+
+ Highlight a specific datapoint in the data series. You can either
+ specify the actual objects, e.g. if you got them from a
+ "plotclick" event, or you can specify the indices, e.g.
+ highlight(1, 3) to highlight the fourth point in the second series
+ (remember, zero-based indexing).
+
+ - unhighlight(series, datapoint) or unhighlight()
+
+ Remove the highlighting of the point, same parameters as
+ highlight.
+
+ If you call unhighlight with no parameters, e.g. as
+ plot.unhighlight(), all current highlights are removed.
+
+ - setData(data)
+
+ You can use this to reset the data used. Note that axis scaling,
+ ticks, legend etc. will not be recomputed (use setupGrid() to do
+ that). You'll probably want to call draw() afterwards.
+
+ You can use this function to speed up redrawing a small plot if
+ you know that the axes won't change. Put in the new data with
+ setData(newdata), call draw(), and you're good to go. Note that
+ for large datasets, almost all the time is consumed in draw()
+ plotting the data so in this case don't bother.
+
+ - setupGrid()
+
+ Recalculate and set axis scaling, ticks, legend etc.
+
+ Note that because of the drawing model of the canvas, this
+ function will immediately redraw (actually reinsert in the DOM)
+ the labels and the legend, but not the actual tick lines because
+ they're drawn on the canvas. You need to call draw() to get the
+ canvas redrawn.
+
+ - draw()
+
+ Redraws the plot canvas.
+
+ - triggerRedrawOverlay()
+
+ Schedules an update of an overlay canvas used for drawing
+ interactive things like a selection and point highlights. This
+ is mostly useful for writing plugins. The redraw doesn't happen
+ immediately, instead a timer is set to catch multiple successive
+ redraws (e.g. from a mousemove). You can get to the overlay by
+ setting up a drawOverlay hook.
+
+ - width()/height()
+
+ Gets the width and height of the plotting area inside the grid.
+ This is smaller than the canvas or placeholder dimensions as some
+ extra space is needed (e.g. for labels).
+
+ - offset()
+
+ Returns the offset of the plotting area inside the grid relative
+ to the document, useful for instance for calculating mouse
+ positions (event.pageX/Y minus this offset is the pixel position
+ inside the plot).
+
+ - pointOffset({ x: xpos, y: ypos })
+
+ Returns the calculated offset of the data point at (x, y) in data
+ space within the placeholder div. If you are working with multiple
+ axes, you can specify the x and y axis references, e.g.
+
+ ```js
+ o = pointOffset({ x: xpos, y: ypos, xaxis: 2, yaxis: 3 })
+ // o.left and o.top now contains the offset within the div
+ ````
+
+ - resize()
+
+ Tells Flot to resize the drawing canvas to the size of the
+ placeholder. You need to run setupGrid() and draw() afterwards as
+ canvas resizing is a destructive operation. This is used
+ internally by the resize plugin.
+
+ - shutdown()
+
+ Cleans up any event handlers Flot has currently registered. This
+ is used internally.
+
+There are also some members that let you peek inside the internal
+workings of Flot which is useful in some cases. Note that if you change
+something in the objects returned, you're changing the objects used by
+Flot to keep track of its state, so be careful.
+
+ - getData()
+
+ Returns an array of the data series currently used in normalized
+ form with missing settings filled in according to the global
+ options. So for instance to find out what color Flot has assigned
+ to the data series, you could do this:
+
+ ```js
+ var series = plot.getData();
+ for (var i = 0; i < series.length; ++i)
+ alert(series[i].color);
+ ```
+
+ A notable other interesting field besides color is datapoints
+ which has a field "points" with the normalized data points in a
+ flat array (the field "pointsize" is the increment in the flat
+ array to get to the next point so for a dataset consisting only of
+ (x,y) pairs it would be 2).
+
+ - getAxes()
+
+ Gets an object with the axes. The axes are returned as the
+ attributes of the object, so for instance getAxes().xaxis is the
+ x axis.
+
+ Various things are stuffed inside an axis object, e.g. you could
+ use getAxes().xaxis.ticks to find out what the ticks are for the
+ xaxis. Two other useful attributes are p2c and c2p, functions for
+ transforming from data point space to the canvas plot space and
+ back. Both returns values that are offset with the plot offset.
+ Check the Flot source code for the complete set of attributes (or
+ output an axis with console.log() and inspect it).
+
+ With multiple axes, the extra axes are returned as x2axis, x3axis,
+ etc., e.g. getAxes().y2axis is the second y axis. You can check
+ y2axis.used to see whether the axis is associated with any data
+ points and y2axis.show to see if it is currently shown.
+
+ - getPlaceholder()
+
+ Returns placeholder that the plot was put into. This can be useful
+ for plugins for adding DOM elements or firing events.
+
+ - getCanvas()
+
+ Returns the canvas used for drawing in case you need to hack on it
+ yourself. You'll probably need to get the plot offset too.
+
+ - getPlotOffset()
+
+ Gets the offset that the grid has within the canvas as an object
+ with distances from the canvas edges as "left", "right", "top",
+ "bottom". I.e., if you draw a circle on the canvas with the center
+ placed at (left, top), its center will be at the top-most, left
+ corner of the grid.
+
+ - getOptions()
+
+ Gets the options for the plot, normalized, with default values
+ filled in. You get a reference to actual values used by Flot, so
+ if you modify the values in here, Flot will use the new values.
+ If you change something, you probably have to call draw() or
+ setupGrid() or triggerRedrawOverlay() to see the change.
+
+
+## Hooks ##
+
+In addition to the public methods, the Plot object also has some hooks
+that can be used to modify the plotting process. You can install a
+callback function at various points in the process, the function then
+gets access to the internal data structures in Flot.
+
+Here's an overview of the phases Flot goes through:
+
+ 1. Plugin initialization, parsing options
+
+ 2. Constructing the canvases used for drawing
+
+ 3. Set data: parsing data specification, calculating colors,
+ copying raw data points into internal format,
+ normalizing them, finding max/min for axis auto-scaling
+
+ 4. Grid setup: calculating axis spacing, ticks, inserting tick
+ labels, the legend
+
+ 5. Draw: drawing the grid, drawing each of the series in turn
+
+ 6. Setting up event handling for interactive features
+
+ 7. Responding to events, if any
+
+ 8. Shutdown: this mostly happens in case a plot is overwritten
+
+Each hook is simply a function which is put in the appropriate array.
+You can add them through the "hooks" option, and they are also available
+after the plot is constructed as the "hooks" attribute on the returned
+plot object, e.g.
+
+```js
+ // define a simple draw hook
+ function hellohook(plot, canvascontext) { alert("hello!"); };
+
+ // pass it in, in an array since we might want to specify several
+ var plot = $.plot(placeholder, data, { hooks: { draw: [hellohook] } });
+
+ // we can now find it again in plot.hooks.draw[0] unless a plugin
+ // has added other hooks
+```
+
+The available hooks are described below. All hook callbacks get the
+plot object as first parameter. You can find some examples of defined
+hooks in the plugins bundled with Flot.
+
+ - processOptions [phase 1]
+
+ ```function(plot, options)```
+
+ Called after Flot has parsed and merged options. Useful in the
+ instance where customizations beyond simple merging of default
+ values is needed. A plugin might use it to detect that it has been
+ enabled and then turn on or off other options.
+
+
+ - processRawData [phase 3]
+
+ ```function(plot, series, data, datapoints)```
+
+ Called before Flot copies and normalizes the raw data for the given
+ series. If the function fills in datapoints.points with normalized
+ points and sets datapoints.pointsize to the size of the points,
+ Flot will skip the copying/normalization step for this series.
+
+ In any case, you might be interested in setting datapoints.format,
+ an array of objects for specifying how a point is normalized and
+ how it interferes with axis scaling. It accepts the following options:
+
+ ```js
+ {
+ x, y: boolean,
+ number: boolean,
+ required: boolean,
+ defaultValue: value,
+ autoscale: boolean
+ }
+ ```
+
+ "x" and "y" specify whether the value is plotted against the x or y axis,
+ and is currently used only to calculate axis min-max ranges. The default
+ format array, for example, looks like this:
+
+ ```js
+ [
+ { x: true, number: true, required: true },
+ { y: true, number: true, required: true }
+ ]
+ ```
+
+ This indicates that a point, i.e. [0, 25], consists of two values, with the
+ first being plotted on the x axis and the second on the y axis.
+
+ If "number" is true, then the value must be numeric, and is set to null if
+ it cannot be converted to a number.
+
+ "defaultValue" provides a fallback in case the original value is null. This
+ is for instance handy for bars, where one can omit the third coordinate
+ (the bottom of the bar), which then defaults to zero.
+
+ If "required" is true, then the value must exist (be non-null) for the
+ point as a whole to be valid. If no value is provided, then the entire
+ point is cleared out with nulls, turning it into a gap in the series.
+
+ "autoscale" determines whether the value is considered when calculating an
+ automatic min-max range for the axes that the value is plotted against.
+
+ - processDatapoints [phase 3]
+
+ ```function(plot, series, datapoints)```
+
+ Called after normalization of the given series but before finding
+ min/max of the data points. This hook is useful for implementing data
+ transformations. "datapoints" contains the normalized data points in
+ a flat array as datapoints.points with the size of a single point
+ given in datapoints.pointsize. Here's a simple transform that
+ multiplies all y coordinates by 2:
+
+ ```js
+ function multiply(plot, series, datapoints) {
+ var points = datapoints.points, ps = datapoints.pointsize;
+ for (var i = 0; i < points.length; i += ps)
+ points[i + 1] *= 2;
+ }
+ ```
+
+ Note that you must leave datapoints in a good condition as Flot
+ doesn't check it or do any normalization on it afterwards.
+
+ - processOffset [phase 4]
+
+ ```function(plot, offset)```
+
+ Called after Flot has initialized the plot's offset, but before it
+ draws any axes or plot elements. This hook is useful for customizing
+ the margins between the grid and the edge of the canvas. "offset" is
+ an object with attributes "top", "bottom", "left" and "right",
+ corresponding to the margins on the four sides of the plot.
+
+ - drawBackground [phase 5]
+
+ ```function(plot, canvascontext)```
+
+ Called before all other drawing operations. Used to draw backgrounds
+ or other custom elements before the plot or axes have been drawn.
+
+ - drawSeries [phase 5]
+
+ ```function(plot, canvascontext, series)```
+
+ Hook for custom drawing of a single series. Called just before the
+ standard drawing routine has been called in the loop that draws
+ each series.
+
+ - draw [phase 5]
+
+ ```function(plot, canvascontext)```
+
+ Hook for drawing on the canvas. Called after the grid is drawn
+ (unless it's disabled or grid.aboveData is set) and the series have
+ been plotted (in case any points, lines or bars have been turned
+ on). For examples of how to draw things, look at the source code.
+
+ - bindEvents [phase 6]
+
+ ```function(plot, eventHolder)```
+
+ Called after Flot has setup its event handlers. Should set any
+ necessary event handlers on eventHolder, a jQuery object with the
+ canvas, e.g.
+
+ ```js
+ function (plot, eventHolder) {
+ eventHolder.mousedown(function (e) {
+ alert("You pressed the mouse at " + e.pageX + " " + e.pageY);
+ });
+ }
+ ```
+
+ Interesting events include click, mousemove, mouseup/down. You can
+ use all jQuery events. Usually, the event handlers will update the
+ state by drawing something (add a drawOverlay hook and call
+ triggerRedrawOverlay) or firing an externally visible event for
+ user code. See the crosshair plugin for an example.
+
+ Currently, eventHolder actually contains both the static canvas
+ used for the plot itself and the overlay canvas used for
+ interactive features because some versions of IE get the stacking
+ order wrong. The hook only gets one event, though (either for the
+ overlay or for the static canvas).
+
+ Note that custom plot events generated by Flot are not generated on
+ eventHolder, but on the div placeholder supplied as the first
+ argument to the plot call. You can get that with
+ plot.getPlaceholder() - that's probably also the one you should use
+ if you need to fire a custom event.
+
+ - drawOverlay [phase 7]
+
+ ```function (plot, canvascontext)```
+
+ The drawOverlay hook is used for interactive things that need a
+ canvas to draw on. The model currently used by Flot works the way
+ that an extra overlay canvas is positioned on top of the static
+ canvas. This overlay is cleared and then completely redrawn
+ whenever something interesting happens. This hook is called when
+ the overlay canvas is to be redrawn.
+
+ "canvascontext" is the 2D context of the overlay canvas. You can
+ use this to draw things. You'll most likely need some of the
+ metrics computed by Flot, e.g. plot.width()/plot.height(). See the
+ crosshair plugin for an example.
+
+ - shutdown [phase 8]
+
+ ```function (plot, eventHolder)```
+
+ Run when plot.shutdown() is called, which usually only happens in
+ case a plot is overwritten by a new plot. If you're writing a
+ plugin that adds extra DOM elements or event handlers, you should
+ add a callback to clean up after you. Take a look at the section in
+ the [PLUGINS](PLUGINS.md) document for more info.
+
+
+## Plugins ##
+
+Plugins extend the functionality of Flot. To use a plugin, simply
+include its Javascript file after Flot in the HTML page.
+
+If you're worried about download size/latency, you can concatenate all
+the plugins you use, and Flot itself for that matter, into one big file
+(make sure you get the order right), then optionally run it through a
+Javascript minifier such as YUI Compressor.
+
+Here's a brief explanation of how the plugin plumbings work:
+
+Each plugin registers itself in the global array $.plot.plugins. When
+you make a new plot object with $.plot, Flot goes through this array
+calling the "init" function of each plugin and merging default options
+from the "option" attribute of the plugin. The init function gets a
+reference to the plot object created and uses this to register hooks
+and add new public methods if needed.
+
+See the [PLUGINS](PLUGINS.md) document for details on how to write a plugin. As the
+above description hints, it's actually pretty easy.
+
+
+## Version number ##
+
+The version number of Flot is available in ```$.plot.version```.
diff --git a/djangoproject/static/js/lib/jquery-flot/CONTRIBUTING.md b/djangoproject/static/js/lib/jquery-flot/CONTRIBUTING.md
new file mode 100644
index 000000000..3e6e43a0f
--- /dev/null
+++ b/djangoproject/static/js/lib/jquery-flot/CONTRIBUTING.md
@@ -0,0 +1,98 @@
+## Contributing to Flot ##
+
+We welcome all contributions, but following these guidelines results in less
+work for us, and a faster and better response.
+
+### Issues ###
+
+Issues are not a way to ask general questions about Flot. If you see unexpected
+behavior but are not 100% certain that it is a bug, please try posting to the
+[forum](http://groups.google.com/group/flot-graphs) first, and confirm that
+what you see is really a Flot problem before creating a new issue for it. When
+reporting a bug, please include a working demonstration of the problem, if
+possible, or at least a clear description of the options you're using and the
+environment (browser and version, jQuery version, other libraries) that you're
+running under.
+
+If you have suggestions for new features, or changes to existing ones, we'd
+love to hear them! Please submit each suggestion as a separate new issue.
+
+If you would like to work on an existing issue, please make sure it is not
+already assigned to someone else. If an issue is assigned to someone, that
+person has already started working on it. So, pick unassigned issues to prevent
+duplicated effort.
+
+### Pull Requests ###
+
+To make merging as easy as possible, please keep these rules in mind:
+
+ 1. Submit new features or architectural changes to the *<version>-work*
+ branch for the next major release. Submit bug fixes to the master branch.
+
+ 2. Divide larger changes into a series of small, logical commits with
+ descriptive messages.
+
+ 3. Rebase, if necessary, before submitting your pull request, to reduce the
+ work we need to do to merge it.
+
+ 4. Format your code according to the style guidelines below.
+
+### Flot Style Guidelines ###
+
+Flot follows the [jQuery Core Style Guidelines](http://docs.jquery.com/JQuery_Core_Style_Guidelines),
+with the following updates and exceptions:
+
+#### Spacing ####
+
+Use four-space indents, no tabs. Do not add horizontal space around parameter
+lists, loop definitions, or array/object indices. For example:
+
+```js
+ for ( var i = 0; i < data.length; i++ ) { // This block is wrong!
+ if ( data[ i ] > 1 ) {
+ data[ i ] = 2;
+ }
+ }
+
+ for (var i = 0; i < data.length; i++) { // This block is correct!
+ if (data[i] > 1) {
+ data[i] = 2;
+ }
+ }
+```
+
+#### Comments ####
+
+Use [jsDoc](http://usejsdoc.org) comments for all file and function headers.
+Use // for all inline and block comments, regardless of length.
+
+All // comment blocks should have an empty line above *and* below them. For
+example:
+
+```js
+ var a = 5;
+
+ // We're going to loop here
+ // TODO: Make this loop faster, better, stronger!
+
+ for (var x = 0; x < 10; x++) {}
+```
+
+#### Wrapping ####
+
+Block comments should be wrapped at 80 characters.
+
+Code should attempt to wrap at 80 characters, but may run longer if wrapping
+would hurt readability more than having to scroll horizontally. This is a
+judgement call made on a situational basis.
+
+Statements containing complex logic should not be wrapped arbitrarily if they
+do not exceed 80 characters. For example:
+
+```js
+ if (a == 1 && // This block is wrong!
+ b == 2 &&
+ c == 3) {}
+
+ if (a == 1 && b == 2 && c == 3) {} // This block is correct!
+```
diff --git a/djangoproject/static/js/lib/jquery-flot/FAQ.md b/djangoproject/static/js/lib/jquery-flot/FAQ.md
new file mode 100644
index 000000000..9131e0439
--- /dev/null
+++ b/djangoproject/static/js/lib/jquery-flot/FAQ.md
@@ -0,0 +1,75 @@
+## Frequently asked questions ##
+
+#### How much data can Flot cope with? ####
+
+Flot will happily draw everything you send to it so the answer
+depends on the browser. The excanvas emulation used for IE (built with
+VML) makes IE by far the slowest browser so be sure to test with that
+if IE users are in your target group (for large plots in IE, you can
+also check out Flashcanvas which may be faster).
+
+1000 points is not a problem, but as soon as you start having more
+points than the pixel width, you should probably start thinking about
+downsampling/aggregation as this is near the resolution limit of the
+chart anyway. If you downsample server-side, you also save bandwidth.
+
+
+#### Flot isn't working when I'm using JSON data as source! ####
+
+Actually, Flot loves JSON data, you just got the format wrong.
+Double check that you're not inputting strings instead of numbers,
+like [["0", "-2.13"], ["5", "4.3"]]. This is most common mistake, and
+the error might not show up immediately because Javascript can do some
+conversion automatically.
+
+
+#### Can I export the graph? ####
+
+You can grab the image rendered by the canvas element used by Flot
+as a PNG or JPEG (remember to set a background). Note that it won't
+include anything not drawn in the canvas (such as the legend). And it
+doesn't work with excanvas which uses VML, but you could try
+Flashcanvas.
+
+
+#### The bars are all tiny in time mode? ####
+
+It's not really possible to determine the bar width automatically.
+So you have to set the width with the barWidth option which is NOT in
+pixels, but in the units of the x axis (or the y axis for horizontal
+bars). For time mode that's milliseconds so the default value of 1
+makes the bars 1 millisecond wide.
+
+
+#### Can I use Flot with libraries like Mootools or Prototype? ####
+
+Yes, Flot supports it out of the box and it's easy! Just use jQuery
+instead of $, e.g. call jQuery.plot instead of $.plot and use
+jQuery(something) instead of $(something). As a convenience, you can
+put in a DOM element for the graph placeholder where the examples and
+the API documentation are using jQuery objects.
+
+Depending on how you include jQuery, you may have to add one line of
+code to prevent jQuery from overwriting functions from the other
+libraries, see the documentation in jQuery ("Using jQuery with other
+libraries") for details.
+
+
+#### Flot doesn't work with [insert name of Javascript UI framework]! ####
+
+Flot is using standard HTML to make charts. If this is not working,
+it's probably because the framework you're using is doing something
+weird with the DOM or with the CSS that is interfering with Flot.
+
+A common problem is that there's display:none on a container until the
+user does something. Many tab widgets work this way, and there's
+nothing wrong with it - you just can't call Flot inside a display:none
+container as explained in the README so you need to hold off the Flot
+call until the container is actually displayed (or use
+visibility:hidden instead of display:none or move the container
+off-screen).
+
+If you find there's a specific thing we can do to Flot to help, feel
+free to submit a bug report. Otherwise, you're welcome to ask for help
+on the forum/mailing list, but please don't submit a bug report to
+Flot.
diff --git a/djangoproject/static/js/lib/jquery-flot/LICENSE.txt b/djangoproject/static/js/lib/jquery-flot/LICENSE.txt
new file mode 100644
index 000000000..719da064f
--- /dev/null
+++ b/djangoproject/static/js/lib/jquery-flot/LICENSE.txt
@@ -0,0 +1,22 @@
+Copyright (c) 2007-2014 IOLA and Ole Laursen
+
+Permission is hereby granted, free of charge, to any person
+obtaining a copy of this software and associated documentation
+files (the "Software"), to deal in the Software without
+restriction, including without limitation the rights to use,
+copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the
+Software is furnished to do so, subject to the following
+conditions:
+
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
+OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+OTHER DEALINGS IN THE SOFTWARE.
diff --git a/djangoproject/static/js/lib/jquery-flot/Makefile b/djangoproject/static/js/lib/jquery-flot/Makefile
new file mode 100644
index 000000000..2e070d0c3
--- /dev/null
+++ b/djangoproject/static/js/lib/jquery-flot/Makefile
@@ -0,0 +1,12 @@
+# Makefile for generating minified files
+
+.PHONY: all
+
+# we cheat and process all .js files instead of an exhaustive list
+all: $(patsubst %.js,%.min.js,$(filter-out %.min.js,$(wildcard *.js)))
+
+%.min.js: %.js
+ yui-compressor $< -o $@
+
+test:
+ ./node_modules/.bin/jshint *jquery.flot.js
diff --git a/djangoproject/static/js/lib/jquery-flot/NEWS.md b/djangoproject/static/js/lib/jquery-flot/NEWS.md
new file mode 100644
index 000000000..ad0303d74
--- /dev/null
+++ b/djangoproject/static/js/lib/jquery-flot/NEWS.md
@@ -0,0 +1,1026 @@
+## Flot 0.8.3 ##
+
+### Changes ###
+
+- Updated example code to avoid encouraging unnecessary re-plots.
+ (patch by soenter, pull request #1221)
+
+### Bug fixes ###
+
+ - Added a work-around to disable the allocation of extra space for first and
+ last axis ticks, allowing plots to span the full width of their container.
+ A proper solution for this bug will be implemented in the 0.9 release.
+ (reported by Josh Pigford and andig, issue #1212, pull request #1290)
+
+ - Fixed a regression introduced in 0.8.1, where the last tick label would
+ sometimes wrap rather than extending the plot's offset to create space.
+ (reported by Elite Gamer, issue #1283)
+
+ - Fixed a regression introduced in 0.8.2, where the resize plugin would use
+ unexpectedly high amounts of CPU even when idle.
+ (reported by tommie, issue #1277, pull request #1289)
+
+ - Fixed the selection example to work with jQuery 1.9.x and later.
+ (reported by EGLadona and dmfalke, issue #1250, pull request #1285)
+
+ - Added a detach shim to fix support for jQuery versions earlier than 1.4.x.
+ (reported by ngavard, issue #1240, pull request #1286)
+
+ - Fixed a rare 'Uncaught TypeError' when using the resize plugin in IE 7/8.
+ (reported by tleish, issue #1265, pull request #1289)
+
+ - Fixed zoom constraints to apply only in the direction of the zoom.
+ (patch by Neil Katin, issue #1204, pull request #1205)
+
+ - Markings lines are no longer blurry when drawn on pixel boundaries.
+ (reported by btccointicker and Rouillard, issue #1210)
+
+ - Don't discard original pie data-series values when combining slices.
+ (patch by Phil Tsarik, pull request #1238)
+
+ - Fixed broken auto-scale behavior when using deprecated [x|y]2axis options.
+ (reported by jorese, issue #1228, pull request #1284)
+
+ - Exposed the dateGenerator function on the plot object, as it used to be
+ before time-mode was moved into a separate plugin.
+ (patch by Paolo Valleri, pull request #1028)
+
+
+## Flot 0.8.2 ##
+
+### Changes ###
+
+ - Added a plot.destroy method as a way to free memory when emptying the plot
+ placeholder and then re-using it for some other purpose.
+ (patch by Thodoris Greasidis, issue #1129, pull request #1130)
+
+ - Added a table of contents and PLUGINS link to the API documentation.
+ (patches by Brian Peiris, pull requests #1064 and #1127)
+
+ - Added Ruby code examples for time conversion.
+ (patch by Mike Połtyn, pull request #1182)
+
+ - Minor improvements to API.md and README.md.
+ (patches by Patrik Ragnarsson, pull requests #1085 and #1086)
+
+ - Updated inlined jQuery Resize to the latest version to fix errors.
+ (reported by Matthew Sabol and sloker, issues #997 ad #1081)
+
+### Bug fixes ###
+
+ - Fixed an unexpected change in behavior that resulted in duplicate tick
+ labels when using a plugin, like flot-tickrotor, that overrode tick labels.
+ (patch by Mark Cote, pull request #1091)
+
+ - Fixed a regression from 0.7 where axis labels were given the wrong width,
+ causing them to overlap at certain scales and ignore the labelWidth option.
+ (patch by Benjamin Gram, pull request #1177)
+
+ - Fixed a bug where the second axis in an xaxes/yaxes array incorrectly had
+ its 'innermost' property set to false or undefined, even if it was on the
+ other side of the plot from the first axis. This resulted in the axis bar
+ being visible when it shouldn't have been, which was especially obvious
+ when the grid had a left/right border width of zero.
+ (reported by Teq1, fix researched by ryleyb, issue #1056)
+
+ - Fixed an error when using a placeholder that has no font-size property.
+ (patch by Craig Oldford, pull request #1135)
+
+ - Fixed a regression from 0.7 where nulls at the end of a series were ignored
+ for purposes of determing the range of the x-axis.
+ (reported by Munsifali Rashid, issue #1095)
+
+ - If a font size is provided, base the default lineHeight on that size rather
+ that the font size of the plot placeholder, which may be very different.
+ (reported by Daniel Hoffmann Bernardes, issue #1131, pull request #1199)
+
+ - Fix broken highlighting for right-aligned bars.
+ (reported by BeWiBu and Mihai Stanciu, issues #975 and #1093, with further
+ assistance by Eric Byers, pull request #1120)
+
+ - Prevent white circles from sometimes showing up inside of pie charts.
+ (reported by Pierre Dubois and Jack Klink, issues #1128 and #1073)
+
+ - Label formatting no longer breaks when a page contains multiple pie charts.
+ (reported by Brend Wanders, issue #1055)
+
+ - When using multiple axes on opposite sides of the plot, the innermost axis
+ coming later in the list no longer has its bar drawn incorrectly.
+ (reported by ryleyb, issue #1056)
+
+ - When removing series labels and redrawing the plot, the legend now updates
+ correctly even when using an external container.
+ (patch by Luis Silva, issue #1159, pull request #1160)
+
+ - The pie plugin no longer ignores the value of the left offset option.
+ (reported by melanker, issue #1136)
+
+ - Fixed a regression from 0.7, where extra padding was added unnecessarily to
+ sides of the plot where there was no last tick label.
+ (reported by sknob001, issue #1048, pull request #1200)
+
+ - Fixed incorrect tooltip behavior in the interacting example.
+ (patch by cleroux, issue #686, pull request #1074)
+
+ - Fixed an error in CSS color extraction with elements outside the DOM.
+ (patch by execjosh, pull request #1084)
+
+ - Fixed :not selector error when using jQuery without Sizzle.
+ (patch by Anthony Ryan, pull request #1180)
+
+ - Worked around a browser issue that caused bars to appear un-filled.
+ (reported by irbian, issue #915)
+
+## Flot 0.8.1 ##
+
+### Bug fixes ###
+
+ - Fixed a regression in the time plugin, introduced in 0.8, that caused dates
+ to align to the minute rather than to the highest appropriate unit. This
+ caused many x-axes in 0.8 to have different ticks than they did in 0.7.
+ (reported by Tom Sheppard, patch by Daniel Shapiro, issue #1017, pull
+ request #1023)
+
+ - Fixed a regression in text rendering, introduced in 0.8, that caused axis
+ labels with the same text as another label on the same axis to disappear.
+ More generally, it's again possible to have the same text in two locations.
+ (issue #1032)
+
+ - Fixed a regression in text rendering, introduced in 0.8, where axis labels
+ were no longer assigned an explicit width, and their text could not wrap.
+ (reported by sabregreen, issue #1019)
+
+ - Fixed a regression in the pie plugin, introduced in 0.8, that prevented it
+ from accepting data in the format '[[x, y]]'.
+ (patch by Nicolas Morel, pull request #1024)
+
+ - The 'zero' series option and 'autoscale' format option are no longer
+ ignored when the series contains a null value.
+ (reported by Daniel Shapiro, issue #1033)
+
+ - Avoid triggering the time-mode plugin exception when there are zero series.
+ (reported by Daniel Rothig, patch by Mark Raymond, issue #1016)
+
+ - When a custom color palette has fewer colors than the default palette, Flot
+ no longer fills out the colors with the remainder of the default.
+ (patch by goorpy, issue #1031, pull request #1034)
+
+ - Fixed missing update for bar highlights after a zoom or other redraw.
+ (reported by Paolo Valleri, issue #1030)
+
+ - Fixed compatibility with jQuery versions earlier than 1.7.
+ (patch by Lee Willis, issue #1027, pull request #1027)
+
+ - The mouse wheel no longer scrolls the page when using the navigate plugin.
+ (patch by vird, pull request #1020)
+
+ - Fixed missing semicolons in the core library.
+ (reported by Michal Zglinski)
+
+
+## Flot 0.8.0 ##
+
+### API changes ###
+
+Support for time series has been moved into a plugin, jquery.flot.time.js.
+This results in less code if time series are not used. The functionality
+remains the same (plus timezone support, as described below); however, the
+plugin must be included if axis.mode is set to "time".
+
+When the axis mode is "time", the axis option "timezone" can be set to null,
+"browser", or a particular timezone (e.g. "America/New_York") to control how
+the dates are displayed. If null, the dates are displayed as UTC. If
+"browser", the dates are displayed in the time zone of the user's browser.
+
+Date/time formatting has changed and now follows a proper subset of the
+standard strftime specifiers, plus one nonstandard specifier for quarters.
+Additionally, if a strftime function is found in the Date object's prototype,
+it will be used instead of the built-in formatter.
+
+Axis tick labels now use the class 'flot-tick-label' instead of 'tickLabel'.
+The text containers for each axis now use the classes 'flot-[x|y]-axis' and
+'flot-[x|y]#-axis' instead of '[x|y]Axis' and '[x|y]#Axis'. For compatibility
+with Flot 0.7 and earlier text will continue to use the old classes as well,
+but they are considered deprecated and will be removed in a future version.
+
+In previous versions the axis 'color' option was used to set the color of tick
+marks and their label text. It now controls the color of the axis line, which
+previously could not be changed separately, and continues to act as a default
+for the tick-mark color. The color of tick label text is now set either by
+overriding the 'flot-tick-label' CSS rule or via the axis 'font' option.
+
+A new plugin, jquery.flot.canvas.js, allows axis tick labels to be rendered
+directly to the canvas, rather than using HTML elements. This feature can be
+toggled with a simple option, making it easy to create interactive plots in the
+browser using HTML, then re-render them to canvas for export as an image.
+
+The plugin tries to remain as faithful as possible to the original HTML render,
+and goes so far as to automatically extract styles from CSS, to avoid having to
+provide a separate set of styles when rendering to canvas. Due to limitations
+of the canvas text API, the plugin cannot reproduce certain features, including
+HTML markup embedded in labels, and advanced text styles such as 'em' units.
+
+The plugin requires support for canvas text, which may not be present in some
+older browsers, even if they support the canvas tag itself. To use the plugin
+with these browsers try using a shim such as canvas-text or FlashCanvas.
+
+The base and overlay canvas are now using the CSS classes "flot-base" and
+"flot-overlay" to prevent accidental clashes (issue 540).
+
+### Changes ###
+
+ - Addition of nonstandard %q specifier to date/time formatting. (patch
+ by risicle, issue 49)
+
+ - Date/time formatting follows proper subset of strftime specifiers, and
+ support added for Date.prototype.strftime, if found. (patch by Mark Cote,
+ issues 419 and 558)
+
+ - Fixed display of year ticks. (patch by Mark Cote, issue 195)
+
+ - Support for time series moved to plugin. (patch by Mark Cote)
+
+ - Display time series in different time zones. (patch by Knut Forkalsrud,
+ issue 141)
+
+ - Added a canvas plugin to enable rendering axis tick labels to the canvas.
+ (sponsored by YCharts.com, implementation by Ole Laursen and David Schnur)
+
+ - Support for setting the interval between redraws of the overlay canvas with
+ redrawOverlayInterval. (suggested in issue 185)
+
+ - Support for multiple thresholds in thresholds plugin. (patch by Arnaud
+ Bellec, issue 523)
+
+ - Support for plotting categories/textual data directly with new categories
+ plugin.
+
+ - Tick generators now get the whole axis rather than just min/max.
+
+ - Added processOffset and drawBackground hooks. (suggested in issue 639)
+
+ - Added a grid "margin" option to set the space between the canvas edge and
+ the grid.
+
+ - Prevent the pie example page from generating single-slice pies. (patch by
+ Shane Reustle)
+
+ - In addition to "left" and "center", bars now recognize "right" as an
+ alignment option. (patch by Michael Mayer, issue 520)
+
+ - Switched from toFixed to a much faster default tickFormatter. (patch by
+ Clemens Stolle)
+
+ - Added to a more helpful error when using a time-mode axis without including
+ the flot.time plugin. (patch by Yael Elmatad)
+
+ - Added a legend "sorted" option to control sorting of legend entries
+ independent of their series order. (patch by Tom Cleaveland)
+
+ - Added a series "highlightColor" option to control the color of the
+ translucent overlay that identifies the dataset when the mouse hovers over
+ it. (patch by Eric Wendelin and Nate Abele, issues 168 and 299)
+
+ - Added a plugin jquery.flot.errorbars, with an accompanying example, that
+ adds the ability to plot error bars, commonly used in many kinds of
+ statistical data visualizations. (patch by Rui Pereira, issue 215)
+
+ - The legend now omits entries whose labelFormatter returns null. (patch by
+ Tom Cleaveland, Christopher Lambert, and Simon Strandgaard)
+
+ - Added support for high pixel density (retina) displays, resulting in much
+ crisper charts on such devices. (patch by Olivier Guerriat, additional
+ fixes by Julien Thomas, maimairel, and Lau Bech Lauritzen)
+
+ - Added the ability to control pie shadow position and alpha via a new pie
+ 'shadow' option. (patch by Julien Thomas, pull request #78)
+
+ - Added the ability to set width and color for individual sides of the grid.
+ (patch by Ara Anjargolian, additional fixes by Karl Swedberg, pull requests #855
+ and #880)
+
+ - The selection plugin's getSelection now returns null when the selection
+ has been cleared. (patch by Nick Campbell, pull request #852)
+
+ - Added a new option called 'zero' to bars and filled lines series, to control
+ whether the y-axis minimum is scaled to fit the data or set to zero.
+ (patch by David Schnur, issues #316, #529, and #856, pull request #911)
+
+ - The plot function is now also a jQuery chainable property.
+ (patch by David Schnur, issues #734 and #816, pull request #953)
+
+ - When only a single pie slice is beneath the combine threshold it is no longer
+ replaced by an 'other' slice. (suggested by Devin Bayer, issue #638)
+
+ - Added lineJoin and minSize options to the selection plugin to control the
+ corner style and minimum size of the selection, respectively.
+ (patch by Ruth Linehan, pull request #963)
+
+### Bug fixes ###
+
+ - Fix problem with null values and pie plugin. (patch by gcruxifix,
+ issue 500)
+
+ - Fix problem with threshold plugin and bars. (based on patch by
+ kaarlenkaski, issue 348)
+
+ - Fix axis box calculations so the boxes include the outermost part of the
+ labels too.
+
+ - Fix problem with event clicking and hovering in IE 8 by updating Excanvas
+ and removing previous work-around. (test case by Ara Anjargolian)
+
+ - Fix issues with blurry 1px border when some measures aren't integer.
+ (reported by Ara Anjargolian)
+
+ - Fix bug with formats in the data processor. (reported by Peter Hull,
+ issue 534)
+
+ - Prevent i from being declared global in extractRange. (reported by
+ Alexander Obukhov, issue 627)
+
+ - Throw errors in a more cross-browser-compatible manner. (patch by
+ Eddie Kay)
+
+ - Prevent pie slice outlines from being drawn when the stroke width is zero.
+ (reported by Chris Minett, issue 585)
+
+ - Updated the navigate plugin's inline copy of jquery.mousewheel to fix
+ Webkit zoom problems. (reported by Hau Nguyen, issue 685)
+
+ - Axis labels no longer appear as decimals rather than integers in certain
+ cases. (patch by Clemens Stolle, issue 541)
+
+ - Automatic color generation no longer produces only whites and blacks when
+ there are many series. (patch by David Schnur and Tom Cleaveland)
+
+ - Fixed an error when custom tick labels weren't provided as strings. (patch
+ by Shad Downey)
+
+ - Prevented the local insertSteps and fmt variables from becoming global.
+ (first reported by Marc Bennewitz and Szymon Barglowski, patch by Nick
+ Campbell, issues #825 and #831, pull request #851)
+
+ - Prevented several threshold plugin variables from becoming global. (patch
+ by Lasse Dahl Ebert)
+
+ - Fixed various jQuery 1.8 compatibility issues. (issues #814 and #819,
+ pull request #877)
+
+ - Pie charts with a slice equal to or approaching 100% of the pie no longer
+ appear invisible. (patch by David Schnur, issues #444, #658, #726, #824
+ and #850, pull request #879)
+
+ - Prevented several local variables from becoming global. (patch by aaa707)
+
+ - Ensure that the overlay and primary canvases remain aligned. (issue #670,
+ pull request #901)
+
+ - Added support for jQuery 1.9 by removing and replacing uses of $.browser.
+ (analysis and patch by Anthony Ryan, pull request #905)
+
+ - Pie charts no longer disappear when redrawn during a resize or update.
+ (reported by Julien Bec, issue #656, pull request #910)
+
+ - Avoided floating-point precision errors when calculating pie percentages.
+ (patch by James Ward, pull request #918)
+
+ - Fixed compatibility with jQuery 1.2.6, which has no 'mouseleave' shortcut.
+ (reported by Bevan, original pull request #920, replaced by direct patch)
+
+ - Fixed sub-pixel rendering issues with crosshair and selection lines.
+ (patches by alanayoub and Daniel Shapiro, pull requests #17 and #925)
+
+ - Fixed rendering issues when using the threshold plugin with several series.
+ (patch by Ivan Novikov, pull request #934)
+
+ - Pie charts no longer disappear when redrawn after calling setData().
+ (reported by zengge1984 and pareeohnos, issues #810 and #945)
+
+ - Added a work-around for the problem where points with a lineWidth of zero
+ still showed up with a visible line. (reported by SalvoSav, issue #842,
+ patch by Jamie Hamel-Smith, pull request #937)
+
+ - Pie charts now accept values in string form, like other plot types.
+ (reported by laerdal.no, issue #534)
+
+ - Avoid rounding errors in the threshold plugin.
+ (reported by jerikojerk, issue #895)
+
+ - Fixed an error when using the navigate plugin with jQuery 1.9.x or later.
+ (reported by Paolo Valleri, issue #964)
+
+ - Fixed inconsistencies between the highlight and unhighlight functions.
+ (reported by djamshed, issue #987)
+
+ - Fixed recalculation of tickSize and tickDecimals on calls to setupGrid.
+ (patch by thecountofzero, pull request #861, issues #860, #1000)
+
+
+## Flot 0.7 ##
+
+### API changes ###
+
+Multiple axes support. Code using dual axes should be changed from using
+x2axis/y2axis in the options to using an array (although backwards-
+compatibility hooks are in place). For instance,
+
+```js
+{
+ xaxis: { ... }, x2axis: { ... },
+ yaxis: { ... }, y2axis: { ... }
+}
+```
+
+becomes
+
+```js
+{
+ xaxes: [ { ... }, { ... } ],
+ yaxes: [ { ... }, { ... } ]
+}
+```
+
+Note that if you're just using one axis, continue to use the xaxis/yaxis
+directly (it now sets the default settings for the arrays). Plugins touching
+the axes must be ported to take the extra axes into account, check the source
+to see some examples.
+
+A related change is that the visibility of axes is now auto-detected. So if
+you were relying on an axis to show up even without any data in the chart, you
+now need to set the axis "show" option explicitly.
+
+"tickColor" on the grid options is now deprecated in favour of a corresponding
+option on the axes, so:
+
+```js
+{ grid: { tickColor: "#000" }}
+```
+
+becomes
+
+```js
+{ xaxis: { tickColor: "#000"}, yaxis: { tickColor: "#000"} }
+```
+
+But if you just configure a base color Flot will now autogenerate a tick color
+by adding transparency. Backwards-compatibility hooks are in place.
+
+Final note: now that IE 9 is coming out with canvas support, you may want to
+adapt the excanvas include to skip loading it in IE 9 (the examples have been
+adapted thanks to Ryley Breiddal). An alternative to excanvas using Flash has
+also surfaced, if your graphs are slow in IE, you may want to give it a spin:
+
+ http://code.google.com/p/flashcanvas/
+
+### Changes ###
+
+ - Support for specifying a bottom for each point for line charts when filling
+ them, this means that an arbitrary bottom can be used instead of just the x
+ axis. (based on patches patiently provided by Roman V. Prikhodchenko)
+
+ - New fillbetween plugin that can compute a bottom for a series from another
+ series, useful for filling areas between lines.
+
+ See new example percentiles.html for a use case.
+
+ - More predictable handling of gaps for the stacking plugin, now all
+ undefined ranges are skipped.
+
+ - Stacking plugin can stack horizontal bar charts.
+
+ - Navigate plugin now redraws the plot while panning instead of only after
+ the fact. (raised by lastthemy, issue 235)
+
+ Can be disabled by setting the pan.frameRate option to null.
+
+ - Date formatter now accepts %0m and %0d to get a zero-padded month or day.
+ (issue raised by Maximillian Dornseif)
+
+ - Revamped internals to support an unlimited number of axes, not just dual.
+ (sponsored by Flight Data Services, www.flightdataservices.com)
+
+ - New setting on axes, "tickLength", to control the size of ticks or turn
+ them off without turning off the labels.
+
+ - Axis labels are now put in container divs with classes, for instance labels
+ in the x axes can be reached via ".xAxis .tickLabel".
+
+ - Support for setting the color of an axis. (sponsored by Flight Data
+ Services, www.flightdataservices.com)
+
+ - Tick color is now auto-generated as the base color with some transparency,
+ unless you override it.
+
+ - Support for aligning ticks in the axes with "alignTicksWithAxis" to ensure
+ that they appear next to each other rather than in between, at the expense
+ of possibly awkward tick steps. (sponsored by Flight Data Services,
+ www.flightdataservices.com)
+
+ - Support for customizing the point type through a callback when plotting
+ points and new symbol plugin with some predefined point types. (sponsored
+ by Utility Data Corporation)
+
+ - Resize plugin for automatically redrawing when the placeholder changes
+ size, e.g. on window resizes. (sponsored by Novus Partners)
+
+ A resize() method has been added to plot object facilitate this.
+
+ - Support Infinity/-Infinity for plotting asymptotes by hacking it into
+ +/-Number.MAX_VALUE. (reported by rabaea.mircea)
+
+ - Support for restricting navigate plugin to not pan/zoom an axis. (based on
+ patch by kkaefer)
+
+ - Support for providing the drag cursor for the navigate plugin as an option.
+ (based on patch by Kelly T. Moore)
+
+ - Options for controlling whether an axis is shown or not (suggestion by Timo
+ Tuominen) and whether to reserve space for it even if it isn't shown.
+
+ - New attribute $.plot.version with the Flot version as a string.
+
+ - The version comment is now included in the minified jquery.flot.min.js.
+
+ - New options.grid.minBorderMargin for adjusting the minimum margin provided
+ around the border (based on patch by corani, issue 188).
+
+ - Refactor replot behaviour so Flot tries to reuse the existing canvas,
+ adding shutdown() methods to the plot. (based on patch by Ryley Breiddal,
+ issue 269)
+
+ This prevents a memory leak in Chrome and hopefully makes replotting faster
+ for those who are using $.plot instead of .setData()/.draw(). Also update
+ jQuery to 1.5.1 to prevent IE leaks fixed in jQuery.
+
+ - New real-time line chart example.
+
+ - New hooks: drawSeries, shutdown.
+
+### Bug fixes ###
+
+ - Fixed problem with findNearbyItem and bars on top of each other. (reported
+ by ragingchikn, issue 242)
+
+ - Fixed problem with ticks and the border. (based on patch from
+ ultimatehustler69, issue 236)
+
+ - Fixed problem with plugins adding options to the series objects.
+
+ - Fixed a problem introduced in 0.6 with specifying a gradient with:
+
+ ```{brightness: x, opacity: y }```
+
+ - Don't use $.browser.msie, check for getContext on the created canvas element
+ instead and try to use excanvas if it's not found.
+
+ Fixes IE 9 compatibility.
+
+ - highlight(s, index) was looking up the point in the original s.data instead
+ of in the computed datapoints array, which breaks with plugins that modify
+ the datapoints, such as the stacking plugin. (reported by curlypaul924,
+ issue 316)
+
+ - More robust handling of axis from data passed in from getData(). (reported)
+ by Morgan)
+
+ - Fixed problem with turning off bar outline. (fix by Jordi Castells,
+ issue 253)
+
+ - Check the selection passed into setSelection in the selection
+ plugin, to guard against errors when synchronizing plots (fix by Lau
+ Bech Lauritzen).
+
+ - Fix bug in crosshair code with mouseout resetting the crosshair even
+ if it is locked (fix by Lau Bech Lauritzen and Banko Adam).
+
+ - Fix bug with points plotting using line width from lines rather than
+ points.
+
+ - Fix bug with passing non-array 0 data (for plugins that don't expect
+ arrays, patch by vpapp1).
+
+ - Fix errors in JSON in examples so they work with jQuery 1.4.2
+ (fix reported by honestbleeps, issue 357).
+
+ - Fix bug with tooltip in interacting.html, this makes the tooltip
+ much smoother (fix by bdkahn). Fix related bug inside highlighting
+ handler in Flot.
+
+ - Use closure trick to make inline colorhelpers plugin respect
+ jQuery.noConflict(true), renaming the global jQuery object (reported
+ by Nick Stielau).
+
+ - Listen for mouseleave events and fire a plothover event with empty
+ item when it occurs to drop highlights when the mouse leaves the
+ plot (reported by by outspirit).
+
+ - Fix bug with using aboveData with a background (reported by
+ amitayd).
+
+ - Fix possible excanvas leak (report and suggested fix by tom9729).
+
+ - Fix bug with backwards compatibility for shadowSize = 0 (report and
+ suggested fix by aspinak).
+
+ - Adapt examples to skip loading excanvas (fix by Ryley Breiddal).
+
+ - Fix bug that prevent a simple f(x) = -x transform from working
+ correctly (fix by Mike, issue 263).
+
+ - Fix bug in restoring cursor in navigate plugin (reported by Matteo
+ Gattanini, issue 395).
+
+ - Fix bug in picking items when transform/inverseTransform is in use
+ (reported by Ofri Raviv, and patches and analysis by Jan and Tom
+ Paton, issue 334 and 467).
+
+ - Fix problem with unaligned ticks and hover/click events caused by
+ padding on the placeholder by hardcoding the placeholder padding to
+ 0 (reported by adityadineshsaxena, Matt Sommer, Daniel Atos and some
+ other people, issue 301).
+
+ - Update colorhelpers plugin to avoid dying when trying to parse an
+ invalid string (reported by cadavor, issue 483).
+
+
+
+## Flot 0.6 ##
+
+### API changes ###
+
+Selection support has been moved to a plugin. Thus if you're passing
+selection: { mode: something }, you MUST include the file
+jquery.flot.selection.js after jquery.flot.js. This reduces the size of
+base Flot and makes it easier to customize the selection as well as
+improving code clarity. The change is based on a patch from andershol.
+
+In the global options specified in the $.plot command, "lines", "points",
+"bars" and "shadowSize" have been moved to a sub-object called "series":
+
+```js
+$.plot(placeholder, data, { lines: { show: true }})
+```
+
+should be changed to
+
+```js
+ $.plot(placeholder, data, { series: { lines: { show: true }}})
+```
+
+All future series-specific options will go into this sub-object to
+simplify plugin writing. Backward-compatibility code is in place, so
+old code should not break.
+
+"plothover" no longer provides the original data point, but instead a
+normalized one, since there may be no corresponding original point.
+
+Due to a bug in previous versions of jQuery, you now need at least
+jQuery 1.2.6. But if you can, try jQuery 1.3.2 as it got some improvements
+in event handling speed.
+
+## Changes ##
+
+ - Added support for disabling interactivity for specific data series.
+ (request from Ronald Schouten and Steve Upton)
+
+ - Flot now calls $() on the placeholder and optional legend container passed
+ in so you can specify DOM elements or CSS expressions to make it easier to
+ use Flot with libraries like Prototype or Mootools or through raw JSON from
+ Ajax responses.
+
+ - A new "plotselecting" event is now emitted while the user is making a
+ selection.
+
+ - The "plothover" event is now emitted immediately instead of at most 10
+ times per second, you'll have to put in a setTimeout yourself if you're
+ doing something really expensive on this event.
+
+ - The built-in date formatter can now be accessed as $.plot.formatDate(...)
+ (suggestion by Matt Manela) and even replaced.
+
+ - Added "borderColor" option to the grid. (patches from Amaury Chamayou and
+ Mike R. Williamson)
+
+ - Added support for gradient backgrounds for the grid. (based on patch from
+ Amaury Chamayou, issue 90)
+
+ The "setting options" example provides a demonstration.
+
+ - Gradient bars. (suggestion by stefpet)
+
+ - Added a "plotunselected" event which is triggered when the selection is
+ removed, see "selection" example. (suggestion by Meda Ugo)
+
+ - The option legend.margin can now specify horizontal and vertical margins
+ independently. (suggestion by someone who's annoyed)
+
+ - Data passed into Flot is now copied to a new canonical format to enable
+ further processing before it hits the drawing routines. As a side-effect,
+ this should make Flot more robust in the face of bad data. (issue 112)
+
+ - Step-wise charting: line charts have a new option "steps" that when set to
+ true connects the points with horizontal/vertical steps instead of diagonal
+ lines.
+
+ - The legend labelFormatter now passes the series in addition to just the
+ label. (suggestion by Vincent Lemeltier)
+
+ - Horizontal bars (based on patch by Jason LeBrun).
+
+ - Support for partial bars by specifying a third coordinate, i.e. they don't
+ have to start from the axis. This can be used to make stacked bars.
+
+ - New option to disable the (grid.show).
+
+ - Added pointOffset method for converting a point in data space to an offset
+ within the placeholder.
+
+ - Plugin system: register an init method in the $.flot.plugins array to get
+ started, see PLUGINS.txt for details on how to write plugins (it's easy).
+ There are also some extra methods to enable access to internal state.
+
+ - Hooks: you can register functions that are called while Flot is crunching
+ the data and doing the plot. This can be used to modify Flot without
+ changing the source, useful for writing plugins. Some hooks are defined,
+ more are likely to come.
+
+ - Threshold plugin: you can set a threshold and a color, and the data points
+ below that threshold will then get the color. Useful for marking data
+ below 0, for instance.
+
+ - Stack plugin: you can specify a stack key for each series to have them
+ summed. This is useful for drawing additive/cumulative graphs with bars and
+ (currently unfilled) lines.
+
+ - Crosshairs plugin: trace the mouse position on the axes, enable with
+ crosshair: { mode: "x"} (see the new tracking example for a use).
+
+ - Image plugin: plot prerendered images.
+
+ - Navigation plugin for panning and zooming a plot.
+
+ - More configurable grid.
+
+ - Axis transformation support, useful for non-linear plots, e.g. log axes and
+ compressed time axes (like omitting weekends).
+
+ - Support for twelve-hour date formatting (patch by Forrest Aldridge).
+
+ - The color parsing code in Flot has been cleaned up and split out so it's
+ now available as a separate jQuery plugin. It's included inline in the Flot
+ source to make dependency managing easier. This also makes it really easy
+ to use the color helpers in Flot plugins.
+
+## Bug fixes ##
+
+ - Fixed two corner-case bugs when drawing filled curves. (report and analysis
+ by Joshua Varner)
+
+ - Fix auto-adjustment code when setting min to 0 for an axis where the
+ dataset is completely flat on that axis. (report by chovy)
+
+ - Fixed a bug with passing in data from getData to setData when the secondary
+ axes are used. (reported by nperelman, issue 65)
+
+ - Fixed so that it is possible to turn lines off when no other chart type is
+ shown (based on problem reported by Glenn Vanderburg), and fixed so that
+ setting lineWidth to 0 also hides the shadow. (based on problem reported by
+ Sergio Nunes)
+
+ - Updated mousemove position expression to the latest from jQuery. (reported
+ by meyuchas)
+
+ - Use CSS borders instead of background in legend. (issues 25 and 45)
+
+ - Explicitly convert axis min/max to numbers.
+
+ - Fixed a bug with drawing marking lines with different colors. (reported by
+ Khurram)
+
+ - Fixed a bug with returning y2 values in the selection event. (fix by
+ exists, issue 75)
+
+ - Only set position relative on placeholder if it hasn't already a position
+ different from static. (reported by kyberneticist, issue 95)
+
+ - Don't round markings to prevent sub-pixel problems. (reported by
+ Dan Lipsitt)
+
+ - Make the grid border act similarly to a regular CSS border, i.e. prevent
+ it from overlapping the plot itself. This also fixes a problem with anti-
+ aliasing when the width is 1 pixel. (reported by Anthony Ettinger)
+
+ - Imported version 3 of excanvas and fixed two issues with the newer version.
+ Hopefully, this will make Flot work with IE8. (nudge by Fabien Menager,
+ further analysis by Booink, issue 133)
+
+ - Changed the shadow code for lines to hopefully look a bit better with
+ vertical lines.
+
+ - Round tick positions to avoid possible problems with fractions. (suggestion
+ by Fred, issue 130)
+
+ - Made the heuristic for determining how many ticks to aim for a bit smarter.
+
+ - Fix for uneven axis margins (report and patch by Paul Kienzle) and snapping
+ to ticks. (report and patch by lifthrasiir)
+
+ - Fixed bug with slicing in findNearbyItems. (patch by zollman)
+
+ - Make heuristic for x axis label widths more dynamic. (patch by
+ rickinhethuis)
+
+ - Make sure points on top take precedence when finding nearby points when
+ hovering. (reported by didroe, issue 224)
+
+
+
+## Flot 0.5 ##
+
+Timestamps are now in UTC. Also "selected" event -> becomes "plotselected"
+with new data, the parameters for setSelection are now different (but
+backwards compatibility hooks are in place), coloredAreas becomes markings
+with a new interface (but backwards compatibility hooks are in place).
+
+### API changes ###
+
+Timestamps in time mode are now displayed according to UTC instead of the time
+zone of the visitor. This affects the way the timestamps should be input;
+you'll probably have to offset the timestamps according to your local time
+zone. It also affects any custom date handling code (which basically now
+should use the equivalent UTC date mehods, e.g. .setUTCMonth() instead of
+.setMonth().
+
+Markings, previously coloredAreas, are now specified as ranges on the axes,
+like ```{ xaxis: { from: 0, to: 10 }}```. Furthermore with markings you can
+now draw horizontal/vertical lines by setting from and to to the same
+coordinate. (idea from line support patch by by Ryan Funduk)
+
+Interactivity: added a new "plothover" event and this and the "plotclick"
+event now returns the closest data item (based on patch by /david, patch by
+Mark Byers for bar support). See the revamped "interacting with the data"
+example for some hints on what you can do.
+
+Highlighting: you can now highlight points and datapoints are autohighlighted
+when you hover over them (if hovering is turned on).
+
+Support for dual axis has been added (based on patch by someone who's annoyed
+and /david). For each data series you can specify which axes it belongs to,
+and there are two more axes, x2axis and y2axis, to customize. This affects the
+"selected" event which has been renamed to "plotselected" and spews out
+```{ xaxis: { from: -10, to: 20 } ... },``` setSelection in which the
+parameters are on a new form (backwards compatible hooks are in place so old
+code shouldn't break) and markings (formerly coloredAreas).
+
+## Changes ##
+
+ - Added support for specifying the size of tick labels (axis.labelWidth,
+ axis.labelHeight). Useful for specifying a max label size to keep multiple
+ plots aligned.
+
+ - The "fill" option can now be a number that specifies the opacity of the
+ fill.
+
+ - You can now specify a coordinate as null (like [2, null]) and Flot will
+ take the other coordinate into account when scaling the axes. (based on
+ patch by joebno)
+
+ - New option for bars "align". Set it to "center" to center the bars on the
+ value they represent.
+
+ - setSelection now takes a second parameter which you can use to prevent the
+ method from firing the "plotselected" handler.
+
+ - Improved the handling of axis auto-scaling with bars.
+
+## Bug fixes ##
+
+ - Fixed a bug in calculating spacing around the plot. (reported by
+ timothytoe)
+
+ - Fixed a bug in finding max values for all-negative data sets.
+
+ - Prevent the possibility of eternal looping in tick calculations.
+
+ - Fixed a bug when borderWidth is set to 0. (reported by Rob/sanchothefat)
+
+ - Fixed a bug with drawing bars extending below 0. (reported by James Hewitt,
+ patch by Ryan Funduk).
+
+ - Fixed a bug with line widths of bars. (reported by MikeM)
+
+ - Fixed a bug with 'nw' and 'sw' legend positions.
+
+ - Fixed a bug with multi-line x-axis tick labels. (reported by Luca Ciano,
+ IE-fix help by Savage Zhang)
+
+ - Using the "container" option in legend now overwrites the container element
+ instead of just appending to it, fixing the infinite legend bug. (reported
+ by several people, fix by Brad Dewey)
+
+
+
+## Flot 0.4 ##
+
+### API changes ###
+
+Deprecated axis.noTicks in favor of just specifying the number as axis.ticks.
+So ```xaxis: { noTicks: 10 }``` becomes ```xaxis: { ticks: 10 }```.
+
+Time series support. Specify axis.mode: "time", put in Javascript timestamps
+as data, and Flot will automatically spit out sensible ticks. Take a look at
+the two new examples. The format can be customized with axis.timeformat and
+axis.monthNames, or if that fails with axis.tickFormatter.
+
+Support for colored background areas via grid.coloredAreas. Specify an array
+of { x1, y1, x2, y2 } objects or a function that returns these given
+{ xmin, xmax, ymin, ymax }.
+
+More members on the plot object (report by Chris Davies and others).
+"getData" for inspecting the assigned settings on data series (e.g. color) and
+"setData", "setupGrid" and "draw" for updating the contents without a total
+replot.
+
+The default number of ticks to aim for is now dependent on the size of the
+plot in pixels. Support for customizing tick interval sizes directly with
+axis.minTickSize and axis.tickSize.
+
+Cleaned up the automatic axis scaling algorithm and fixed how it interacts
+with ticks. Also fixed a couple of tick-related corner case bugs (one reported
+by mainstreetmark, another reported by timothytoe).
+
+The option axis.tickFormatter now takes a function with two parameters, the
+second parameter is an optional object with information about the axis. It has
+min, max, tickDecimals, tickSize.
+
+## Changes ##
+
+ - Added support for segmented lines. (based on patch from Michael MacDonald)
+
+ - Added support for ignoring null and bad values. (suggestion from Nick
+ Konidaris and joshwaihi)
+
+ - Added support for changing the border width. (thanks to joebno and safoo)
+
+ - Label colors can be changed via CSS by selecting the tickLabel class.
+
+## Bug fixes ##
+
+ - Fixed a bug in handling single-item bar series. (reported by Emil Filipov)
+
+ - Fixed erratic behaviour when interacting with the plot with IE 7. (reported
+ by Lau Bech Lauritzen).
+
+ - Prevent IE/Safari text selection when selecting stuff on the canvas.
+
+
+
+## Flot 0.3 ##
+
+This is mostly a quick-fix release because jquery.js wasn't included in the
+previous zip/tarball.
+
+## Changes ##
+
+ - Include jquery.js in the zip/tarball.
+
+ - Support clicking on the plot. Turn it on with grid: { clickable: true },
+ then you get a "plotclick" event on the graph placeholder with the position
+ in units of the plot.
+
+## Bug fixes ##
+
+ - Fixed a bug in dealing with data where min = max. (thanks to Michael
+ Messinides)
+
+
+
+## Flot 0.2 ##
+
+The API should now be fully documented.
+
+### API changes ###
+
+Moved labelMargin option to grid from x/yaxis.
+
+## Changes ##
+
+ - Added support for putting a background behind the default legend. The
+ default is the partly transparent background color. Added backgroundColor
+ and backgroundOpacity to the legend options to control this.
+
+ - The ticks options can now be a callback function that takes one parameter,
+ an object with the attributes min and max. The function should return a
+ ticks array.
+
+ - Added labelFormatter option in legend, useful for turning the legend
+ labels into links.
+
+ - Reduced the size of the code. (patch by Guy Fraser)
+
+
+
+## Flot 0.1 ##
+
+First public release.
diff --git a/djangoproject/static/js/lib/jquery-flot/PLUGINS.md b/djangoproject/static/js/lib/jquery-flot/PLUGINS.md
new file mode 100644
index 000000000..b5bf30020
--- /dev/null
+++ b/djangoproject/static/js/lib/jquery-flot/PLUGINS.md
@@ -0,0 +1,143 @@
+## Writing plugins ##
+
+All you need to do to make a new plugin is creating an init function
+and a set of options (if needed), stuffing it into an object and
+putting it in the $.plot.plugins array. For example:
+
+```js
+function myCoolPluginInit(plot) {
+ plot.coolstring = "Hello!";
+};
+
+$.plot.plugins.push({ init: myCoolPluginInit, options: { ... } });
+
+// if $.plot is called, it will return a plot object with the
+// attribute "coolstring"
+```
+
+Now, given that the plugin might run in many different places, it's
+a good idea to avoid leaking names. The usual trick here is wrap the
+above lines in an anonymous function which is called immediately, like
+this: (function () { inner code ... })(). To make it even more robust
+in case $ is not bound to jQuery but some other Javascript library, we
+can write it as
+
+```js
+(function ($) {
+ // plugin definition
+ // ...
+})(jQuery);
+```
+
+There's a complete example below, but you should also check out the
+plugins bundled with Flot.
+
+
+## Complete example ##
+
+Here is a simple debug plugin which alerts each of the series in the
+plot. It has a single option that control whether it is enabled and
+how much info to output:
+
+```js
+(function ($) {
+ function init(plot) {
+ var debugLevel = 1;
+
+ function checkDebugEnabled(plot, options) {
+ if (options.debug) {
+ debugLevel = options.debug;
+ plot.hooks.processDatapoints.push(alertSeries);
+ }
+ }
+
+ function alertSeries(plot, series, datapoints) {
+ var msg = "series " + series.label;
+ if (debugLevel > 1) {
+ msg += " with " + series.data.length + " points";
+ alert(msg);
+ }
+ }
+
+ plot.hooks.processOptions.push(checkDebugEnabled);
+ }
+
+ var options = { debug: 0 };
+
+ $.plot.plugins.push({
+ init: init,
+ options: options,
+ name: "simpledebug",
+ version: "0.1"
+ });
+})(jQuery);
+```
+
+We also define "name" and "version". It's not used by Flot, but might
+be helpful for other plugins in resolving dependencies.
+
+Put the above in a file named "jquery.flot.debug.js", include it in an
+HTML page and then it can be used with:
+
+```js
+ $.plot($("#placeholder"), [...], { debug: 2 });
+```
+
+This simple plugin illustrates a couple of points:
+
+ - It uses the anonymous function trick to avoid name pollution.
+ - It can be enabled/disabled through an option.
+ - Variables in the init function can be used to store plot-specific
+ state between the hooks.
+
+The two last points are important because there may be multiple plots
+on the same page, and you'd want to make sure they are not mixed up.
+
+
+## Shutting down a plugin ##
+
+Each plot object has a shutdown hook which is run when plot.shutdown()
+is called. This usually mostly happens in case another plot is made on
+top of an existing one.
+
+The purpose of the hook is to give you a chance to unbind any event
+handlers you've registered and remove any extra DOM things you've
+inserted.
+
+The problem with event handlers is that you can have registered a
+handler which is run in some point in the future, e.g. with
+setTimeout(). Meanwhile, the plot may have been shutdown and removed,
+but because your event handler is still referencing it, it can't be
+garbage collected yet, and worse, if your handler eventually runs, it
+may overwrite stuff on a completely different plot.
+
+
+## Some hints on the options ##
+
+Plugins should always support appropriate options to enable/disable
+them because the plugin user may have several plots on the same page
+where only one should use the plugin. In most cases it's probably a
+good idea if the plugin is turned off rather than on per default, just
+like most of the powerful features in Flot.
+
+If the plugin needs options that are specific to each series, like the
+points or lines options in core Flot, you can put them in "series" in
+the options object, e.g.
+
+```js
+var options = {
+ series: {
+ downsample: {
+ algorithm: null,
+ maxpoints: 1000
+ }
+ }
+}
+```
+
+Then they will be copied by Flot into each series, providing default
+values in case none are specified.
+
+Think hard and long about naming the options. These names are going to
+be public API, and code is going to depend on them if the plugin is
+successful.
diff --git a/djangoproject/static/js/lib/jquery-flot/README.md b/djangoproject/static/js/lib/jquery-flot/README.md
new file mode 100644
index 000000000..a8f70640a
--- /dev/null
+++ b/djangoproject/static/js/lib/jquery-flot/README.md
@@ -0,0 +1,110 @@
+# Flot [![Build status](https://travis-ci.org/flot/flot.png)](https://travis-ci.org/flot/flot)
+
+## About ##
+
+Flot is a Javascript plotting library for jQuery.
+Read more at the website:
+
+Take a look at the the examples in examples/index.html; they should give a good
+impression of what Flot can do, and the source code of the examples is probably
+the fastest way to learn how to use Flot.
+
+
+## Installation ##
+
+Just include the Javascript file after you've included jQuery.
+
+Generally, all browsers that support the HTML5 canvas tag are
+supported.
+
+For support for Internet Explorer < 9, you can use [Excanvas]
+[excanvas], a canvas emulator; this is used in the examples bundled
+with Flot. You just include the excanvas script like this:
+
+```html
+
+```
+
+If it's not working on your development IE 6.0, check that it has
+support for VML which Excanvas is relying on. It appears that some
+stripped down versions used for test environments on virtual machines
+lack the VML support.
+
+You can also try using [Flashcanvas][flashcanvas], which uses Flash to
+do the emulation. Although Flash can be a bit slower to load than VML,
+if you've got a lot of points, the Flash version can be much faster
+overall. Flot contains some wrapper code for activating Excanvas which
+Flashcanvas is compatible with.
+
+You need at least jQuery 1.2.6, but try at least 1.3.2 for interactive
+charts because of performance improvements in event handling.
+
+
+## Basic usage ##
+
+Create a placeholder div to put the graph in:
+
+```html
+
+```
+
+You need to set the width and height of this div, otherwise the plot
+library doesn't know how to scale the graph. You can do it inline like
+this:
+
+```html
+
+```
+
+You can also do it with an external stylesheet. Make sure that the
+placeholder isn't within something with a display:none CSS property -
+in that case, Flot has trouble measuring label dimensions which
+results in garbled looks and might have trouble measuring the
+placeholder dimensions which is fatal (it'll throw an exception).
+
+Then when the div is ready in the DOM, which is usually on document
+ready, run the plot function:
+
+```js
+$.plot($("#placeholder"), data, options);
+```
+
+Here, data is an array of data series and options is an object with
+settings if you want to customize the plot. Take a look at the
+examples for some ideas of what to put in or look at the
+[API reference](API.md). Here's a quick example that'll draw a line
+from (0, 0) to (1, 1):
+
+```js
+$.plot($("#placeholder"), [ [[0, 0], [1, 1]] ], { yaxis: { max: 1 } });
+```
+
+The plot function immediately draws the chart and then returns a plot
+object with a couple of methods.
+
+
+## What's with the name? ##
+
+First: it's pronounced with a short o, like "plot". Not like "flawed".
+
+So "Flot" rhymes with "plot".
+
+And if you look up "flot" in a Danish-to-English dictionary, some of
+the words that come up are "good-looking", "attractive", "stylish",
+"smart", "impressive", "extravagant". One of the main goals with Flot
+is pretty looks.
+
+
+## Notes about the examples ##
+
+In order to have a useful, functional example of time-series plots using time
+zones, date.js from [timezone-js][timezone-js] (released under the Apache 2.0
+license) and the [Olson][olson] time zone database (released to the public
+domain) have been included in the examples directory. They are used in
+examples/axes-time-zones/index.html.
+
+
+[excanvas]: http://code.google.com/p/explorercanvas/
+[flashcanvas]: http://code.google.com/p/flashcanvas/
+[timezone-js]: https://github.com/mde/timezone-js
+[olson]: http://ftp.iana.org/time-zones
diff --git a/djangoproject/static/js/lib/jquery-flot/component.json b/djangoproject/static/js/lib/jquery-flot/component.json
new file mode 100644
index 000000000..596117235
--- /dev/null
+++ b/djangoproject/static/js/lib/jquery-flot/component.json
@@ -0,0 +1,8 @@
+{
+ "name": "Flot",
+ "version": "0.8.3",
+ "main": "jquery.flot.js",
+ "dependencies": {
+ "jquery": ">= 1.2.6"
+ }
+}
diff --git a/djangoproject/static/js/lib/jquery-flot/excanvas.js b/djangoproject/static/js/lib/jquery-flot/excanvas.js
new file mode 100644
index 000000000..70a8f25ca
--- /dev/null
+++ b/djangoproject/static/js/lib/jquery-flot/excanvas.js
@@ -0,0 +1,1428 @@
+// Copyright 2006 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+
+// Known Issues:
+//
+// * Patterns only support repeat.
+// * Radial gradient are not implemented. The VML version of these look very
+// different from the canvas one.
+// * Clipping paths are not implemented.
+// * Coordsize. The width and height attribute have higher priority than the
+// width and height style values which isn't correct.
+// * Painting mode isn't implemented.
+// * Canvas width/height should is using content-box by default. IE in
+// Quirks mode will draw the canvas using border-box. Either change your
+// doctype to HTML5
+// (http://www.whatwg.org/specs/web-apps/current-work/#the-doctype)
+// or use Box Sizing Behavior from WebFX
+// (http://webfx.eae.net/dhtml/boxsizing/boxsizing.html)
+// * Non uniform scaling does not correctly scale strokes.
+// * Filling very large shapes (above 5000 points) is buggy.
+// * Optimize. There is always room for speed improvements.
+
+// Only add this code if we do not already have a canvas implementation
+if (!document.createElement('canvas').getContext) {
+
+(function() {
+
+ // alias some functions to make (compiled) code shorter
+ var m = Math;
+ var mr = m.round;
+ var ms = m.sin;
+ var mc = m.cos;
+ var abs = m.abs;
+ var sqrt = m.sqrt;
+
+ // this is used for sub pixel precision
+ var Z = 10;
+ var Z2 = Z / 2;
+
+ var IE_VERSION = +navigator.userAgent.match(/MSIE ([\d.]+)?/)[1];
+
+ /**
+ * This funtion is assigned to the elements as element.getContext().
+ * @this {HTMLElement}
+ * @return {CanvasRenderingContext2D_}
+ */
+ function getContext() {
+ return this.context_ ||
+ (this.context_ = new CanvasRenderingContext2D_(this));
+ }
+
+ var slice = Array.prototype.slice;
+
+ /**
+ * Binds a function to an object. The returned function will always use the
+ * passed in {@code obj} as {@code this}.
+ *
+ * Example:
+ *
+ * g = bind(f, obj, a, b)
+ * g(c, d) // will do f.call(obj, a, b, c, d)
+ *
+ * @param {Function} f The function to bind the object to
+ * @param {Object} obj The object that should act as this when the function
+ * is called
+ * @param {*} var_args Rest arguments that will be used as the initial
+ * arguments when the function is called
+ * @return {Function} A new function that has bound this
+ */
+ function bind(f, obj, var_args) {
+ var a = slice.call(arguments, 2);
+ return function() {
+ return f.apply(obj, a.concat(slice.call(arguments)));
+ };
+ }
+
+ function encodeHtmlAttribute(s) {
+ return String(s).replace(/&/g, '&').replace(/"/g, '"');
+ }
+
+ function addNamespace(doc, prefix, urn) {
+ if (!doc.namespaces[prefix]) {
+ doc.namespaces.add(prefix, urn, '#default#VML');
+ }
+ }
+
+ function addNamespacesAndStylesheet(doc) {
+ addNamespace(doc, 'g_vml_', 'urn:schemas-microsoft-com:vml');
+ addNamespace(doc, 'g_o_', 'urn:schemas-microsoft-com:office:office');
+
+ // Setup default CSS. Only add one style sheet per document
+ if (!doc.styleSheets['ex_canvas_']) {
+ var ss = doc.createStyleSheet();
+ ss.owningElement.id = 'ex_canvas_';
+ ss.cssText = 'canvas{display:inline-block;overflow:hidden;' +
+ // default size is 300x150 in Gecko and Opera
+ 'text-align:left;width:300px;height:150px}';
+ }
+ }
+
+ // Add namespaces and stylesheet at startup.
+ addNamespacesAndStylesheet(document);
+
+ var G_vmlCanvasManager_ = {
+ init: function(opt_doc) {
+ var doc = opt_doc || document;
+ // Create a dummy element so that IE will allow canvas elements to be
+ // recognized.
+ doc.createElement('canvas');
+ doc.attachEvent('onreadystatechange', bind(this.init_, this, doc));
+ },
+
+ init_: function(doc) {
+ // find all canvas elements
+ var els = doc.getElementsByTagName('canvas');
+ for (var i = 0; i < els.length; i++) {
+ this.initElement(els[i]);
+ }
+ },
+
+ /**
+ * Public initializes a canvas element so that it can be used as canvas
+ * element from now on. This is called automatically before the page is
+ * loaded but if you are creating elements using createElement you need to
+ * make sure this is called on the element.
+ * @param {HTMLElement} el The canvas element to initialize.
+ * @return {HTMLElement} the element that was created.
+ */
+ initElement: function(el) {
+ if (!el.getContext) {
+ el.getContext = getContext;
+
+ // Add namespaces and stylesheet to document of the element.
+ addNamespacesAndStylesheet(el.ownerDocument);
+
+ // Remove fallback content. There is no way to hide text nodes so we
+ // just remove all childNodes. We could hide all elements and remove
+ // text nodes but who really cares about the fallback content.
+ el.innerHTML = '';
+
+ // do not use inline function because that will leak memory
+ el.attachEvent('onpropertychange', onPropertyChange);
+ el.attachEvent('onresize', onResize);
+
+ var attrs = el.attributes;
+ if (attrs.width && attrs.width.specified) {
+ // TODO: use runtimeStyle and coordsize
+ // el.getContext().setWidth_(attrs.width.nodeValue);
+ el.style.width = attrs.width.nodeValue + 'px';
+ } else {
+ el.width = el.clientWidth;
+ }
+ if (attrs.height && attrs.height.specified) {
+ // TODO: use runtimeStyle and coordsize
+ // el.getContext().setHeight_(attrs.height.nodeValue);
+ el.style.height = attrs.height.nodeValue + 'px';
+ } else {
+ el.height = el.clientHeight;
+ }
+ //el.getContext().setCoordsize_()
+ }
+ return el;
+ }
+ };
+
+ function onPropertyChange(e) {
+ var el = e.srcElement;
+
+ switch (e.propertyName) {
+ case 'width':
+ el.getContext().clearRect();
+ el.style.width = el.attributes.width.nodeValue + 'px';
+ // In IE8 this does not trigger onresize.
+ el.firstChild.style.width = el.clientWidth + 'px';
+ break;
+ case 'height':
+ el.getContext().clearRect();
+ el.style.height = el.attributes.height.nodeValue + 'px';
+ el.firstChild.style.height = el.clientHeight + 'px';
+ break;
+ }
+ }
+
+ function onResize(e) {
+ var el = e.srcElement;
+ if (el.firstChild) {
+ el.firstChild.style.width = el.clientWidth + 'px';
+ el.firstChild.style.height = el.clientHeight + 'px';
+ }
+ }
+
+ G_vmlCanvasManager_.init();
+
+ // precompute "00" to "FF"
+ var decToHex = [];
+ for (var i = 0; i < 16; i++) {
+ for (var j = 0; j < 16; j++) {
+ decToHex[i * 16 + j] = i.toString(16) + j.toString(16);
+ }
+ }
+
+ function createMatrixIdentity() {
+ return [
+ [1, 0, 0],
+ [0, 1, 0],
+ [0, 0, 1]
+ ];
+ }
+
+ function matrixMultiply(m1, m2) {
+ var result = createMatrixIdentity();
+
+ for (var x = 0; x < 3; x++) {
+ for (var y = 0; y < 3; y++) {
+ var sum = 0;
+
+ for (var z = 0; z < 3; z++) {
+ sum += m1[x][z] * m2[z][y];
+ }
+
+ result[x][y] = sum;
+ }
+ }
+ return result;
+ }
+
+ function copyState(o1, o2) {
+ o2.fillStyle = o1.fillStyle;
+ o2.lineCap = o1.lineCap;
+ o2.lineJoin = o1.lineJoin;
+ o2.lineWidth = o1.lineWidth;
+ o2.miterLimit = o1.miterLimit;
+ o2.shadowBlur = o1.shadowBlur;
+ o2.shadowColor = o1.shadowColor;
+ o2.shadowOffsetX = o1.shadowOffsetX;
+ o2.shadowOffsetY = o1.shadowOffsetY;
+ o2.strokeStyle = o1.strokeStyle;
+ o2.globalAlpha = o1.globalAlpha;
+ o2.font = o1.font;
+ o2.textAlign = o1.textAlign;
+ o2.textBaseline = o1.textBaseline;
+ o2.arcScaleX_ = o1.arcScaleX_;
+ o2.arcScaleY_ = o1.arcScaleY_;
+ o2.lineScale_ = o1.lineScale_;
+ }
+
+ var colorData = {
+ aliceblue: '#F0F8FF',
+ antiquewhite: '#FAEBD7',
+ aquamarine: '#7FFFD4',
+ azure: '#F0FFFF',
+ beige: '#F5F5DC',
+ bisque: '#FFE4C4',
+ black: '#000000',
+ blanchedalmond: '#FFEBCD',
+ 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',
+ gainsboro: '#DCDCDC',
+ ghostwhite: '#F8F8FF',
+ gold: '#FFD700',
+ goldenrod: '#DAA520',
+ grey: '#808080',
+ greenyellow: '#ADFF2F',
+ 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',
+ lightgreen: '#90EE90',
+ lightgrey: '#D3D3D3',
+ lightpink: '#FFB6C1',
+ lightsalmon: '#FFA07A',
+ lightseagreen: '#20B2AA',
+ lightskyblue: '#87CEFA',
+ lightslategray: '#778899',
+ lightslategrey: '#778899',
+ lightsteelblue: '#B0C4DE',
+ lightyellow: '#FFFFE0',
+ limegreen: '#32CD32',
+ linen: '#FAF0E6',
+ magenta: '#FF00FF',
+ 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',
+ oldlace: '#FDF5E6',
+ 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',
+ rosybrown: '#BC8F8F',
+ royalblue: '#4169E1',
+ saddlebrown: '#8B4513',
+ salmon: '#FA8072',
+ sandybrown: '#F4A460',
+ seagreen: '#2E8B57',
+ seashell: '#FFF5EE',
+ sienna: '#A0522D',
+ skyblue: '#87CEEB',
+ slateblue: '#6A5ACD',
+ slategray: '#708090',
+ slategrey: '#708090',
+ snow: '#FFFAFA',
+ springgreen: '#00FF7F',
+ steelblue: '#4682B4',
+ tan: '#D2B48C',
+ thistle: '#D8BFD8',
+ tomato: '#FF6347',
+ turquoise: '#40E0D0',
+ violet: '#EE82EE',
+ wheat: '#F5DEB3',
+ whitesmoke: '#F5F5F5',
+ yellowgreen: '#9ACD32'
+ };
+
+
+ function getRgbHslContent(styleString) {
+ var start = styleString.indexOf('(', 3);
+ var end = styleString.indexOf(')', start + 1);
+ var parts = styleString.substring(start + 1, end).split(',');
+ // add alpha if needed
+ if (parts.length != 4 || styleString.charAt(3) != 'a') {
+ parts[3] = 1;
+ }
+ return parts;
+ }
+
+ function percent(s) {
+ return parseFloat(s) / 100;
+ }
+
+ function clamp(v, min, max) {
+ return Math.min(max, Math.max(min, v));
+ }
+
+ function hslToRgb(parts){
+ var r, g, b, h, s, l;
+ h = parseFloat(parts[0]) / 360 % 360;
+ if (h < 0)
+ h++;
+ s = clamp(percent(parts[1]), 0, 1);
+ l = clamp(percent(parts[2]), 0, 1);
+ if (s == 0) {
+ r = g = b = l; // achromatic
+ } else {
+ var q = l < 0.5 ? l * (1 + s) : l + s - l * s;
+ var p = 2 * l - q;
+ r = hueToRgb(p, q, h + 1 / 3);
+ g = hueToRgb(p, q, h);
+ b = hueToRgb(p, q, h - 1 / 3);
+ }
+
+ return '#' + decToHex[Math.floor(r * 255)] +
+ decToHex[Math.floor(g * 255)] +
+ decToHex[Math.floor(b * 255)];
+ }
+
+ function hueToRgb(m1, m2, h) {
+ if (h < 0)
+ h++;
+ if (h > 1)
+ h--;
+
+ if (6 * h < 1)
+ return m1 + (m2 - m1) * 6 * h;
+ else if (2 * h < 1)
+ return m2;
+ else if (3 * h < 2)
+ return m1 + (m2 - m1) * (2 / 3 - h) * 6;
+ else
+ return m1;
+ }
+
+ var processStyleCache = {};
+
+ function processStyle(styleString) {
+ if (styleString in processStyleCache) {
+ return processStyleCache[styleString];
+ }
+
+ var str, alpha = 1;
+
+ styleString = String(styleString);
+ if (styleString.charAt(0) == '#') {
+ str = styleString;
+ } else if (/^rgb/.test(styleString)) {
+ var parts = getRgbHslContent(styleString);
+ var str = '#', n;
+ for (var i = 0; i < 3; i++) {
+ if (parts[i].indexOf('%') != -1) {
+ n = Math.floor(percent(parts[i]) * 255);
+ } else {
+ n = +parts[i];
+ }
+ str += decToHex[clamp(n, 0, 255)];
+ }
+ alpha = +parts[3];
+ } else if (/^hsl/.test(styleString)) {
+ var parts = getRgbHslContent(styleString);
+ str = hslToRgb(parts);
+ alpha = parts[3];
+ } else {
+ str = colorData[styleString] || styleString;
+ }
+ return processStyleCache[styleString] = {color: str, alpha: alpha};
+ }
+
+ var DEFAULT_STYLE = {
+ style: 'normal',
+ variant: 'normal',
+ weight: 'normal',
+ size: 10,
+ family: 'sans-serif'
+ };
+
+ // Internal text style cache
+ var fontStyleCache = {};
+
+ function processFontStyle(styleString) {
+ if (fontStyleCache[styleString]) {
+ return fontStyleCache[styleString];
+ }
+
+ var el = document.createElement('div');
+ var style = el.style;
+ try {
+ style.font = styleString;
+ } catch (ex) {
+ // Ignore failures to set to invalid font.
+ }
+
+ return fontStyleCache[styleString] = {
+ style: style.fontStyle || DEFAULT_STYLE.style,
+ variant: style.fontVariant || DEFAULT_STYLE.variant,
+ weight: style.fontWeight || DEFAULT_STYLE.weight,
+ size: style.fontSize || DEFAULT_STYLE.size,
+ family: style.fontFamily || DEFAULT_STYLE.family
+ };
+ }
+
+ function getComputedStyle(style, element) {
+ var computedStyle = {};
+
+ for (var p in style) {
+ computedStyle[p] = style[p];
+ }
+
+ // Compute the size
+ var canvasFontSize = parseFloat(element.currentStyle.fontSize),
+ fontSize = parseFloat(style.size);
+
+ if (typeof style.size == 'number') {
+ computedStyle.size = style.size;
+ } else if (style.size.indexOf('px') != -1) {
+ computedStyle.size = fontSize;
+ } else if (style.size.indexOf('em') != -1) {
+ computedStyle.size = canvasFontSize * fontSize;
+ } else if(style.size.indexOf('%') != -1) {
+ computedStyle.size = (canvasFontSize / 100) * fontSize;
+ } else if (style.size.indexOf('pt') != -1) {
+ computedStyle.size = fontSize / .75;
+ } else {
+ computedStyle.size = canvasFontSize;
+ }
+
+ // Different scaling between normal text and VML text. This was found using
+ // trial and error to get the same size as non VML text.
+ computedStyle.size *= 0.981;
+
+ return computedStyle;
+ }
+
+ function buildStyle(style) {
+ return style.style + ' ' + style.variant + ' ' + style.weight + ' ' +
+ style.size + 'px ' + style.family;
+ }
+
+ var lineCapMap = {
+ 'butt': 'flat',
+ 'round': 'round'
+ };
+
+ function processLineCap(lineCap) {
+ return lineCapMap[lineCap] || 'square';
+ }
+
+ /**
+ * This class implements CanvasRenderingContext2D interface as described by
+ * the WHATWG.
+ * @param {HTMLElement} canvasElement The element that the 2D context should
+ * be associated with
+ */
+ function CanvasRenderingContext2D_(canvasElement) {
+ this.m_ = createMatrixIdentity();
+
+ this.mStack_ = [];
+ this.aStack_ = [];
+ this.currentPath_ = [];
+
+ // Canvas context properties
+ this.strokeStyle = '#000';
+ this.fillStyle = '#000';
+
+ this.lineWidth = 1;
+ this.lineJoin = 'miter';
+ this.lineCap = 'butt';
+ this.miterLimit = Z * 1;
+ this.globalAlpha = 1;
+ this.font = '10px sans-serif';
+ this.textAlign = 'left';
+ this.textBaseline = 'alphabetic';
+ this.canvas = canvasElement;
+
+ var cssText = 'width:' + canvasElement.clientWidth + 'px;height:' +
+ canvasElement.clientHeight + 'px;overflow:hidden;position:absolute';
+ var el = canvasElement.ownerDocument.createElement('div');
+ el.style.cssText = cssText;
+ canvasElement.appendChild(el);
+
+ var overlayEl = el.cloneNode(false);
+ // Use a non transparent background.
+ overlayEl.style.backgroundColor = 'red';
+ overlayEl.style.filter = 'alpha(opacity=0)';
+ canvasElement.appendChild(overlayEl);
+
+ this.element_ = el;
+ this.arcScaleX_ = 1;
+ this.arcScaleY_ = 1;
+ this.lineScale_ = 1;
+ }
+
+ var contextPrototype = CanvasRenderingContext2D_.prototype;
+ contextPrototype.clearRect = function() {
+ if (this.textMeasureEl_) {
+ this.textMeasureEl_.removeNode(true);
+ this.textMeasureEl_ = null;
+ }
+ this.element_.innerHTML = '';
+ };
+
+ contextPrototype.beginPath = function() {
+ // TODO: Branch current matrix so that save/restore has no effect
+ // as per safari docs.
+ this.currentPath_ = [];
+ };
+
+ contextPrototype.moveTo = function(aX, aY) {
+ var p = getCoords(this, aX, aY);
+ this.currentPath_.push({type: 'moveTo', x: p.x, y: p.y});
+ this.currentX_ = p.x;
+ this.currentY_ = p.y;
+ };
+
+ contextPrototype.lineTo = function(aX, aY) {
+ var p = getCoords(this, aX, aY);
+ this.currentPath_.push({type: 'lineTo', x: p.x, y: p.y});
+
+ this.currentX_ = p.x;
+ this.currentY_ = p.y;
+ };
+
+ contextPrototype.bezierCurveTo = function(aCP1x, aCP1y,
+ aCP2x, aCP2y,
+ aX, aY) {
+ var p = getCoords(this, aX, aY);
+ var cp1 = getCoords(this, aCP1x, aCP1y);
+ var cp2 = getCoords(this, aCP2x, aCP2y);
+ bezierCurveTo(this, cp1, cp2, p);
+ };
+
+ // Helper function that takes the already fixed cordinates.
+ function bezierCurveTo(self, cp1, cp2, p) {
+ self.currentPath_.push({
+ type: 'bezierCurveTo',
+ cp1x: cp1.x,
+ cp1y: cp1.y,
+ cp2x: cp2.x,
+ cp2y: cp2.y,
+ x: p.x,
+ y: p.y
+ });
+ self.currentX_ = p.x;
+ self.currentY_ = p.y;
+ }
+
+ contextPrototype.quadraticCurveTo = function(aCPx, aCPy, aX, aY) {
+ // the following is lifted almost directly from
+ // http://developer.mozilla.org/en/docs/Canvas_tutorial:Drawing_shapes
+
+ var cp = getCoords(this, aCPx, aCPy);
+ var p = getCoords(this, aX, aY);
+
+ var cp1 = {
+ x: this.currentX_ + 2.0 / 3.0 * (cp.x - this.currentX_),
+ y: this.currentY_ + 2.0 / 3.0 * (cp.y - this.currentY_)
+ };
+ var cp2 = {
+ x: cp1.x + (p.x - this.currentX_) / 3.0,
+ y: cp1.y + (p.y - this.currentY_) / 3.0
+ };
+
+ bezierCurveTo(this, cp1, cp2, p);
+ };
+
+ contextPrototype.arc = function(aX, aY, aRadius,
+ aStartAngle, aEndAngle, aClockwise) {
+ aRadius *= Z;
+ var arcType = aClockwise ? 'at' : 'wa';
+
+ var xStart = aX + mc(aStartAngle) * aRadius - Z2;
+ var yStart = aY + ms(aStartAngle) * aRadius - Z2;
+
+ var xEnd = aX + mc(aEndAngle) * aRadius - Z2;
+ var yEnd = aY + ms(aEndAngle) * aRadius - Z2;
+
+ // IE won't render arches drawn counter clockwise if xStart == xEnd.
+ if (xStart == xEnd && !aClockwise) {
+ xStart += 0.125; // Offset xStart by 1/80 of a pixel. Use something
+ // that can be represented in binary
+ }
+
+ var p = getCoords(this, aX, aY);
+ var pStart = getCoords(this, xStart, yStart);
+ var pEnd = getCoords(this, xEnd, yEnd);
+
+ this.currentPath_.push({type: arcType,
+ x: p.x,
+ y: p.y,
+ radius: aRadius,
+ xStart: pStart.x,
+ yStart: pStart.y,
+ xEnd: pEnd.x,
+ yEnd: pEnd.y});
+
+ };
+
+ contextPrototype.rect = function(aX, aY, aWidth, aHeight) {
+ this.moveTo(aX, aY);
+ this.lineTo(aX + aWidth, aY);
+ this.lineTo(aX + aWidth, aY + aHeight);
+ this.lineTo(aX, aY + aHeight);
+ this.closePath();
+ };
+
+ contextPrototype.strokeRect = function(aX, aY, aWidth, aHeight) {
+ var oldPath = this.currentPath_;
+ this.beginPath();
+
+ this.moveTo(aX, aY);
+ this.lineTo(aX + aWidth, aY);
+ this.lineTo(aX + aWidth, aY + aHeight);
+ this.lineTo(aX, aY + aHeight);
+ this.closePath();
+ this.stroke();
+
+ this.currentPath_ = oldPath;
+ };
+
+ contextPrototype.fillRect = function(aX, aY, aWidth, aHeight) {
+ var oldPath = this.currentPath_;
+ this.beginPath();
+
+ this.moveTo(aX, aY);
+ this.lineTo(aX + aWidth, aY);
+ this.lineTo(aX + aWidth, aY + aHeight);
+ this.lineTo(aX, aY + aHeight);
+ this.closePath();
+ this.fill();
+
+ this.currentPath_ = oldPath;
+ };
+
+ contextPrototype.createLinearGradient = function(aX0, aY0, aX1, aY1) {
+ var gradient = new CanvasGradient_('gradient');
+ gradient.x0_ = aX0;
+ gradient.y0_ = aY0;
+ gradient.x1_ = aX1;
+ gradient.y1_ = aY1;
+ return gradient;
+ };
+
+ contextPrototype.createRadialGradient = function(aX0, aY0, aR0,
+ aX1, aY1, aR1) {
+ var gradient = new CanvasGradient_('gradientradial');
+ gradient.x0_ = aX0;
+ gradient.y0_ = aY0;
+ gradient.r0_ = aR0;
+ gradient.x1_ = aX1;
+ gradient.y1_ = aY1;
+ gradient.r1_ = aR1;
+ return gradient;
+ };
+
+ contextPrototype.drawImage = function(image, var_args) {
+ var dx, dy, dw, dh, sx, sy, sw, sh;
+
+ // to find the original width we overide the width and height
+ var oldRuntimeWidth = image.runtimeStyle.width;
+ var oldRuntimeHeight = image.runtimeStyle.height;
+ image.runtimeStyle.width = 'auto';
+ image.runtimeStyle.height = 'auto';
+
+ // get the original size
+ var w = image.width;
+ var h = image.height;
+
+ // and remove overides
+ image.runtimeStyle.width = oldRuntimeWidth;
+ image.runtimeStyle.height = oldRuntimeHeight;
+
+ if (arguments.length == 3) {
+ dx = arguments[1];
+ dy = arguments[2];
+ sx = sy = 0;
+ sw = dw = w;
+ sh = dh = h;
+ } else if (arguments.length == 5) {
+ dx = arguments[1];
+ dy = arguments[2];
+ dw = arguments[3];
+ dh = arguments[4];
+ sx = sy = 0;
+ sw = w;
+ sh = h;
+ } else if (arguments.length == 9) {
+ sx = arguments[1];
+ sy = arguments[2];
+ sw = arguments[3];
+ sh = arguments[4];
+ dx = arguments[5];
+ dy = arguments[6];
+ dw = arguments[7];
+ dh = arguments[8];
+ } else {
+ throw Error('Invalid number of arguments');
+ }
+
+ var d = getCoords(this, dx, dy);
+
+ var w2 = sw / 2;
+ var h2 = sh / 2;
+
+ var vmlStr = [];
+
+ var W = 10;
+ var H = 10;
+
+ // For some reason that I've now forgotten, using divs didn't work
+ vmlStr.push(' ' ,
+ ' ',
+ ' ');
+
+ this.element_.insertAdjacentHTML('BeforeEnd', vmlStr.join(''));
+ };
+
+ contextPrototype.stroke = function(aFill) {
+ var W = 10;
+ var H = 10;
+ // Divide the shape into chunks if it's too long because IE has a limit
+ // somewhere for how long a VML shape can be. This simple division does
+ // not work with fills, only strokes, unfortunately.
+ var chunkSize = 5000;
+
+ var min = {x: null, y: null};
+ var max = {x: null, y: null};
+
+ for (var j = 0; j < this.currentPath_.length; j += chunkSize) {
+ var lineStr = [];
+ var lineOpen = false;
+
+ lineStr.push('');
+
+ if (!aFill) {
+ appendStroke(this, lineStr);
+ } else {
+ appendFill(this, lineStr, min, max);
+ }
+
+ lineStr.push(' ');
+
+ this.element_.insertAdjacentHTML('beforeEnd', lineStr.join(''));
+ }
+ };
+
+ function appendStroke(ctx, lineStr) {
+ var a = processStyle(ctx.strokeStyle);
+ var color = a.color;
+ var opacity = a.alpha * ctx.globalAlpha;
+ var lineWidth = ctx.lineScale_ * ctx.lineWidth;
+
+ // VML cannot correctly render a line if the width is less than 1px.
+ // In that case, we dilute the color to make the line look thinner.
+ if (lineWidth < 1) {
+ opacity *= lineWidth;
+ }
+
+ lineStr.push(
+ ' '
+ );
+ }
+
+ function appendFill(ctx, lineStr, min, max) {
+ var fillStyle = ctx.fillStyle;
+ var arcScaleX = ctx.arcScaleX_;
+ var arcScaleY = ctx.arcScaleY_;
+ var width = max.x - min.x;
+ var height = max.y - min.y;
+ if (fillStyle instanceof CanvasGradient_) {
+ // TODO: Gradients transformed with the transformation matrix.
+ var angle = 0;
+ var focus = {x: 0, y: 0};
+
+ // additional offset
+ var shift = 0;
+ // scale factor for offset
+ var expansion = 1;
+
+ if (fillStyle.type_ == 'gradient') {
+ var x0 = fillStyle.x0_ / arcScaleX;
+ var y0 = fillStyle.y0_ / arcScaleY;
+ var x1 = fillStyle.x1_ / arcScaleX;
+ var y1 = fillStyle.y1_ / arcScaleY;
+ var p0 = getCoords(ctx, x0, y0);
+ var p1 = getCoords(ctx, x1, y1);
+ var dx = p1.x - p0.x;
+ var dy = p1.y - p0.y;
+ angle = Math.atan2(dx, dy) * 180 / Math.PI;
+
+ // The angle should be a non-negative number.
+ if (angle < 0) {
+ angle += 360;
+ }
+
+ // Very small angles produce an unexpected result because they are
+ // converted to a scientific notation string.
+ if (angle < 1e-6) {
+ angle = 0;
+ }
+ } else {
+ var p0 = getCoords(ctx, fillStyle.x0_, fillStyle.y0_);
+ focus = {
+ x: (p0.x - min.x) / width,
+ y: (p0.y - min.y) / height
+ };
+
+ width /= arcScaleX * Z;
+ height /= arcScaleY * Z;
+ var dimension = m.max(width, height);
+ shift = 2 * fillStyle.r0_ / dimension;
+ expansion = 2 * fillStyle.r1_ / dimension - shift;
+ }
+
+ // We need to sort the color stops in ascending order by offset,
+ // otherwise IE won't interpret it correctly.
+ var stops = fillStyle.colors_;
+ stops.sort(function(cs1, cs2) {
+ return cs1.offset - cs2.offset;
+ });
+
+ var length = stops.length;
+ var color1 = stops[0].color;
+ var color2 = stops[length - 1].color;
+ var opacity1 = stops[0].alpha * ctx.globalAlpha;
+ var opacity2 = stops[length - 1].alpha * ctx.globalAlpha;
+
+ var colors = [];
+ for (var i = 0; i < length; i++) {
+ var stop = stops[i];
+ colors.push(stop.offset * expansion + shift + ' ' + stop.color);
+ }
+
+ // When colors attribute is used, the meanings of opacity and o:opacity2
+ // are reversed.
+ lineStr.push(' ');
+ } else if (fillStyle instanceof CanvasPattern_) {
+ if (width && height) {
+ var deltaLeft = -min.x;
+ var deltaTop = -min.y;
+ lineStr.push(' ');
+ }
+ } else {
+ var a = processStyle(ctx.fillStyle);
+ var color = a.color;
+ var opacity = a.alpha * ctx.globalAlpha;
+ lineStr.push(' ');
+ }
+ }
+
+ contextPrototype.fill = function() {
+ this.stroke(true);
+ };
+
+ contextPrototype.closePath = function() {
+ this.currentPath_.push({type: 'close'});
+ };
+
+ function getCoords(ctx, aX, aY) {
+ var m = ctx.m_;
+ return {
+ x: Z * (aX * m[0][0] + aY * m[1][0] + m[2][0]) - Z2,
+ y: Z * (aX * m[0][1] + aY * m[1][1] + m[2][1]) - Z2
+ };
+ };
+
+ contextPrototype.save = function() {
+ var o = {};
+ copyState(this, o);
+ this.aStack_.push(o);
+ this.mStack_.push(this.m_);
+ this.m_ = matrixMultiply(createMatrixIdentity(), this.m_);
+ };
+
+ contextPrototype.restore = function() {
+ if (this.aStack_.length) {
+ copyState(this.aStack_.pop(), this);
+ this.m_ = this.mStack_.pop();
+ }
+ };
+
+ function matrixIsFinite(m) {
+ return isFinite(m[0][0]) && isFinite(m[0][1]) &&
+ isFinite(m[1][0]) && isFinite(m[1][1]) &&
+ isFinite(m[2][0]) && isFinite(m[2][1]);
+ }
+
+ function setM(ctx, m, updateLineScale) {
+ if (!matrixIsFinite(m)) {
+ return;
+ }
+ ctx.m_ = m;
+
+ if (updateLineScale) {
+ // Get the line scale.
+ // Determinant of this.m_ means how much the area is enlarged by the
+ // transformation. So its square root can be used as a scale factor
+ // for width.
+ var det = m[0][0] * m[1][1] - m[0][1] * m[1][0];
+ ctx.lineScale_ = sqrt(abs(det));
+ }
+ }
+
+ contextPrototype.translate = function(aX, aY) {
+ var m1 = [
+ [1, 0, 0],
+ [0, 1, 0],
+ [aX, aY, 1]
+ ];
+
+ setM(this, matrixMultiply(m1, this.m_), false);
+ };
+
+ contextPrototype.rotate = function(aRot) {
+ var c = mc(aRot);
+ var s = ms(aRot);
+
+ var m1 = [
+ [c, s, 0],
+ [-s, c, 0],
+ [0, 0, 1]
+ ];
+
+ setM(this, matrixMultiply(m1, this.m_), false);
+ };
+
+ contextPrototype.scale = function(aX, aY) {
+ this.arcScaleX_ *= aX;
+ this.arcScaleY_ *= aY;
+ var m1 = [
+ [aX, 0, 0],
+ [0, aY, 0],
+ [0, 0, 1]
+ ];
+
+ setM(this, matrixMultiply(m1, this.m_), true);
+ };
+
+ contextPrototype.transform = function(m11, m12, m21, m22, dx, dy) {
+ var m1 = [
+ [m11, m12, 0],
+ [m21, m22, 0],
+ [dx, dy, 1]
+ ];
+
+ setM(this, matrixMultiply(m1, this.m_), true);
+ };
+
+ contextPrototype.setTransform = function(m11, m12, m21, m22, dx, dy) {
+ var m = [
+ [m11, m12, 0],
+ [m21, m22, 0],
+ [dx, dy, 1]
+ ];
+
+ setM(this, m, true);
+ };
+
+ /**
+ * The text drawing function.
+ * The maxWidth argument isn't taken in account, since no browser supports
+ * it yet.
+ */
+ contextPrototype.drawText_ = function(text, x, y, maxWidth, stroke) {
+ var m = this.m_,
+ delta = 1000,
+ left = 0,
+ right = delta,
+ offset = {x: 0, y: 0},
+ lineStr = [];
+
+ var fontStyle = getComputedStyle(processFontStyle(this.font),
+ this.element_);
+
+ var fontStyleString = buildStyle(fontStyle);
+
+ var elementStyle = this.element_.currentStyle;
+ var textAlign = this.textAlign.toLowerCase();
+ switch (textAlign) {
+ case 'left':
+ case 'center':
+ case 'right':
+ break;
+ case 'end':
+ textAlign = elementStyle.direction == 'ltr' ? 'right' : 'left';
+ break;
+ case 'start':
+ textAlign = elementStyle.direction == 'rtl' ? 'right' : 'left';
+ break;
+ default:
+ textAlign = 'left';
+ }
+
+ // 1.75 is an arbitrary number, as there is no info about the text baseline
+ switch (this.textBaseline) {
+ case 'hanging':
+ case 'top':
+ offset.y = fontStyle.size / 1.75;
+ break;
+ case 'middle':
+ break;
+ default:
+ case null:
+ case 'alphabetic':
+ case 'ideographic':
+ case 'bottom':
+ offset.y = -fontStyle.size / 2.25;
+ break;
+ }
+
+ switch(textAlign) {
+ case 'right':
+ left = delta;
+ right = 0.05;
+ break;
+ case 'center':
+ left = right = delta / 2;
+ break;
+ }
+
+ var d = getCoords(this, x + offset.x, y + offset.y);
+
+ lineStr.push('');
+
+ if (stroke) {
+ appendStroke(this, lineStr);
+ } else {
+ // TODO: Fix the min and max params.
+ appendFill(this, lineStr, {x: -left, y: 0},
+ {x: right, y: fontStyle.size});
+ }
+
+ var skewM = m[0][0].toFixed(3) + ',' + m[1][0].toFixed(3) + ',' +
+ m[0][1].toFixed(3) + ',' + m[1][1].toFixed(3) + ',0,0';
+
+ var skewOffset = mr(d.x / Z) + ',' + mr(d.y / Z);
+
+ lineStr.push(' ',
+ ' ',
+ ' ');
+
+ this.element_.insertAdjacentHTML('beforeEnd', lineStr.join(''));
+ };
+
+ contextPrototype.fillText = function(text, x, y, maxWidth) {
+ this.drawText_(text, x, y, maxWidth, false);
+ };
+
+ contextPrototype.strokeText = function(text, x, y, maxWidth) {
+ this.drawText_(text, x, y, maxWidth, true);
+ };
+
+ contextPrototype.measureText = function(text) {
+ if (!this.textMeasureEl_) {
+ var s = ' ';
+ this.element_.insertAdjacentHTML('beforeEnd', s);
+ this.textMeasureEl_ = this.element_.lastChild;
+ }
+ var doc = this.element_.ownerDocument;
+ this.textMeasureEl_.innerHTML = '';
+ this.textMeasureEl_.style.font = this.font;
+ // Don't use innerHTML or innerText because they allow markup/whitespace.
+ this.textMeasureEl_.appendChild(doc.createTextNode(text));
+ return {width: this.textMeasureEl_.offsetWidth};
+ };
+
+ /******** STUBS ********/
+ contextPrototype.clip = function() {
+ // TODO: Implement
+ };
+
+ contextPrototype.arcTo = function() {
+ // TODO: Implement
+ };
+
+ contextPrototype.createPattern = function(image, repetition) {
+ return new CanvasPattern_(image, repetition);
+ };
+
+ // Gradient / Pattern Stubs
+ function CanvasGradient_(aType) {
+ this.type_ = aType;
+ this.x0_ = 0;
+ this.y0_ = 0;
+ this.r0_ = 0;
+ this.x1_ = 0;
+ this.y1_ = 0;
+ this.r1_ = 0;
+ this.colors_ = [];
+ }
+
+ CanvasGradient_.prototype.addColorStop = function(aOffset, aColor) {
+ aColor = processStyle(aColor);
+ this.colors_.push({offset: aOffset,
+ color: aColor.color,
+ alpha: aColor.alpha});
+ };
+
+ function CanvasPattern_(image, repetition) {
+ assertImageIsValid(image);
+ switch (repetition) {
+ case 'repeat':
+ case null:
+ case '':
+ this.repetition_ = 'repeat';
+ break
+ case 'repeat-x':
+ case 'repeat-y':
+ case 'no-repeat':
+ this.repetition_ = repetition;
+ break;
+ default:
+ throwException('SYNTAX_ERR');
+ }
+
+ this.src_ = image.src;
+ this.width_ = image.width;
+ this.height_ = image.height;
+ }
+
+ function throwException(s) {
+ throw new DOMException_(s);
+ }
+
+ function assertImageIsValid(img) {
+ if (!img || img.nodeType != 1 || img.tagName != 'IMG') {
+ throwException('TYPE_MISMATCH_ERR');
+ }
+ if (img.readyState != 'complete') {
+ throwException('INVALID_STATE_ERR');
+ }
+ }
+
+ function DOMException_(s) {
+ this.code = this[s];
+ this.message = s +': DOM Exception ' + this.code;
+ }
+ var p = DOMException_.prototype = new Error;
+ p.INDEX_SIZE_ERR = 1;
+ p.DOMSTRING_SIZE_ERR = 2;
+ p.HIERARCHY_REQUEST_ERR = 3;
+ p.WRONG_DOCUMENT_ERR = 4;
+ p.INVALID_CHARACTER_ERR = 5;
+ p.NO_DATA_ALLOWED_ERR = 6;
+ p.NO_MODIFICATION_ALLOWED_ERR = 7;
+ p.NOT_FOUND_ERR = 8;
+ p.NOT_SUPPORTED_ERR = 9;
+ p.INUSE_ATTRIBUTE_ERR = 10;
+ p.INVALID_STATE_ERR = 11;
+ p.SYNTAX_ERR = 12;
+ p.INVALID_MODIFICATION_ERR = 13;
+ p.NAMESPACE_ERR = 14;
+ p.INVALID_ACCESS_ERR = 15;
+ p.VALIDATION_ERR = 16;
+ p.TYPE_MISMATCH_ERR = 17;
+
+ // set up externs
+ G_vmlCanvasManager = G_vmlCanvasManager_;
+ CanvasRenderingContext2D = CanvasRenderingContext2D_;
+ CanvasGradient = CanvasGradient_;
+ CanvasPattern = CanvasPattern_;
+ DOMException = DOMException_;
+})();
+
+} // if
diff --git a/djangoproject/static/js/lib/jquery-flot/excanvas.min.js b/djangoproject/static/js/lib/jquery-flot/excanvas.min.js
new file mode 100644
index 000000000..fcf876c74
--- /dev/null
+++ b/djangoproject/static/js/lib/jquery-flot/excanvas.min.js
@@ -0,0 +1 @@
+if(!document.createElement("canvas").getContext){(function(){var ab=Math;var n=ab.round;var l=ab.sin;var A=ab.cos;var H=ab.abs;var N=ab.sqrt;var d=10;var f=d/2;var z=+navigator.userAgent.match(/MSIE ([\d.]+)?/)[1];function y(){return this.context_||(this.context_=new D(this))}var t=Array.prototype.slice;function g(j,m,p){var i=t.call(arguments,2);return function(){return j.apply(m,i.concat(t.call(arguments)))}}function af(i){return String(i).replace(/&/g,"&").replace(/"/g,""")}function Y(m,j,i){if(!m.namespaces[j]){m.namespaces.add(j,i,"#default#VML")}}function R(j){Y(j,"g_vml_","urn:schemas-microsoft-com:vml");Y(j,"g_o_","urn:schemas-microsoft-com:office:office");if(!j.styleSheets.ex_canvas_){var i=j.createStyleSheet();i.owningElement.id="ex_canvas_";i.cssText="canvas{display:inline-block;overflow:hidden;text-align:left;width:300px;height:150px}"}}R(document);var e={init:function(i){var j=i||document;j.createElement("canvas");j.attachEvent("onreadystatechange",g(this.init_,this,j))},init_:function(p){var m=p.getElementsByTagName("canvas");for(var j=0;j1){m--}if(6*m<1){return j+(i-j)*6*m}else{if(2*m<1){return i}else{if(3*m<2){return j+(i-j)*(2/3-m)*6}else{return j}}}}var C={};function F(j){if(j in C){return C[j]}var ag,Z=1;j=String(j);if(j.charAt(0)=="#"){ag=j}else{if(/^rgb/.test(j)){var p=M(j);var ag="#",ah;for(var m=0;m<3;m++){if(p[m].indexOf("%")!=-1){ah=Math.floor(c(p[m])*255)}else{ah=+p[m]}ag+=k[r(ah,0,255)]}Z=+p[3]}else{if(/^hsl/.test(j)){var p=M(j);ag=I(p);Z=p[3]}else{ag=b[j]||j}}}return C[j]={color:ag,alpha:Z}}var o={style:"normal",variant:"normal",weight:"normal",size:10,family:"sans-serif"};var L={};function E(i){if(L[i]){return L[i]}var p=document.createElement("div");var m=p.style;try{m.font=i}catch(j){}return L[i]={style:m.fontStyle||o.style,variant:m.fontVariant||o.variant,weight:m.fontWeight||o.weight,size:m.fontSize||o.size,family:m.fontFamily||o.family}}function u(m,j){var i={};for(var ah in m){i[ah]=m[ah]}var ag=parseFloat(j.currentStyle.fontSize),Z=parseFloat(m.size);if(typeof m.size=="number"){i.size=m.size}else{if(m.size.indexOf("px")!=-1){i.size=Z}else{if(m.size.indexOf("em")!=-1){i.size=ag*Z}else{if(m.size.indexOf("%")!=-1){i.size=(ag/100)*Z}else{if(m.size.indexOf("pt")!=-1){i.size=Z/0.75}else{i.size=ag}}}}}i.size*=0.981;return i}function ac(i){return i.style+" "+i.variant+" "+i.weight+" "+i.size+"px "+i.family}var s={butt:"flat",round:"round"};function S(i){return s[i]||"square"}function D(i){this.m_=B();this.mStack_=[];this.aStack_=[];this.currentPath_=[];this.strokeStyle="#000";this.fillStyle="#000";this.lineWidth=1;this.lineJoin="miter";this.lineCap="butt";this.miterLimit=d*1;this.globalAlpha=1;this.font="10px sans-serif";this.textAlign="left";this.textBaseline="alphabetic";this.canvas=i;var m="width:"+i.clientWidth+"px;height:"+i.clientHeight+"px;overflow:hidden;position:absolute";var j=i.ownerDocument.createElement("div");j.style.cssText=m;i.appendChild(j);var p=j.cloneNode(false);p.style.backgroundColor="red";p.style.filter="alpha(opacity=0)";i.appendChild(p);this.element_=j;this.arcScaleX_=1;this.arcScaleY_=1;this.lineScale_=1}var q=D.prototype;q.clearRect=function(){if(this.textMeasureEl_){this.textMeasureEl_.removeNode(true);this.textMeasureEl_=null}this.element_.innerHTML=""};q.beginPath=function(){this.currentPath_=[]};q.moveTo=function(j,i){var m=V(this,j,i);this.currentPath_.push({type:"moveTo",x:m.x,y:m.y});this.currentX_=m.x;this.currentY_=m.y};q.lineTo=function(j,i){var m=V(this,j,i);this.currentPath_.push({type:"lineTo",x:m.x,y:m.y});this.currentX_=m.x;this.currentY_=m.y};q.bezierCurveTo=function(m,j,ak,aj,ai,ag){var i=V(this,ai,ag);var ah=V(this,m,j);var Z=V(this,ak,aj);K(this,ah,Z,i)};function K(i,Z,m,j){i.currentPath_.push({type:"bezierCurveTo",cp1x:Z.x,cp1y:Z.y,cp2x:m.x,cp2y:m.y,x:j.x,y:j.y});i.currentX_=j.x;i.currentY_=j.y}q.quadraticCurveTo=function(ai,m,j,i){var ah=V(this,ai,m);var ag=V(this,j,i);var aj={x:this.currentX_+2/3*(ah.x-this.currentX_),y:this.currentY_+2/3*(ah.y-this.currentY_)};var Z={x:aj.x+(ag.x-this.currentX_)/3,y:aj.y+(ag.y-this.currentY_)/3};K(this,aj,Z,ag)};q.arc=function(al,aj,ak,ag,j,m){ak*=d;var ap=m?"at":"wa";var am=al+A(ag)*ak-f;var ao=aj+l(ag)*ak-f;var i=al+A(j)*ak-f;var an=aj+l(j)*ak-f;if(am==i&&!m){am+=0.125}var Z=V(this,al,aj);var ai=V(this,am,ao);var ah=V(this,i,an);this.currentPath_.push({type:ap,x:Z.x,y:Z.y,radius:ak,xStart:ai.x,yStart:ai.y,xEnd:ah.x,yEnd:ah.y})};q.rect=function(m,j,i,p){this.moveTo(m,j);this.lineTo(m+i,j);this.lineTo(m+i,j+p);this.lineTo(m,j+p);this.closePath()};q.strokeRect=function(m,j,i,p){var Z=this.currentPath_;this.beginPath();this.moveTo(m,j);this.lineTo(m+i,j);this.lineTo(m+i,j+p);this.lineTo(m,j+p);this.closePath();this.stroke();this.currentPath_=Z};q.fillRect=function(m,j,i,p){var Z=this.currentPath_;this.beginPath();this.moveTo(m,j);this.lineTo(m+i,j);this.lineTo(m+i,j+p);this.lineTo(m,j+p);this.closePath();this.fill();this.currentPath_=Z};q.createLinearGradient=function(j,p,i,m){var Z=new U("gradient");Z.x0_=j;Z.y0_=p;Z.x1_=i;Z.y1_=m;return Z};q.createRadialGradient=function(p,ag,m,j,Z,i){var ah=new U("gradientradial");ah.x0_=p;ah.y0_=ag;ah.r0_=m;ah.x1_=j;ah.y1_=Z;ah.r1_=i;return ah};q.drawImage=function(aq,m){var aj,ah,al,ay,ao,am,at,aA;var ak=aq.runtimeStyle.width;var ap=aq.runtimeStyle.height;aq.runtimeStyle.width="auto";aq.runtimeStyle.height="auto";var ai=aq.width;var aw=aq.height;aq.runtimeStyle.width=ak;aq.runtimeStyle.height=ap;if(arguments.length==3){aj=arguments[1];ah=arguments[2];ao=am=0;at=al=ai;aA=ay=aw}else{if(arguments.length==5){aj=arguments[1];ah=arguments[2];al=arguments[3];ay=arguments[4];ao=am=0;at=ai;aA=aw}else{if(arguments.length==9){ao=arguments[1];am=arguments[2];at=arguments[3];aA=arguments[4];aj=arguments[5];ah=arguments[6];al=arguments[7];ay=arguments[8]}else{throw Error("Invalid number of arguments")}}}var az=V(this,aj,ah);var p=at/2;var j=aA/2;var ax=[];var i=10;var ag=10;ax.push(" ',' "," ");this.element_.insertAdjacentHTML("BeforeEnd",ax.join(""))};q.stroke=function(ao){var Z=10;var ap=10;var ag=5000;var ai={x:null,y:null};var an={x:null,y:null};for(var aj=0;ajan.x){an.x=m.x}if(ai.y==null||m.yan.y){an.y=m.y}}}am.push(' ">');if(!ao){w(this,am)}else{G(this,am,ai,an)}am.push("");this.element_.insertAdjacentHTML("beforeEnd",am.join(""))}};function w(m,ag){var j=F(m.strokeStyle);var p=j.color;var Z=j.alpha*m.globalAlpha;var i=m.lineScale_*m.lineWidth;if(i<1){Z*=i}ag.push(" ')}function G(aq,ai,aK,ar){var aj=aq.fillStyle;var aB=aq.arcScaleX_;var aA=aq.arcScaleY_;var j=ar.x-aK.x;var p=ar.y-aK.y;if(aj instanceof U){var an=0;var aF={x:0,y:0};var ax=0;var am=1;if(aj.type_=="gradient"){var al=aj.x0_/aB;var m=aj.y0_/aA;var ak=aj.x1_/aB;var aM=aj.y1_/aA;var aJ=V(aq,al,m);var aI=V(aq,ak,aM);var ag=aI.x-aJ.x;var Z=aI.y-aJ.y;an=Math.atan2(ag,Z)*180/Math.PI;if(an<0){an+=360}if(an<0.000001){an=0}}else{var aJ=V(aq,aj.x0_,aj.y0_);aF={x:(aJ.x-aK.x)/j,y:(aJ.y-aK.y)/p};j/=aB*d;p/=aA*d;var aD=ab.max(j,p);ax=2*aj.r0_/aD;am=2*aj.r1_/aD-ax}var av=aj.colors_;av.sort(function(aN,i){return aN.offset-i.offset});var ap=av.length;var au=av[0].color;var at=av[ap-1].color;var az=av[0].alpha*aq.globalAlpha;var ay=av[ap-1].alpha*aq.globalAlpha;var aE=[];for(var aH=0;aH ')}else{if(aj instanceof T){if(j&&p){var ah=-aK.x;var aC=-aK.y;ai.push(" ')}}else{var aL=F(aq.fillStyle);var aw=aL.color;var aG=aL.alpha*aq.globalAlpha;ai.push(' ')}}}q.fill=function(){this.stroke(true)};q.closePath=function(){this.currentPath_.push({type:"close"})};function V(j,Z,p){var i=j.m_;return{x:d*(Z*i[0][0]+p*i[1][0]+i[2][0])-f,y:d*(Z*i[0][1]+p*i[1][1]+i[2][1])-f}}q.save=function(){var i={};v(this,i);this.aStack_.push(i);this.mStack_.push(this.m_);this.m_=J(B(),this.m_)};q.restore=function(){if(this.aStack_.length){v(this.aStack_.pop(),this);this.m_=this.mStack_.pop()}};function h(i){return isFinite(i[0][0])&&isFinite(i[0][1])&&isFinite(i[1][0])&&isFinite(i[1][1])&&isFinite(i[2][0])&&isFinite(i[2][1])}function aa(j,i,p){if(!h(i)){return}j.m_=i;if(p){var Z=i[0][0]*i[1][1]-i[0][1]*i[1][0];j.lineScale_=N(H(Z))}}q.translate=function(m,j){var i=[[1,0,0],[0,1,0],[m,j,1]];aa(this,J(i,this.m_),false)};q.rotate=function(j){var p=A(j);var m=l(j);var i=[[p,m,0],[-m,p,0],[0,0,1]];aa(this,J(i,this.m_),false)};q.scale=function(m,j){this.arcScaleX_*=m;this.arcScaleY_*=j;var i=[[m,0,0],[0,j,0],[0,0,1]];aa(this,J(i,this.m_),true)};q.transform=function(Z,p,ah,ag,j,i){var m=[[Z,p,0],[ah,ag,0],[j,i,1]];aa(this,J(m,this.m_),true)};q.setTransform=function(ag,Z,ai,ah,p,j){var i=[[ag,Z,0],[ai,ah,0],[p,j,1]];aa(this,i,true)};q.drawText_=function(am,ak,aj,ap,ai){var ao=this.m_,at=1000,j=0,ar=at,ah={x:0,y:0},ag=[];var i=u(E(this.font),this.element_);var p=ac(i);var au=this.element_.currentStyle;var Z=this.textAlign.toLowerCase();switch(Z){case"left":case"center":case"right":break;case"end":Z=au.direction=="ltr"?"right":"left";break;case"start":Z=au.direction=="rtl"?"right":"left";break;default:Z="left"}switch(this.textBaseline){case"hanging":case"top":ah.y=i.size/1.75;break;case"middle":break;default:case null:case"alphabetic":case"ideographic":case"bottom":ah.y=-i.size/2.25;break}switch(Z){case"right":j=at;ar=0.05;break;case"center":j=ar=at/2;break}var aq=V(this,ak+ah.x,aj+ah.y);ag.push('');if(ai){w(this,ag)}else{G(this,ag,{x:-j,y:0},{x:ar,y:i.size})}var an=ao[0][0].toFixed(3)+","+ao[1][0].toFixed(3)+","+ao[0][1].toFixed(3)+","+ao[1][1].toFixed(3)+",0,0";var al=n(aq.x/d)+","+n(aq.y/d);ag.push(' ',' ',' ');this.element_.insertAdjacentHTML("beforeEnd",ag.join(""))};q.fillText=function(m,i,p,j){this.drawText_(m,i,p,j,false)};q.strokeText=function(m,i,p,j){this.drawText_(m,i,p,j,true)};q.measureText=function(m){if(!this.textMeasureEl_){var i=' ';this.element_.insertAdjacentHTML("beforeEnd",i);this.textMeasureEl_=this.element_.lastChild}var j=this.element_.ownerDocument;this.textMeasureEl_.innerHTML="";this.textMeasureEl_.style.font=this.font;this.textMeasureEl_.appendChild(j.createTextNode(m));return{width:this.textMeasureEl_.offsetWidth}};q.clip=function(){};q.arcTo=function(){};q.createPattern=function(j,i){return new T(j,i)};function U(i){this.type_=i;this.x0_=0;this.y0_=0;this.r0_=0;this.x1_=0;this.y1_=0;this.r1_=0;this.colors_=[]}U.prototype.addColorStop=function(j,i){i=F(i);this.colors_.push({offset:j,color:i.color,alpha:i.alpha})};function T(j,i){Q(j);switch(i){case"repeat":case null:case"":this.repetition_="repeat";break;case"repeat-x":case"repeat-y":case"no-repeat":this.repetition_=i;break;default:O("SYNTAX_ERR")}this.src_=j.src;this.width_=j.width;this.height_=j.height}function O(i){throw new P(i)}function Q(i){if(!i||i.nodeType!=1||i.tagName!="IMG"){O("TYPE_MISMATCH_ERR")}if(i.readyState!="complete"){O("INVALID_STATE_ERR")}}function P(i){this.code=this[i];this.message=i+": DOM Exception "+this.code}var X=P.prototype=new Error;X.INDEX_SIZE_ERR=1;X.DOMSTRING_SIZE_ERR=2;X.HIERARCHY_REQUEST_ERR=3;X.WRONG_DOCUMENT_ERR=4;X.INVALID_CHARACTER_ERR=5;X.NO_DATA_ALLOWED_ERR=6;X.NO_MODIFICATION_ALLOWED_ERR=7;X.NOT_FOUND_ERR=8;X.NOT_SUPPORTED_ERR=9;X.INUSE_ATTRIBUTE_ERR=10;X.INVALID_STATE_ERR=11;X.SYNTAX_ERR=12;X.INVALID_MODIFICATION_ERR=13;X.NAMESPACE_ERR=14;X.INVALID_ACCESS_ERR=15;X.VALIDATION_ERR=16;X.TYPE_MISMATCH_ERR=17;G_vmlCanvasManager=e;CanvasRenderingContext2D=D;CanvasGradient=U;CanvasPattern=T;DOMException=P})()};
\ No newline at end of file
diff --git a/djangoproject/static/js/lib/jquery-flot/flot.jquery.json b/djangoproject/static/js/lib/jquery-flot/flot.jquery.json
new file mode 100644
index 000000000..91ac79af1
--- /dev/null
+++ b/djangoproject/static/js/lib/jquery-flot/flot.jquery.json
@@ -0,0 +1,27 @@
+{
+ "name": "flot",
+ "version": "0.8.3",
+ "title": "Flot",
+ "author": {
+ "name": "Ole Laursen",
+ "url": "https://github.com/OleLaursen"
+ },
+ "licenses": [{
+ "type": "MIT",
+ "url": "http://github.com/flot/flot/blob/master/LICENSE.txt"
+ }],
+ "dependencies": {
+ "jquery": ">=1.2.6"
+ },
+ "description": "Flot is a pure JavaScript plotting library for jQuery, with a focus on simple usage, attractive looks and interactive features.",
+ "keywords": ["plot", "chart", "graph", "visualization", "canvas", "graphics"],
+ "homepage": "http://www.flotcharts.org",
+ "docs": "http://github.com/flot/flot/blob/master/API.md",
+ "demo": "http://www.flotcharts.org/flot/examples/",
+ "bugs": "http://github.com/flot/flot/issues",
+ "maintainers": [{
+ "name": "David Schnur",
+ "email": "dnschnur@gmail.com",
+ "url": "http://github.com/dnschnur"
+ }]
+}
diff --git a/djangoproject/static/js/lib/jquery-flot/jquery.colorhelpers.js b/djangoproject/static/js/lib/jquery-flot/jquery.colorhelpers.js
new file mode 100644
index 000000000..b2f6dc4e4
--- /dev/null
+++ b/djangoproject/static/js/lib/jquery-flot/jquery.colorhelpers.js
@@ -0,0 +1,180 @@
+/* Plugin for jQuery for working with colors.
+ *
+ * Version 1.1.
+ *
+ * Inspiration from jQuery color animation plugin by John Resig.
+ *
+ * Released under the MIT license by Ole Laursen, October 2009.
+ *
+ * Examples:
+ *
+ * $.color.parse("#fff").scale('rgb', 0.25).add('a', -0.5).toString()
+ * var c = $.color.extract($("#mydiv"), 'background-color');
+ * console.log(c.r, c.g, c.b, c.a);
+ * $.color.make(100, 50, 25, 0.4).toString() // returns "rgba(100,50,25,0.4)"
+ *
+ * Note that .scale() and .add() return the same modified object
+ * instead of making a new one.
+ *
+ * V. 1.1: Fix error handling so e.g. parsing an empty string does
+ * produce a color rather than just crashing.
+ */
+
+(function($) {
+ $.color = {};
+
+ // construct color object with some convenient chainable helpers
+ $.color.make = function (r, g, b, a) {
+ var o = {};
+ o.r = r || 0;
+ o.g = g || 0;
+ o.b = b || 0;
+ o.a = a != null ? a : 1;
+
+ o.add = function (c, d) {
+ for (var i = 0; i < c.length; ++i)
+ o[c.charAt(i)] += d;
+ return o.normalize();
+ };
+
+ o.scale = function (c, f) {
+ for (var i = 0; i < c.length; ++i)
+ o[c.charAt(i)] *= f;
+ return o.normalize();
+ };
+
+ o.toString = function () {
+ if (o.a >= 1.0) {
+ return "rgb("+[o.r, o.g, o.b].join(",")+")";
+ } else {
+ return "rgba("+[o.r, o.g, o.b, o.a].join(",")+")";
+ }
+ };
+
+ o.normalize = function () {
+ function clamp(min, value, max) {
+ return value < min ? min: (value > max ? max: value);
+ }
+
+ o.r = clamp(0, parseInt(o.r), 255);
+ o.g = clamp(0, parseInt(o.g), 255);
+ o.b = clamp(0, parseInt(o.b), 255);
+ o.a = clamp(0, o.a, 1);
+ return o;
+ };
+
+ o.clone = function () {
+ return $.color.make(o.r, o.b, o.g, o.a);
+ };
+
+ return o.normalize();
+ }
+
+ // extract CSS color property from element, going up in the DOM
+ // if it's "transparent"
+ $.color.extract = function (elem, css) {
+ var c;
+
+ do {
+ c = elem.css(css).toLowerCase();
+ // keep going until we find an element that has color, or
+ // we hit the body or root (have no parent)
+ if (c != '' && c != 'transparent')
+ break;
+ elem = elem.parent();
+ } while (elem.length && !$.nodeName(elem.get(0), "body"));
+
+ // catch Safari's way of signalling transparent
+ if (c == "rgba(0, 0, 0, 0)")
+ c = "transparent";
+
+ return $.color.parse(c);
+ }
+
+ // parse CSS color string (like "rgb(10, 32, 43)" or "#fff"),
+ // returns color object, if parsing failed, you get black (0, 0,
+ // 0) out
+ $.color.parse = function (str) {
+ var res, m = $.color.make;
+
+ // Look for rgb(num,num,num)
+ if (res = /rgb\(\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*\)/.exec(str))
+ return m(parseInt(res[1], 10), parseInt(res[2], 10), parseInt(res[3], 10));
+
+ // Look for rgba(num,num,num,num)
+ if (res = /rgba\(\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*,\s*([0-9]+(?:\.[0-9]+)?)\s*\)/.exec(str))
+ return m(parseInt(res[1], 10), parseInt(res[2], 10), parseInt(res[3], 10), parseFloat(res[4]));
+
+ // Look for rgb(num%,num%,num%)
+ if (res = /rgb\(\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\%\s*\)/.exec(str))
+ return m(parseFloat(res[1])*2.55, parseFloat(res[2])*2.55, parseFloat(res[3])*2.55);
+
+ // Look for rgba(num%,num%,num%,num)
+ if (res = /rgba\(\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\s*\)/.exec(str))
+ return m(parseFloat(res[1])*2.55, parseFloat(res[2])*2.55, parseFloat(res[3])*2.55, parseFloat(res[4]));
+
+ // Look for #a0b1c2
+ if (res = /#([a-fA-F0-9]{2})([a-fA-F0-9]{2})([a-fA-F0-9]{2})/.exec(str))
+ return m(parseInt(res[1], 16), parseInt(res[2], 16), parseInt(res[3], 16));
+
+ // Look for #fff
+ if (res = /#([a-fA-F0-9])([a-fA-F0-9])([a-fA-F0-9])/.exec(str))
+ return m(parseInt(res[1]+res[1], 16), parseInt(res[2]+res[2], 16), parseInt(res[3]+res[3], 16));
+
+ // Otherwise, we're most likely dealing with a named color
+ var name = $.trim(str).toLowerCase();
+ if (name == "transparent")
+ return m(255, 255, 255, 0);
+ else {
+ // default to black
+ res = lookupColors[name] || [0, 0, 0];
+ return m(res[0], res[1], res[2]);
+ }
+ }
+
+ var lookupColors = {
+ aqua:[0,255,255],
+ azure:[240,255,255],
+ beige:[245,245,220],
+ black:[0,0,0],
+ blue:[0,0,255],
+ brown:[165,42,42],
+ cyan:[0,255,255],
+ darkblue:[0,0,139],
+ darkcyan:[0,139,139],
+ darkgrey:[169,169,169],
+ darkgreen:[0,100,0],
+ darkkhaki:[189,183,107],
+ darkmagenta:[139,0,139],
+ darkolivegreen:[85,107,47],
+ darkorange:[255,140,0],
+ darkorchid:[153,50,204],
+ darkred:[139,0,0],
+ darksalmon:[233,150,122],
+ darkviolet:[148,0,211],
+ fuchsia:[255,0,255],
+ gold:[255,215,0],
+ green:[0,128,0],
+ indigo:[75,0,130],
+ khaki:[240,230,140],
+ lightblue:[173,216,230],
+ lightcyan:[224,255,255],
+ lightgreen:[144,238,144],
+ lightgrey:[211,211,211],
+ lightpink:[255,182,193],
+ lightyellow:[255,255,224],
+ lime:[0,255,0],
+ magenta:[255,0,255],
+ maroon:[128,0,0],
+ navy:[0,0,128],
+ olive:[128,128,0],
+ orange:[255,165,0],
+ pink:[255,192,203],
+ purple:[128,0,128],
+ violet:[128,0,128],
+ red:[255,0,0],
+ silver:[192,192,192],
+ white:[255,255,255],
+ yellow:[255,255,0]
+ };
+})(jQuery);
diff --git a/djangoproject/static/js/lib/jquery-flot/jquery.flot.canvas.js b/djangoproject/static/js/lib/jquery-flot/jquery.flot.canvas.js
new file mode 100644
index 000000000..29328d581
--- /dev/null
+++ b/djangoproject/static/js/lib/jquery-flot/jquery.flot.canvas.js
@@ -0,0 +1,345 @@
+/* Flot plugin for drawing all elements of a plot on the canvas.
+
+Copyright (c) 2007-2014 IOLA and Ole Laursen.
+Licensed under the MIT license.
+
+Flot normally produces certain elements, like axis labels and the legend, using
+HTML elements. This permits greater interactivity and customization, and often
+looks better, due to cross-browser canvas text inconsistencies and limitations.
+
+It can also be desirable to render the plot entirely in canvas, particularly
+if the goal is to save it as an image, or if Flot is being used in a context
+where the HTML DOM does not exist, as is the case within Node.js. This plugin
+switches out Flot's standard drawing operations for canvas-only replacements.
+
+Currently the plugin supports only axis labels, but it will eventually allow
+every element of the plot to be rendered directly to canvas.
+
+The plugin supports these options:
+
+{
+ canvas: boolean
+}
+
+The "canvas" option controls whether full canvas drawing is enabled, making it
+possible to toggle on and off. This is useful when a plot uses HTML text in the
+browser, but needs to redraw with canvas text when exporting as an image.
+
+*/
+
+(function($) {
+
+ var options = {
+ canvas: true
+ };
+
+ var render, getTextInfo, addText;
+
+ // Cache the prototype hasOwnProperty for faster access
+
+ var hasOwnProperty = Object.prototype.hasOwnProperty;
+
+ function init(plot, classes) {
+
+ var Canvas = classes.Canvas;
+
+ // We only want to replace the functions once; the second time around
+ // we would just get our new function back. This whole replacing of
+ // prototype functions is a disaster, and needs to be changed ASAP.
+
+ if (render == null) {
+ getTextInfo = Canvas.prototype.getTextInfo,
+ addText = Canvas.prototype.addText,
+ render = Canvas.prototype.render;
+ }
+
+ // Finishes rendering the canvas, including overlaid text
+
+ Canvas.prototype.render = function() {
+
+ if (!plot.getOptions().canvas) {
+ return render.call(this);
+ }
+
+ var context = this.context,
+ cache = this._textCache;
+
+ // For each text layer, render elements marked as active
+
+ context.save();
+ context.textBaseline = "middle";
+
+ for (var layerKey in cache) {
+ if (hasOwnProperty.call(cache, layerKey)) {
+ var layerCache = cache[layerKey];
+ for (var styleKey in layerCache) {
+ if (hasOwnProperty.call(layerCache, styleKey)) {
+ var styleCache = layerCache[styleKey],
+ updateStyles = true;
+ for (var key in styleCache) {
+ if (hasOwnProperty.call(styleCache, key)) {
+
+ var info = styleCache[key],
+ positions = info.positions,
+ lines = info.lines;
+
+ // Since every element at this level of the cache have the
+ // same font and fill styles, we can just change them once
+ // using the values from the first element.
+
+ if (updateStyles) {
+ context.fillStyle = info.font.color;
+ context.font = info.font.definition;
+ updateStyles = false;
+ }
+
+ for (var i = 0, position; position = positions[i]; i++) {
+ if (position.active) {
+ for (var j = 0, line; line = position.lines[j]; j++) {
+ context.fillText(lines[j].text, line[0], line[1]);
+ }
+ } else {
+ positions.splice(i--, 1);
+ }
+ }
+
+ if (positions.length == 0) {
+ delete styleCache[key];
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+
+ context.restore();
+ };
+
+ // Creates (if necessary) and returns a text info object.
+ //
+ // When the canvas option is set, the object looks like this:
+ //
+ // {
+ // width: Width of the text's bounding box.
+ // height: Height of the text's bounding box.
+ // positions: Array of positions at which this text is drawn.
+ // lines: [{
+ // height: Height of this line.
+ // widths: Width of this line.
+ // text: Text on this line.
+ // }],
+ // font: {
+ // definition: Canvas font property string.
+ // color: Color of the text.
+ // },
+ // }
+ //
+ // The positions array contains objects that look like this:
+ //
+ // {
+ // active: Flag indicating whether the text should be visible.
+ // lines: Array of [x, y] coordinates at which to draw the line.
+ // x: X coordinate at which to draw the text.
+ // y: Y coordinate at which to draw the text.
+ // }
+
+ Canvas.prototype.getTextInfo = function(layer, text, font, angle, width) {
+
+ if (!plot.getOptions().canvas) {
+ return getTextInfo.call(this, layer, text, font, angle, width);
+ }
+
+ var textStyle, layerCache, styleCache, info;
+
+ // Cast the value to a string, in case we were given a number
+
+ text = "" + text;
+
+ // If the font is a font-spec object, generate a CSS definition
+
+ if (typeof font === "object") {
+ textStyle = font.style + " " + font.variant + " " + font.weight + " " + font.size + "px " + font.family;
+ } else {
+ textStyle = font;
+ }
+
+ // Retrieve (or create) the cache for the text's layer and styles
+
+ layerCache = this._textCache[layer];
+
+ if (layerCache == null) {
+ layerCache = this._textCache[layer] = {};
+ }
+
+ styleCache = layerCache[textStyle];
+
+ if (styleCache == null) {
+ styleCache = layerCache[textStyle] = {};
+ }
+
+ info = styleCache[text];
+
+ if (info == null) {
+
+ var context = this.context;
+
+ // If the font was provided as CSS, create a div with those
+ // classes and examine it to generate a canvas font spec.
+
+ if (typeof font !== "object") {
+
+ var element = $("
")
+ .css("position", "absolute")
+ .addClass(typeof font === "string" ? font : null)
+ .appendTo(this.getTextLayer(layer));
+
+ font = {
+ lineHeight: element.height(),
+ style: element.css("font-style"),
+ variant: element.css("font-variant"),
+ weight: element.css("font-weight"),
+ family: element.css("font-family"),
+ color: element.css("color")
+ };
+
+ // Setting line-height to 1, without units, sets it equal
+ // to the font-size, even if the font-size is abstract,
+ // like 'smaller'. This enables us to read the real size
+ // via the element's height, working around browsers that
+ // return the literal 'smaller' value.
+
+ font.size = element.css("line-height", 1).height();
+
+ element.remove();
+ }
+
+ textStyle = font.style + " " + font.variant + " " + font.weight + " " + font.size + "px " + font.family;
+
+ // Create a new info object, initializing the dimensions to
+ // zero so we can count them up line-by-line.
+
+ info = styleCache[text] = {
+ width: 0,
+ height: 0,
+ positions: [],
+ lines: [],
+ font: {
+ definition: textStyle,
+ color: font.color
+ }
+ };
+
+ context.save();
+ context.font = textStyle;
+
+ // Canvas can't handle multi-line strings; break on various
+ // newlines, including HTML brs, to build a list of lines.
+ // Note that we could split directly on regexps, but IE < 9 is
+ // broken; revisit when we drop IE 7/8 support.
+
+ var lines = (text + "").replace(/ |\r\n|\r/g, "\n").split("\n");
+
+ for (var i = 0; i < lines.length; ++i) {
+
+ var lineText = lines[i],
+ measured = context.measureText(lineText);
+
+ info.width = Math.max(measured.width, info.width);
+ info.height += font.lineHeight;
+
+ info.lines.push({
+ text: lineText,
+ width: measured.width,
+ height: font.lineHeight
+ });
+ }
+
+ context.restore();
+ }
+
+ return info;
+ };
+
+ // Adds a text string to the canvas text overlay.
+
+ Canvas.prototype.addText = function(layer, x, y, text, font, angle, width, halign, valign) {
+
+ if (!plot.getOptions().canvas) {
+ return addText.call(this, layer, x, y, text, font, angle, width, halign, valign);
+ }
+
+ var info = this.getTextInfo(layer, text, font, angle, width),
+ positions = info.positions,
+ lines = info.lines;
+
+ // Text is drawn with baseline 'middle', which we need to account
+ // for by adding half a line's height to the y position.
+
+ y += info.height / lines.length / 2;
+
+ // Tweak the initial y-position to match vertical alignment
+
+ if (valign == "middle") {
+ y = Math.round(y - info.height / 2);
+ } else if (valign == "bottom") {
+ y = Math.round(y - info.height);
+ } else {
+ y = Math.round(y);
+ }
+
+ // FIXME: LEGACY BROWSER FIX
+ // AFFECTS: Opera < 12.00
+
+ // Offset the y coordinate, since Opera is off pretty
+ // consistently compared to the other browsers.
+
+ if (!!(window.opera && window.opera.version().split(".")[0] < 12)) {
+ y -= 2;
+ }
+
+ // Determine whether this text already exists at this position.
+ // If so, mark it for inclusion in the next render pass.
+
+ for (var i = 0, position; position = positions[i]; i++) {
+ if (position.x == x && position.y == y) {
+ position.active = true;
+ return;
+ }
+ }
+
+ // If the text doesn't exist at this position, create a new entry
+
+ position = {
+ active: true,
+ lines: [],
+ x: x,
+ y: y
+ };
+
+ positions.push(position);
+
+ // Fill in the x & y positions of each line, adjusting them
+ // individually for horizontal alignment.
+
+ for (var i = 0, line; line = lines[i]; i++) {
+ if (halign == "center") {
+ position.lines.push([Math.round(x - line.width / 2), y]);
+ } else if (halign == "right") {
+ position.lines.push([Math.round(x - line.width), y]);
+ } else {
+ position.lines.push([Math.round(x), y]);
+ }
+ y += line.height;
+ }
+ };
+ }
+
+ $.plot.plugins.push({
+ init: init,
+ options: options,
+ name: "canvas",
+ version: "1.0"
+ });
+
+})(jQuery);
diff --git a/djangoproject/static/js/lib/jquery-flot/jquery.flot.categories.js b/djangoproject/static/js/lib/jquery-flot/jquery.flot.categories.js
new file mode 100644
index 000000000..2f9b25797
--- /dev/null
+++ b/djangoproject/static/js/lib/jquery-flot/jquery.flot.categories.js
@@ -0,0 +1,190 @@
+/* Flot plugin for plotting textual data or categories.
+
+Copyright (c) 2007-2014 IOLA and Ole Laursen.
+Licensed under the MIT license.
+
+Consider a dataset like [["February", 34], ["March", 20], ...]. This plugin
+allows you to plot such a dataset directly.
+
+To enable it, you must specify mode: "categories" on the axis with the textual
+labels, e.g.
+
+ $.plot("#placeholder", data, { xaxis: { mode: "categories" } });
+
+By default, the labels are ordered as they are met in the data series. If you
+need a different ordering, you can specify "categories" on the axis options
+and list the categories there:
+
+ xaxis: {
+ mode: "categories",
+ categories: ["February", "March", "April"]
+ }
+
+If you need to customize the distances between the categories, you can specify
+"categories" as an object mapping labels to values
+
+ xaxis: {
+ mode: "categories",
+ categories: { "February": 1, "March": 3, "April": 4 }
+ }
+
+If you don't specify all categories, the remaining categories will be numbered
+from the max value plus 1 (with a spacing of 1 between each).
+
+Internally, the plugin works by transforming the input data through an auto-
+generated mapping where the first category becomes 0, the second 1, etc.
+Hence, a point like ["February", 34] becomes [0, 34] internally in Flot (this
+is visible in hover and click events that return numbers rather than the
+category labels). The plugin also overrides the tick generator to spit out the
+categories as ticks instead of the values.
+
+If you need to map a value back to its label, the mapping is always accessible
+as "categories" on the axis object, e.g. plot.getAxes().xaxis.categories.
+
+*/
+
+(function ($) {
+ var options = {
+ xaxis: {
+ categories: null
+ },
+ yaxis: {
+ categories: null
+ }
+ };
+
+ function processRawData(plot, series, data, datapoints) {
+ // if categories are enabled, we need to disable
+ // auto-transformation to numbers so the strings are intact
+ // for later processing
+
+ var xCategories = series.xaxis.options.mode == "categories",
+ yCategories = series.yaxis.options.mode == "categories";
+
+ if (!(xCategories || yCategories))
+ return;
+
+ var format = datapoints.format;
+
+ if (!format) {
+ // FIXME: auto-detection should really not be defined here
+ var s = series;
+ format = [];
+ format.push({ x: true, number: true, required: true });
+ format.push({ y: true, number: true, required: true });
+
+ if (s.bars.show || (s.lines.show && s.lines.fill)) {
+ var autoscale = !!((s.bars.show && s.bars.zero) || (s.lines.show && s.lines.zero));
+ format.push({ y: true, number: true, required: false, defaultValue: 0, autoscale: autoscale });
+ if (s.bars.horizontal) {
+ delete format[format.length - 1].y;
+ format[format.length - 1].x = true;
+ }
+ }
+
+ datapoints.format = format;
+ }
+
+ for (var m = 0; m < format.length; ++m) {
+ if (format[m].x && xCategories)
+ format[m].number = false;
+
+ if (format[m].y && yCategories)
+ format[m].number = false;
+ }
+ }
+
+ function getNextIndex(categories) {
+ var index = -1;
+
+ for (var v in categories)
+ if (categories[v] > index)
+ index = categories[v];
+
+ return index + 1;
+ }
+
+ function categoriesTickGenerator(axis) {
+ var res = [];
+ for (var label in axis.categories) {
+ var v = axis.categories[label];
+ if (v >= axis.min && v <= axis.max)
+ res.push([v, label]);
+ }
+
+ res.sort(function (a, b) { return a[0] - b[0]; });
+
+ return res;
+ }
+
+ function setupCategoriesForAxis(series, axis, datapoints) {
+ if (series[axis].options.mode != "categories")
+ return;
+
+ if (!series[axis].categories) {
+ // parse options
+ var c = {}, o = series[axis].options.categories || {};
+ if ($.isArray(o)) {
+ for (var i = 0; i < o.length; ++i)
+ c[o[i]] = i;
+ }
+ else {
+ for (var v in o)
+ c[v] = o[v];
+ }
+
+ series[axis].categories = c;
+ }
+
+ // fix ticks
+ if (!series[axis].options.ticks)
+ series[axis].options.ticks = categoriesTickGenerator;
+
+ transformPointsOnAxis(datapoints, axis, series[axis].categories);
+ }
+
+ function transformPointsOnAxis(datapoints, axis, categories) {
+ // go through the points, transforming them
+ var points = datapoints.points,
+ ps = datapoints.pointsize,
+ format = datapoints.format,
+ formatColumn = axis.charAt(0),
+ index = getNextIndex(categories);
+
+ for (var i = 0; i < points.length; i += ps) {
+ if (points[i] == null)
+ continue;
+
+ for (var m = 0; m < ps; ++m) {
+ var val = points[i + m];
+
+ if (val == null || !format[m][formatColumn])
+ continue;
+
+ if (!(val in categories)) {
+ categories[val] = index;
+ ++index;
+ }
+
+ points[i + m] = categories[val];
+ }
+ }
+ }
+
+ function processDatapoints(plot, series, datapoints) {
+ setupCategoriesForAxis(series, "xaxis", datapoints);
+ setupCategoriesForAxis(series, "yaxis", datapoints);
+ }
+
+ function init(plot) {
+ plot.hooks.processRawData.push(processRawData);
+ plot.hooks.processDatapoints.push(processDatapoints);
+ }
+
+ $.plot.plugins.push({
+ init: init,
+ options: options,
+ name: 'categories',
+ version: '1.0'
+ });
+})(jQuery);
diff --git a/djangoproject/static/js/lib/jquery-flot/jquery.flot.concat.js b/djangoproject/static/js/lib/jquery-flot/jquery.flot.concat.js
new file mode 100644
index 000000000..8f4680c64
--- /dev/null
+++ b/djangoproject/static/js/lib/jquery-flot/jquery.flot.concat.js
@@ -0,0 +1,3600 @@
+/* Javascript plotting library for jQuery, version 0.8.3.
+
+Copyright (c) 2007-2014 IOLA and Ole Laursen.
+Licensed under the MIT license.
+
+*/
+
+// first an inline dependency, jquery.colorhelpers.js, we inline it here
+// for convenience
+
+/* Plugin for jQuery for working with colors.
+ *
+ * Version 1.1.
+ *
+ * Inspiration from jQuery color animation plugin by John Resig.
+ *
+ * Released under the MIT license by Ole Laursen, October 2009.
+ *
+ * Examples:
+ *
+ * $.color.parse("#fff").scale('rgb', 0.25).add('a', -0.5).toString()
+ * var c = $.color.extract($("#mydiv"), 'background-color');
+ * console.log(c.r, c.g, c.b, c.a);
+ * $.color.make(100, 50, 25, 0.4).toString() // returns "rgba(100,50,25,0.4)"
+ *
+ * Note that .scale() and .add() return the same modified object
+ * instead of making a new one.
+ *
+ * V. 1.1: Fix error handling so e.g. parsing an empty string does
+ * produce a color rather than just crashing.
+ */
+(function($){$.color={};$.color.make=function(r,g,b,a){var o={};o.r=r||0;o.g=g||0;o.b=b||0;o.a=a!=null?a:1;o.add=function(c,d){for(var i=0;i=1){return"rgb("+[o.r,o.g,o.b].join(",")+")"}else{return"rgba("+[o.r,o.g,o.b,o.a].join(",")+")"}};o.normalize=function(){function clamp(min,value,max){return valuemax?max:value}o.r=clamp(0,parseInt(o.r),255);o.g=clamp(0,parseInt(o.g),255);o.b=clamp(0,parseInt(o.b),255);o.a=clamp(0,o.a,1);return o};o.clone=function(){return $.color.make(o.r,o.b,o.g,o.a)};return o.normalize()};$.color.extract=function(elem,css){var c;do{c=elem.css(css).toLowerCase();if(c!=""&&c!="transparent")break;elem=elem.parent()}while(elem.length&&!$.nodeName(elem.get(0),"body"));if(c=="rgba(0, 0, 0, 0)")c="transparent";return $.color.parse(c)};$.color.parse=function(str){var res,m=$.color.make;if(res=/rgb\(\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*\)/.exec(str))return m(parseInt(res[1],10),parseInt(res[2],10),parseInt(res[3],10));if(res=/rgba\(\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*,\s*([0-9]+(?:\.[0-9]+)?)\s*\)/.exec(str))return m(parseInt(res[1],10),parseInt(res[2],10),parseInt(res[3],10),parseFloat(res[4]));if(res=/rgb\(\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\%\s*\)/.exec(str))return m(parseFloat(res[1])*2.55,parseFloat(res[2])*2.55,parseFloat(res[3])*2.55);if(res=/rgba\(\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\s*\)/.exec(str))return m(parseFloat(res[1])*2.55,parseFloat(res[2])*2.55,parseFloat(res[3])*2.55,parseFloat(res[4]));if(res=/#([a-fA-F0-9]{2})([a-fA-F0-9]{2})([a-fA-F0-9]{2})/.exec(str))return m(parseInt(res[1],16),parseInt(res[2],16),parseInt(res[3],16));if(res=/#([a-fA-F0-9])([a-fA-F0-9])([a-fA-F0-9])/.exec(str))return m(parseInt(res[1]+res[1],16),parseInt(res[2]+res[2],16),parseInt(res[3]+res[3],16));var name=$.trim(str).toLowerCase();if(name=="transparent")return m(255,255,255,0);else{res=lookupColors[name]||[0,0,0];return m(res[0],res[1],res[2])}};var lookupColors={aqua:[0,255,255],azure:[240,255,255],beige:[245,245,220],black:[0,0,0],blue:[0,0,255],brown:[165,42,42],cyan:[0,255,255],darkblue:[0,0,139],darkcyan:[0,139,139],darkgrey:[169,169,169],darkgreen:[0,100,0],darkkhaki:[189,183,107],darkmagenta:[139,0,139],darkolivegreen:[85,107,47],darkorange:[255,140,0],darkorchid:[153,50,204],darkred:[139,0,0],darksalmon:[233,150,122],darkviolet:[148,0,211],fuchsia:[255,0,255],gold:[255,215,0],green:[0,128,0],indigo:[75,0,130],khaki:[240,230,140],lightblue:[173,216,230],lightcyan:[224,255,255],lightgreen:[144,238,144],lightgrey:[211,211,211],lightpink:[255,182,193],lightyellow:[255,255,224],lime:[0,255,0],magenta:[255,0,255],maroon:[128,0,0],navy:[0,0,128],olive:[128,128,0],orange:[255,165,0],pink:[255,192,203],purple:[128,0,128],violet:[128,0,128],red:[255,0,0],silver:[192,192,192],white:[255,255,255],yellow:[255,255,0]}})(jQuery);
+
+// the actual Flot code
+(function($) {
+
+ // Cache the prototype hasOwnProperty for faster access
+
+ var hasOwnProperty = Object.prototype.hasOwnProperty;
+
+ // A shim to provide 'detach' to jQuery versions prior to 1.4. Using a DOM
+ // operation produces the same effect as detach, i.e. removing the element
+ // without touching its jQuery data.
+
+ // Do not merge this into Flot 0.9, since it requires jQuery 1.4.4+.
+
+ if (!$.fn.detach) {
+ $.fn.detach = function() {
+ return this.each(function() {
+ if (this.parentNode) {
+ this.parentNode.removeChild( this );
+ }
+ });
+ };
+ }
+
+ ///////////////////////////////////////////////////////////////////////////
+ // The Canvas object is a wrapper around an HTML5 tag.
+ //
+ // @constructor
+ // @param {string} cls List of classes to apply to the canvas.
+ // @param {element} container Element onto which to append the canvas.
+ //
+ // Requiring a container is a little iffy, but unfortunately canvas
+ // operations don't work unless the canvas is attached to the DOM.
+
+ function Canvas(cls, container) {
+
+ var element = container.children("." + cls)[0];
+
+ if (element == null) {
+
+ element = document.createElement("canvas");
+ element.className = cls;
+
+ $(element).css({ direction: "ltr", position: "absolute", left: 0, top: 0 })
+ .appendTo(container);
+
+ // If HTML5 Canvas isn't available, fall back to [Ex|Flash]canvas
+
+ if (!element.getContext) {
+ if (window.G_vmlCanvasManager) {
+ element = window.G_vmlCanvasManager.initElement(element);
+ } else {
+ throw new Error("Canvas is not available. If you're using IE with a fall-back such as Excanvas, then there's either a mistake in your conditional include, or the page has no DOCTYPE and is rendering in Quirks Mode.");
+ }
+ }
+ }
+
+ this.element = element;
+
+ var context = this.context = element.getContext("2d");
+
+ // Determine the screen's ratio of physical to device-independent
+ // pixels. This is the ratio between the canvas width that the browser
+ // advertises and the number of pixels actually present in that space.
+
+ // The iPhone 4, for example, has a device-independent width of 320px,
+ // but its screen is actually 640px wide. It therefore has a pixel
+ // ratio of 2, while most normal devices have a ratio of 1.
+
+ var devicePixelRatio = window.devicePixelRatio || 1,
+ backingStoreRatio =
+ context.webkitBackingStorePixelRatio ||
+ context.mozBackingStorePixelRatio ||
+ context.msBackingStorePixelRatio ||
+ context.oBackingStorePixelRatio ||
+ context.backingStorePixelRatio || 1;
+
+ this.pixelRatio = devicePixelRatio / backingStoreRatio;
+
+ // Size the canvas to match the internal dimensions of its container
+
+ this.resize(container.width(), container.height());
+
+ // Collection of HTML div layers for text overlaid onto the canvas
+
+ this.textContainer = null;
+ this.text = {};
+
+ // Cache of text fragments and metrics, so we can avoid expensively
+ // re-calculating them when the plot is re-rendered in a loop.
+
+ this._textCache = {};
+ }
+
+ // Resizes the canvas to the given dimensions.
+ //
+ // @param {number} width New width of the canvas, in pixels.
+ // @param {number} width New height of the canvas, in pixels.
+
+ Canvas.prototype.resize = function(width, height) {
+
+ if (width <= 0 || height <= 0) {
+ throw new Error("Invalid dimensions for plot, width = " + width + ", height = " + height);
+ }
+
+ var element = this.element,
+ context = this.context,
+ pixelRatio = this.pixelRatio;
+
+ // Resize the canvas, increasing its density based on the display's
+ // pixel ratio; basically giving it more pixels without increasing the
+ // size of its element, to take advantage of the fact that retina
+ // displays have that many more pixels in the same advertised space.
+
+ // Resizing should reset the state (excanvas seems to be buggy though)
+
+ if (this.width != width) {
+ element.width = width * pixelRatio;
+ element.style.width = width + "px";
+ this.width = width;
+ }
+
+ if (this.height != height) {
+ element.height = height * pixelRatio;
+ element.style.height = height + "px";
+ this.height = height;
+ }
+
+ // Save the context, so we can reset in case we get replotted. The
+ // restore ensure that we're really back at the initial state, and
+ // should be safe even if we haven't saved the initial state yet.
+
+ context.restore();
+ context.save();
+
+ // Scale the coordinate space to match the display density; so even though we
+ // may have twice as many pixels, we still want lines and other drawing to
+ // appear at the same size; the extra pixels will just make them crisper.
+
+ context.scale(pixelRatio, pixelRatio);
+ };
+
+ // Clears the entire canvas area, not including any overlaid HTML text
+
+ Canvas.prototype.clear = function() {
+ this.context.clearRect(0, 0, this.width, this.height);
+ };
+
+ // Finishes rendering the canvas, including managing the text overlay.
+
+ Canvas.prototype.render = function() {
+
+ var cache = this._textCache;
+
+ // For each text layer, add elements marked as active that haven't
+ // already been rendered, and remove those that are no longer active.
+
+ for (var layerKey in cache) {
+ if (hasOwnProperty.call(cache, layerKey)) {
+
+ var layer = this.getTextLayer(layerKey),
+ layerCache = cache[layerKey];
+
+ layer.hide();
+
+ for (var styleKey in layerCache) {
+ if (hasOwnProperty.call(layerCache, styleKey)) {
+ var styleCache = layerCache[styleKey];
+ for (var key in styleCache) {
+ if (hasOwnProperty.call(styleCache, key)) {
+
+ var positions = styleCache[key].positions;
+
+ for (var i = 0, position; position = positions[i]; i++) {
+ if (position.active) {
+ if (!position.rendered) {
+ layer.append(position.element);
+ position.rendered = true;
+ }
+ } else {
+ positions.splice(i--, 1);
+ if (position.rendered) {
+ position.element.detach();
+ }
+ }
+ }
+
+ if (positions.length == 0) {
+ delete styleCache[key];
+ }
+ }
+ }
+ }
+ }
+
+ layer.show();
+ }
+ }
+ };
+
+ // Creates (if necessary) and returns the text overlay container.
+ //
+ // @param {string} classes String of space-separated CSS classes used to
+ // uniquely identify the text layer.
+ // @return {object} The jQuery-wrapped text-layer div.
+
+ Canvas.prototype.getTextLayer = function(classes) {
+
+ var layer = this.text[classes];
+
+ // Create the text layer if it doesn't exist
+
+ if (layer == null) {
+
+ // Create the text layer container, if it doesn't exist
+
+ if (this.textContainer == null) {
+ this.textContainer = $("
")
+ .css({
+ position: "absolute",
+ top: 0,
+ left: 0,
+ bottom: 0,
+ right: 0,
+ 'font-size': "smaller",
+ color: "#545454"
+ })
+ .insertAfter(this.element);
+ }
+
+ layer = this.text[classes] = $("
")
+ .addClass(classes)
+ .css({
+ position: "absolute",
+ top: 0,
+ left: 0,
+ bottom: 0,
+ right: 0
+ })
+ .appendTo(this.textContainer);
+ }
+
+ return layer;
+ };
+
+ // Creates (if necessary) and returns a text info object.
+ //
+ // The object looks like this:
+ //
+ // {
+ // width: Width of the text's wrapper div.
+ // height: Height of the text's wrapper div.
+ // element: The jQuery-wrapped HTML div containing the text.
+ // positions: Array of positions at which this text is drawn.
+ // }
+ //
+ // The positions array contains objects that look like this:
+ //
+ // {
+ // active: Flag indicating whether the text should be visible.
+ // rendered: Flag indicating whether the text is currently visible.
+ // element: The jQuery-wrapped HTML div containing the text.
+ // x: X coordinate at which to draw the text.
+ // y: Y coordinate at which to draw the text.
+ // }
+ //
+ // Each position after the first receives a clone of the original element.
+ //
+ // The idea is that that the width, height, and general 'identity' of the
+ // text is constant no matter where it is placed; the placements are a
+ // secondary property.
+ //
+ // Canvas maintains a cache of recently-used text info objects; getTextInfo
+ // either returns the cached element or creates a new entry.
+ //
+ // @param {string} layer A string of space-separated CSS classes uniquely
+ // identifying the layer containing this text.
+ // @param {string} text Text string to retrieve info for.
+ // @param {(string|object)=} font Either a string of space-separated CSS
+ // classes or a font-spec object, defining the text's font and style.
+ // @param {number=} angle Angle at which to rotate the text, in degrees.
+ // Angle is currently unused, it will be implemented in the future.
+ // @param {number=} width Maximum width of the text before it wraps.
+ // @return {object} a text info object.
+
+ Canvas.prototype.getTextInfo = function(layer, text, font, angle, width) {
+
+ var textStyle, layerCache, styleCache, info;
+
+ // Cast the value to a string, in case we were given a number or such
+
+ text = "" + text;
+
+ // If the font is a font-spec object, generate a CSS font definition
+
+ if (typeof font === "object") {
+ textStyle = font.style + " " + font.variant + " " + font.weight + " " + font.size + "px/" + font.lineHeight + "px " + font.family;
+ } else {
+ textStyle = font;
+ }
+
+ // Retrieve (or create) the cache for the text's layer and styles
+
+ layerCache = this._textCache[layer];
+
+ if (layerCache == null) {
+ layerCache = this._textCache[layer] = {};
+ }
+
+ styleCache = layerCache[textStyle];
+
+ if (styleCache == null) {
+ styleCache = layerCache[textStyle] = {};
+ }
+
+ info = styleCache[text];
+
+ // If we can't find a matching element in our cache, create a new one
+
+ if (info == null) {
+
+ var element = $("
").html(text)
+ .css({
+ position: "absolute",
+ 'max-width': width,
+ top: -9999
+ })
+ .appendTo(this.getTextLayer(layer));
+
+ if (typeof font === "object") {
+ element.css({
+ font: textStyle,
+ color: font.color
+ });
+ } else if (typeof font === "string") {
+ element.addClass(font);
+ }
+
+ info = styleCache[text] = {
+ width: element.outerWidth(true),
+ height: element.outerHeight(true),
+ element: element,
+ positions: []
+ };
+
+ element.detach();
+ }
+
+ return info;
+ };
+
+ // Adds a text string to the canvas text overlay.
+ //
+ // The text isn't drawn immediately; it is marked as rendering, which will
+ // result in its addition to the canvas on the next render pass.
+ //
+ // @param {string} layer A string of space-separated CSS classes uniquely
+ // identifying the layer containing this text.
+ // @param {number} x X coordinate at which to draw the text.
+ // @param {number} y Y coordinate at which to draw the text.
+ // @param {string} text Text string to draw.
+ // @param {(string|object)=} font Either a string of space-separated CSS
+ // classes or a font-spec object, defining the text's font and style.
+ // @param {number=} angle Angle at which to rotate the text, in degrees.
+ // Angle is currently unused, it will be implemented in the future.
+ // @param {number=} width Maximum width of the text before it wraps.
+ // @param {string=} halign Horizontal alignment of the text; either "left",
+ // "center" or "right".
+ // @param {string=} valign Vertical alignment of the text; either "top",
+ // "middle" or "bottom".
+
+ Canvas.prototype.addText = function(layer, x, y, text, font, angle, width, halign, valign) {
+
+ var info = this.getTextInfo(layer, text, font, angle, width),
+ positions = info.positions;
+
+ // Tweak the div's position to match the text's alignment
+
+ if (halign == "center") {
+ x -= info.width / 2;
+ } else if (halign == "right") {
+ x -= info.width;
+ }
+
+ if (valign == "middle") {
+ y -= info.height / 2;
+ } else if (valign == "bottom") {
+ y -= info.height;
+ }
+
+ // Determine whether this text already exists at this position.
+ // If so, mark it for inclusion in the next render pass.
+
+ for (var i = 0, position; position = positions[i]; i++) {
+ if (position.x == x && position.y == y) {
+ position.active = true;
+ return;
+ }
+ }
+
+ // If the text doesn't exist at this position, create a new entry
+
+ // For the very first position we'll re-use the original element,
+ // while for subsequent ones we'll clone it.
+
+ position = {
+ active: true,
+ rendered: false,
+ element: positions.length ? info.element.clone() : info.element,
+ x: x,
+ y: y
+ };
+
+ positions.push(position);
+
+ // Move the element to its final position within the container
+
+ position.element.css({
+ top: Math.round(y),
+ left: Math.round(x),
+ 'text-align': halign // In case the text wraps
+ });
+ };
+
+ // Removes one or more text strings from the canvas text overlay.
+ //
+ // If no parameters are given, all text within the layer is removed.
+ //
+ // Note that the text is not immediately removed; it is simply marked as
+ // inactive, which will result in its removal on the next render pass.
+ // This avoids the performance penalty for 'clear and redraw' behavior,
+ // where we potentially get rid of all text on a layer, but will likely
+ // add back most or all of it later, as when redrawing axes, for example.
+ //
+ // @param {string} layer A string of space-separated CSS classes uniquely
+ // identifying the layer containing this text.
+ // @param {number=} x X coordinate of the text.
+ // @param {number=} y Y coordinate of the text.
+ // @param {string=} text Text string to remove.
+ // @param {(string|object)=} font Either a string of space-separated CSS
+ // classes or a font-spec object, defining the text's font and style.
+ // @param {number=} angle Angle at which the text is rotated, in degrees.
+ // Angle is currently unused, it will be implemented in the future.
+
+ Canvas.prototype.removeText = function(layer, x, y, text, font, angle) {
+ if (text == null) {
+ var layerCache = this._textCache[layer];
+ if (layerCache != null) {
+ for (var styleKey in layerCache) {
+ if (hasOwnProperty.call(layerCache, styleKey)) {
+ var styleCache = layerCache[styleKey];
+ for (var key in styleCache) {
+ if (hasOwnProperty.call(styleCache, key)) {
+ var positions = styleCache[key].positions;
+ for (var i = 0, position; position = positions[i]; i++) {
+ position.active = false;
+ }
+ }
+ }
+ }
+ }
+ }
+ } else {
+ var positions = this.getTextInfo(layer, text, font, angle).positions;
+ for (var i = 0, position; position = positions[i]; i++) {
+ if (position.x == x && position.y == y) {
+ position.active = false;
+ }
+ }
+ }
+ };
+
+ ///////////////////////////////////////////////////////////////////////////
+ // The top-level container for the entire plot.
+
+ function Plot(placeholder, data_, options_, plugins) {
+ // data is on the form:
+ // [ series1, series2 ... ]
+ // where series is either just the data as [ [x1, y1], [x2, y2], ... ]
+ // or { data: [ [x1, y1], [x2, y2], ... ], label: "some label", ... }
+
+ var series = [],
+ options = {
+ // the color theme used for graphs
+ colors: ["#edc240", "#afd8f8", "#cb4b4b", "#4da74d", "#9440ed"],
+ legend: {
+ show: true,
+ noColumns: 1, // number of colums in legend table
+ labelFormatter: null, // fn: string -> string
+ labelBoxBorderColor: "#ccc", // border color for the little label boxes
+ container: null, // container (as jQuery object) to put legend in, null means default on top of graph
+ position: "ne", // position of default legend container within plot
+ margin: 5, // distance from grid edge to default legend container within plot
+ backgroundColor: null, // null means auto-detect
+ backgroundOpacity: 0.85, // set to 0 to avoid background
+ sorted: null // default to no legend sorting
+ },
+ xaxis: {
+ show: null, // null = auto-detect, true = always, false = never
+ position: "bottom", // or "top"
+ mode: null, // null or "time"
+ font: null, // null (derived from CSS in placeholder) or object like { size: 11, lineHeight: 13, style: "italic", weight: "bold", family: "sans-serif", variant: "small-caps" }
+ color: null, // base color, labels, ticks
+ tickColor: null, // possibly different color of ticks, e.g. "rgba(0,0,0,0.15)"
+ transform: null, // null or f: number -> number to transform axis
+ inverseTransform: null, // if transform is set, this should be the inverse function
+ min: null, // min. value to show, null means set automatically
+ max: null, // max. value to show, null means set automatically
+ autoscaleMargin: null, // margin in % to add if auto-setting min/max
+ ticks: null, // either [1, 3] or [[1, "a"], 3] or (fn: axis info -> ticks) or app. number of ticks for auto-ticks
+ tickFormatter: null, // fn: number -> string
+ labelWidth: null, // size of tick labels in pixels
+ labelHeight: null,
+ reserveSpace: null, // whether to reserve space even if axis isn't shown
+ tickLength: null, // size in pixels of ticks, or "full" for whole line
+ alignTicksWithAxis: null, // axis number or null for no sync
+ tickDecimals: null, // no. of decimals, null means auto
+ tickSize: null, // number or [number, "unit"]
+ minTickSize: null // number or [number, "unit"]
+ },
+ yaxis: {
+ autoscaleMargin: 0.02,
+ position: "left" // or "right"
+ },
+ xaxes: [],
+ yaxes: [],
+ series: {
+ points: {
+ show: false,
+ radius: 3,
+ lineWidth: 2, // in pixels
+ fill: true,
+ fillColor: "#ffffff",
+ symbol: "circle" // or callback
+ },
+ lines: {
+ // we don't put in show: false so we can see
+ // whether lines were actively disabled
+ lineWidth: 2, // in pixels
+ fill: false,
+ fillColor: null,
+ steps: false
+ // Omit 'zero', so we can later default its value to
+ // match that of the 'fill' option.
+ },
+ bars: {
+ show: false,
+ lineWidth: 2, // in pixels
+ barWidth: 1, // in units of the x axis
+ fill: true,
+ fillColor: null,
+ align: "left", // "left", "right", or "center"
+ horizontal: false,
+ zero: true
+ },
+ shadowSize: 3,
+ highlightColor: null
+ },
+ grid: {
+ show: true,
+ aboveData: false,
+ color: "#545454", // primary color used for outline and labels
+ backgroundColor: null, // null for transparent, else color
+ borderColor: null, // set if different from the grid color
+ tickColor: null, // color for the ticks, e.g. "rgba(0,0,0,0.15)"
+ margin: 0, // distance from the canvas edge to the grid
+ labelMargin: 5, // in pixels
+ axisMargin: 8, // in pixels
+ borderWidth: 2, // in pixels
+ minBorderMargin: null, // in pixels, null means taken from points radius
+ markings: null, // array of ranges or fn: axes -> array of ranges
+ markingsColor: "#f4f4f4",
+ markingsLineWidth: 2,
+ // interactive stuff
+ clickable: false,
+ hoverable: false,
+ autoHighlight: true, // highlight in case mouse is near
+ mouseActiveRadius: 10 // how far the mouse can be away to activate an item
+ },
+ interaction: {
+ redrawOverlayInterval: 1000/60 // time between updates, -1 means in same flow
+ },
+ hooks: {}
+ },
+ surface = null, // the canvas for the plot itself
+ overlay = null, // canvas for interactive stuff on top of plot
+ eventHolder = null, // jQuery object that events should be bound to
+ ctx = null, octx = null,
+ xaxes = [], yaxes = [],
+ plotOffset = { left: 0, right: 0, top: 0, bottom: 0},
+ plotWidth = 0, plotHeight = 0,
+ hooks = {
+ processOptions: [],
+ processRawData: [],
+ processDatapoints: [],
+ processOffset: [],
+ drawBackground: [],
+ drawSeries: [],
+ draw: [],
+ bindEvents: [],
+ drawOverlay: [],
+ shutdown: []
+ },
+ plot = this;
+
+ // public functions
+ plot.setData = setData;
+ plot.setupGrid = setupGrid;
+ plot.draw = draw;
+ plot.getPlaceholder = function() { return placeholder; };
+ plot.getCanvas = function() { return surface.element; };
+ plot.getPlotOffset = function() { return plotOffset; };
+ plot.width = function () { return plotWidth; };
+ plot.height = function () { return plotHeight; };
+ plot.offset = function () {
+ var o = eventHolder.offset();
+ o.left += plotOffset.left;
+ o.top += plotOffset.top;
+ return o;
+ };
+ plot.getData = function () { return series; };
+ plot.getAxes = function () {
+ var res = {}, i;
+ $.each(xaxes.concat(yaxes), function (_, axis) {
+ if (axis)
+ res[axis.direction + (axis.n != 1 ? axis.n : "") + "axis"] = axis;
+ });
+ return res;
+ };
+ plot.getXAxes = function () { return xaxes; };
+ plot.getYAxes = function () { return yaxes; };
+ plot.c2p = canvasToAxisCoords;
+ plot.p2c = axisToCanvasCoords;
+ plot.getOptions = function () { return options; };
+ plot.highlight = highlight;
+ plot.unhighlight = unhighlight;
+ plot.triggerRedrawOverlay = triggerRedrawOverlay;
+ plot.pointOffset = function(point) {
+ return {
+ left: parseInt(xaxes[axisNumber(point, "x") - 1].p2c(+point.x) + plotOffset.left, 10),
+ top: parseInt(yaxes[axisNumber(point, "y") - 1].p2c(+point.y) + plotOffset.top, 10)
+ };
+ };
+ plot.shutdown = shutdown;
+ plot.destroy = function () {
+ shutdown();
+ placeholder.removeData("plot").empty();
+
+ series = [];
+ options = null;
+ surface = null;
+ overlay = null;
+ eventHolder = null;
+ ctx = null;
+ octx = null;
+ xaxes = [];
+ yaxes = [];
+ hooks = null;
+ highlights = [];
+ plot = null;
+ };
+ plot.resize = function () {
+ var width = placeholder.width(),
+ height = placeholder.height();
+ surface.resize(width, height);
+ overlay.resize(width, height);
+ };
+
+ // public attributes
+ plot.hooks = hooks;
+
+ // initialize
+ initPlugins(plot);
+ parseOptions(options_);
+ setupCanvases();
+ setData(data_);
+ setupGrid();
+ draw();
+ bindEvents();
+
+
+ function executeHooks(hook, args) {
+ args = [plot].concat(args);
+ for (var i = 0; i < hook.length; ++i)
+ hook[i].apply(this, args);
+ }
+
+ function initPlugins() {
+
+ // References to key classes, allowing plugins to modify them
+
+ var classes = {
+ Canvas: Canvas
+ };
+
+ for (var i = 0; i < plugins.length; ++i) {
+ var p = plugins[i];
+ p.init(plot, classes);
+ if (p.options)
+ $.extend(true, options, p.options);
+ }
+ }
+
+ function parseOptions(opts) {
+
+ $.extend(true, options, opts);
+
+ // $.extend merges arrays, rather than replacing them. When less
+ // colors are provided than the size of the default palette, we
+ // end up with those colors plus the remaining defaults, which is
+ // not expected behavior; avoid it by replacing them here.
+
+ if (opts && opts.colors) {
+ options.colors = opts.colors;
+ }
+
+ if (options.xaxis.color == null)
+ options.xaxis.color = $.color.parse(options.grid.color).scale('a', 0.22).toString();
+ if (options.yaxis.color == null)
+ options.yaxis.color = $.color.parse(options.grid.color).scale('a', 0.22).toString();
+
+ if (options.xaxis.tickColor == null) // grid.tickColor for back-compatibility
+ options.xaxis.tickColor = options.grid.tickColor || options.xaxis.color;
+ if (options.yaxis.tickColor == null) // grid.tickColor for back-compatibility
+ options.yaxis.tickColor = options.grid.tickColor || options.yaxis.color;
+
+ if (options.grid.borderColor == null)
+ options.grid.borderColor = options.grid.color;
+ if (options.grid.tickColor == null)
+ options.grid.tickColor = $.color.parse(options.grid.color).scale('a', 0.22).toString();
+
+ // Fill in defaults for axis options, including any unspecified
+ // font-spec fields, if a font-spec was provided.
+
+ // If no x/y axis options were provided, create one of each anyway,
+ // since the rest of the code assumes that they exist.
+
+ var i, axisOptions, axisCount,
+ fontSize = placeholder.css("font-size"),
+ fontSizeDefault = fontSize ? +fontSize.replace("px", "") : 13,
+ fontDefaults = {
+ style: placeholder.css("font-style"),
+ size: Math.round(0.8 * fontSizeDefault),
+ variant: placeholder.css("font-variant"),
+ weight: placeholder.css("font-weight"),
+ family: placeholder.css("font-family")
+ };
+
+ axisCount = options.xaxes.length || 1;
+ for (i = 0; i < axisCount; ++i) {
+
+ axisOptions = options.xaxes[i];
+ if (axisOptions && !axisOptions.tickColor) {
+ axisOptions.tickColor = axisOptions.color;
+ }
+
+ axisOptions = $.extend(true, {}, options.xaxis, axisOptions);
+ options.xaxes[i] = axisOptions;
+
+ if (axisOptions.font) {
+ axisOptions.font = $.extend({}, fontDefaults, axisOptions.font);
+ if (!axisOptions.font.color) {
+ axisOptions.font.color = axisOptions.color;
+ }
+ if (!axisOptions.font.lineHeight) {
+ axisOptions.font.lineHeight = Math.round(axisOptions.font.size * 1.15);
+ }
+ }
+ }
+
+ axisCount = options.yaxes.length || 1;
+ for (i = 0; i < axisCount; ++i) {
+
+ axisOptions = options.yaxes[i];
+ if (axisOptions && !axisOptions.tickColor) {
+ axisOptions.tickColor = axisOptions.color;
+ }
+
+ axisOptions = $.extend(true, {}, options.yaxis, axisOptions);
+ options.yaxes[i] = axisOptions;
+
+ if (axisOptions.font) {
+ axisOptions.font = $.extend({}, fontDefaults, axisOptions.font);
+ if (!axisOptions.font.color) {
+ axisOptions.font.color = axisOptions.color;
+ }
+ if (!axisOptions.font.lineHeight) {
+ axisOptions.font.lineHeight = Math.round(axisOptions.font.size * 1.15);
+ }
+ }
+ }
+
+ // backwards compatibility, to be removed in future
+ if (options.xaxis.noTicks && options.xaxis.ticks == null)
+ options.xaxis.ticks = options.xaxis.noTicks;
+ if (options.yaxis.noTicks && options.yaxis.ticks == null)
+ options.yaxis.ticks = options.yaxis.noTicks;
+ if (options.x2axis) {
+ options.xaxes[1] = $.extend(true, {}, options.xaxis, options.x2axis);
+ options.xaxes[1].position = "top";
+ // Override the inherit to allow the axis to auto-scale
+ if (options.x2axis.min == null) {
+ options.xaxes[1].min = null;
+ }
+ if (options.x2axis.max == null) {
+ options.xaxes[1].max = null;
+ }
+ }
+ if (options.y2axis) {
+ options.yaxes[1] = $.extend(true, {}, options.yaxis, options.y2axis);
+ options.yaxes[1].position = "right";
+ // Override the inherit to allow the axis to auto-scale
+ if (options.y2axis.min == null) {
+ options.yaxes[1].min = null;
+ }
+ if (options.y2axis.max == null) {
+ options.yaxes[1].max = null;
+ }
+ }
+ if (options.grid.coloredAreas)
+ options.grid.markings = options.grid.coloredAreas;
+ if (options.grid.coloredAreasColor)
+ options.grid.markingsColor = options.grid.coloredAreasColor;
+ if (options.lines)
+ $.extend(true, options.series.lines, options.lines);
+ if (options.points)
+ $.extend(true, options.series.points, options.points);
+ if (options.bars)
+ $.extend(true, options.series.bars, options.bars);
+ if (options.shadowSize != null)
+ options.series.shadowSize = options.shadowSize;
+ if (options.highlightColor != null)
+ options.series.highlightColor = options.highlightColor;
+
+ // save options on axes for future reference
+ for (i = 0; i < options.xaxes.length; ++i)
+ getOrCreateAxis(xaxes, i + 1).options = options.xaxes[i];
+ for (i = 0; i < options.yaxes.length; ++i)
+ getOrCreateAxis(yaxes, i + 1).options = options.yaxes[i];
+
+ // add hooks from options
+ for (var n in hooks)
+ if (options.hooks[n] && options.hooks[n].length)
+ hooks[n] = hooks[n].concat(options.hooks[n]);
+
+ executeHooks(hooks.processOptions, [options]);
+ }
+
+ function setData(d) {
+ series = parseData(d);
+ fillInSeriesOptions();
+ processData();
+ }
+
+ function parseData(d) {
+ var res = [];
+ for (var i = 0; i < d.length; ++i) {
+ var s = $.extend(true, {}, options.series);
+
+ if (d[i].data != null) {
+ s.data = d[i].data; // move the data instead of deep-copy
+ delete d[i].data;
+
+ $.extend(true, s, d[i]);
+
+ d[i].data = s.data;
+ }
+ else
+ s.data = d[i];
+ res.push(s);
+ }
+
+ return res;
+ }
+
+ function axisNumber(obj, coord) {
+ var a = obj[coord + "axis"];
+ if (typeof a == "object") // if we got a real axis, extract number
+ a = a.n;
+ if (typeof a != "number")
+ a = 1; // default to first axis
+ return a;
+ }
+
+ function allAxes() {
+ // return flat array without annoying null entries
+ return $.grep(xaxes.concat(yaxes), function (a) { return a; });
+ }
+
+ function canvasToAxisCoords(pos) {
+ // return an object with x/y corresponding to all used axes
+ var res = {}, i, axis;
+ for (i = 0; i < xaxes.length; ++i) {
+ axis = xaxes[i];
+ if (axis && axis.used)
+ res["x" + axis.n] = axis.c2p(pos.left);
+ }
+
+ for (i = 0; i < yaxes.length; ++i) {
+ axis = yaxes[i];
+ if (axis && axis.used)
+ res["y" + axis.n] = axis.c2p(pos.top);
+ }
+
+ if (res.x1 !== undefined)
+ res.x = res.x1;
+ if (res.y1 !== undefined)
+ res.y = res.y1;
+
+ return res;
+ }
+
+ function axisToCanvasCoords(pos) {
+ // get canvas coords from the first pair of x/y found in pos
+ var res = {}, i, axis, key;
+
+ for (i = 0; i < xaxes.length; ++i) {
+ axis = xaxes[i];
+ if (axis && axis.used) {
+ key = "x" + axis.n;
+ if (pos[key] == null && axis.n == 1)
+ key = "x";
+
+ if (pos[key] != null) {
+ res.left = axis.p2c(pos[key]);
+ break;
+ }
+ }
+ }
+
+ for (i = 0; i < yaxes.length; ++i) {
+ axis = yaxes[i];
+ if (axis && axis.used) {
+ key = "y" + axis.n;
+ if (pos[key] == null && axis.n == 1)
+ key = "y";
+
+ if (pos[key] != null) {
+ res.top = axis.p2c(pos[key]);
+ break;
+ }
+ }
+ }
+
+ return res;
+ }
+
+ function getOrCreateAxis(axes, number) {
+ if (!axes[number - 1])
+ axes[number - 1] = {
+ n: number, // save the number for future reference
+ direction: axes == xaxes ? "x" : "y",
+ options: $.extend(true, {}, axes == xaxes ? options.xaxis : options.yaxis)
+ };
+
+ return axes[number - 1];
+ }
+
+ function fillInSeriesOptions() {
+
+ var neededColors = series.length, maxIndex = -1, i;
+
+ // Subtract the number of series that already have fixed colors or
+ // color indexes from the number that we still need to generate.
+
+ for (i = 0; i < series.length; ++i) {
+ var sc = series[i].color;
+ if (sc != null) {
+ neededColors--;
+ if (typeof sc == "number" && sc > maxIndex) {
+ maxIndex = sc;
+ }
+ }
+ }
+
+ // If any of the series have fixed color indexes, then we need to
+ // generate at least as many colors as the highest index.
+
+ if (neededColors <= maxIndex) {
+ neededColors = maxIndex + 1;
+ }
+
+ // Generate all the colors, using first the option colors and then
+ // variations on those colors once they're exhausted.
+
+ var c, colors = [], colorPool = options.colors,
+ colorPoolSize = colorPool.length, variation = 0;
+
+ for (i = 0; i < neededColors; i++) {
+
+ c = $.color.parse(colorPool[i % colorPoolSize] || "#666");
+
+ // Each time we exhaust the colors in the pool we adjust
+ // a scaling factor used to produce more variations on
+ // those colors. The factor alternates negative/positive
+ // to produce lighter/darker colors.
+
+ // Reset the variation after every few cycles, or else
+ // it will end up producing only white or black colors.
+
+ if (i % colorPoolSize == 0 && i) {
+ if (variation >= 0) {
+ if (variation < 0.5) {
+ variation = -variation - 0.2;
+ } else variation = 0;
+ } else variation = -variation;
+ }
+
+ colors[i] = c.scale('rgb', 1 + variation);
+ }
+
+ // Finalize the series options, filling in their colors
+
+ var colori = 0, s;
+ for (i = 0; i < series.length; ++i) {
+ s = series[i];
+
+ // assign colors
+ if (s.color == null) {
+ s.color = colors[colori].toString();
+ ++colori;
+ }
+ else if (typeof s.color == "number")
+ s.color = colors[s.color].toString();
+
+ // turn on lines automatically in case nothing is set
+ if (s.lines.show == null) {
+ var v, show = true;
+ for (v in s)
+ if (s[v] && s[v].show) {
+ show = false;
+ break;
+ }
+ if (show)
+ s.lines.show = true;
+ }
+
+ // If nothing was provided for lines.zero, default it to match
+ // lines.fill, since areas by default should extend to zero.
+
+ if (s.lines.zero == null) {
+ s.lines.zero = !!s.lines.fill;
+ }
+
+ // setup axes
+ s.xaxis = getOrCreateAxis(xaxes, axisNumber(s, "x"));
+ s.yaxis = getOrCreateAxis(yaxes, axisNumber(s, "y"));
+ }
+ }
+
+ function processData() {
+ var topSentry = Number.POSITIVE_INFINITY,
+ bottomSentry = Number.NEGATIVE_INFINITY,
+ fakeInfinity = Number.MAX_VALUE,
+ i, j, k, m, length,
+ s, points, ps, x, y, axis, val, f, p,
+ data, format;
+
+ function updateAxis(axis, min, max) {
+ if (min < axis.datamin && min != -fakeInfinity)
+ axis.datamin = min;
+ if (max > axis.datamax && max != fakeInfinity)
+ axis.datamax = max;
+ }
+
+ $.each(allAxes(), function (_, axis) {
+ // init axis
+ axis.datamin = topSentry;
+ axis.datamax = bottomSentry;
+ axis.used = false;
+ });
+
+ for (i = 0; i < series.length; ++i) {
+ s = series[i];
+ s.datapoints = { points: [] };
+
+ executeHooks(hooks.processRawData, [ s, s.data, s.datapoints ]);
+ }
+
+ // first pass: clean and copy data
+ for (i = 0; i < series.length; ++i) {
+ s = series[i];
+
+ data = s.data;
+ format = s.datapoints.format;
+
+ if (!format) {
+ format = [];
+ // find out how to copy
+ format.push({ x: true, number: true, required: true });
+ format.push({ y: true, number: true, required: true });
+
+ if (s.bars.show || (s.lines.show && s.lines.fill)) {
+ var autoscale = !!((s.bars.show && s.bars.zero) || (s.lines.show && s.lines.zero));
+ format.push({ y: true, number: true, required: false, defaultValue: 0, autoscale: autoscale });
+ if (s.bars.horizontal) {
+ delete format[format.length - 1].y;
+ format[format.length - 1].x = true;
+ }
+ }
+
+ s.datapoints.format = format;
+ }
+
+ if (s.datapoints.pointsize != null)
+ continue; // already filled in
+
+ s.datapoints.pointsize = format.length;
+
+ ps = s.datapoints.pointsize;
+ points = s.datapoints.points;
+
+ var insertSteps = s.lines.show && s.lines.steps;
+ s.xaxis.used = s.yaxis.used = true;
+
+ for (j = k = 0; j < data.length; ++j, k += ps) {
+ p = data[j];
+
+ var nullify = p == null;
+ if (!nullify) {
+ for (m = 0; m < ps; ++m) {
+ val = p[m];
+ f = format[m];
+
+ if (f) {
+ if (f.number && val != null) {
+ val = +val; // convert to number
+ if (isNaN(val))
+ val = null;
+ else if (val == Infinity)
+ val = fakeInfinity;
+ else if (val == -Infinity)
+ val = -fakeInfinity;
+ }
+
+ if (val == null) {
+ if (f.required)
+ nullify = true;
+
+ if (f.defaultValue != null)
+ val = f.defaultValue;
+ }
+ }
+
+ points[k + m] = val;
+ }
+ }
+
+ if (nullify) {
+ for (m = 0; m < ps; ++m) {
+ val = points[k + m];
+ if (val != null) {
+ f = format[m];
+ // extract min/max info
+ if (f.autoscale !== false) {
+ if (f.x) {
+ updateAxis(s.xaxis, val, val);
+ }
+ if (f.y) {
+ updateAxis(s.yaxis, val, val);
+ }
+ }
+ }
+ points[k + m] = null;
+ }
+ }
+ else {
+ // a little bit of line specific stuff that
+ // perhaps shouldn't be here, but lacking
+ // better means...
+ if (insertSteps && k > 0
+ && points[k - ps] != null
+ && points[k - ps] != points[k]
+ && points[k - ps + 1] != points[k + 1]) {
+ // copy the point to make room for a middle point
+ for (m = 0; m < ps; ++m)
+ points[k + ps + m] = points[k + m];
+
+ // middle point has same y
+ points[k + 1] = points[k - ps + 1];
+
+ // we've added a point, better reflect that
+ k += ps;
+ }
+ }
+ }
+ }
+
+ // give the hooks a chance to run
+ for (i = 0; i < series.length; ++i) {
+ s = series[i];
+
+ executeHooks(hooks.processDatapoints, [ s, s.datapoints]);
+ }
+
+ // second pass: find datamax/datamin for auto-scaling
+ for (i = 0; i < series.length; ++i) {
+ s = series[i];
+ points = s.datapoints.points;
+ ps = s.datapoints.pointsize;
+ format = s.datapoints.format;
+
+ var xmin = topSentry, ymin = topSentry,
+ xmax = bottomSentry, ymax = bottomSentry;
+
+ for (j = 0; j < points.length; j += ps) {
+ if (points[j] == null)
+ continue;
+
+ for (m = 0; m < ps; ++m) {
+ val = points[j + m];
+ f = format[m];
+ if (!f || f.autoscale === false || val == fakeInfinity || val == -fakeInfinity)
+ continue;
+
+ if (f.x) {
+ if (val < xmin)
+ xmin = val;
+ if (val > xmax)
+ xmax = val;
+ }
+ if (f.y) {
+ if (val < ymin)
+ ymin = val;
+ if (val > ymax)
+ ymax = val;
+ }
+ }
+ }
+
+ if (s.bars.show) {
+ // make sure we got room for the bar on the dancing floor
+ var delta;
+
+ switch (s.bars.align) {
+ case "left":
+ delta = 0;
+ break;
+ case "right":
+ delta = -s.bars.barWidth;
+ break;
+ default:
+ delta = -s.bars.barWidth / 2;
+ }
+
+ if (s.bars.horizontal) {
+ ymin += delta;
+ ymax += delta + s.bars.barWidth;
+ }
+ else {
+ xmin += delta;
+ xmax += delta + s.bars.barWidth;
+ }
+ }
+
+ updateAxis(s.xaxis, xmin, xmax);
+ updateAxis(s.yaxis, ymin, ymax);
+ }
+
+ $.each(allAxes(), function (_, axis) {
+ if (axis.datamin == topSentry)
+ axis.datamin = null;
+ if (axis.datamax == bottomSentry)
+ axis.datamax = null;
+ });
+ }
+
+ function setupCanvases() {
+
+ // Make sure the placeholder is clear of everything except canvases
+ // from a previous plot in this container that we'll try to re-use.
+
+ placeholder.css("padding", 0) // padding messes up the positioning
+ .children().filter(function(){
+ return !$(this).hasClass("flot-overlay") && !$(this).hasClass('flot-base');
+ }).remove();
+
+ if (placeholder.css("position") == 'static')
+ placeholder.css("position", "relative"); // for positioning labels and overlay
+
+ surface = new Canvas("flot-base", placeholder);
+ overlay = new Canvas("flot-overlay", placeholder); // overlay canvas for interactive features
+
+ ctx = surface.context;
+ octx = overlay.context;
+
+ // define which element we're listening for events on
+ eventHolder = $(overlay.element).unbind();
+
+ // If we're re-using a plot object, shut down the old one
+
+ var existing = placeholder.data("plot");
+
+ if (existing) {
+ existing.shutdown();
+ overlay.clear();
+ }
+
+ // save in case we get replotted
+ placeholder.data("plot", plot);
+ }
+
+ function bindEvents() {
+ // bind events
+ if (options.grid.hoverable) {
+ eventHolder.mousemove(onMouseMove);
+
+ // Use bind, rather than .mouseleave, because we officially
+ // still support jQuery 1.2.6, which doesn't define a shortcut
+ // for mouseenter or mouseleave. This was a bug/oversight that
+ // was fixed somewhere around 1.3.x. We can return to using
+ // .mouseleave when we drop support for 1.2.6.
+
+ eventHolder.bind("mouseleave", onMouseLeave);
+ }
+
+ if (options.grid.clickable)
+ eventHolder.click(onClick);
+
+ executeHooks(hooks.bindEvents, [eventHolder]);
+ }
+
+ function shutdown() {
+ if (redrawTimeout)
+ clearTimeout(redrawTimeout);
+
+ eventHolder.unbind("mousemove", onMouseMove);
+ eventHolder.unbind("mouseleave", onMouseLeave);
+ eventHolder.unbind("click", onClick);
+
+ executeHooks(hooks.shutdown, [eventHolder]);
+ }
+
+ function setTransformationHelpers(axis) {
+ // set helper functions on the axis, assumes plot area
+ // has been computed already
+
+ function identity(x) { return x; }
+
+ var s, m, t = axis.options.transform || identity,
+ it = axis.options.inverseTransform;
+
+ // precompute how much the axis is scaling a point
+ // in canvas space
+ if (axis.direction == "x") {
+ s = axis.scale = plotWidth / Math.abs(t(axis.max) - t(axis.min));
+ m = Math.min(t(axis.max), t(axis.min));
+ }
+ else {
+ s = axis.scale = plotHeight / Math.abs(t(axis.max) - t(axis.min));
+ s = -s;
+ m = Math.max(t(axis.max), t(axis.min));
+ }
+
+ // data point to canvas coordinate
+ if (t == identity) // slight optimization
+ axis.p2c = function (p) { return (p - m) * s; };
+ else
+ axis.p2c = function (p) { return (t(p) - m) * s; };
+ // canvas coordinate to data point
+ if (!it)
+ axis.c2p = function (c) { return m + c / s; };
+ else
+ axis.c2p = function (c) { return it(m + c / s); };
+ }
+
+ function measureTickLabels(axis) {
+
+ var opts = axis.options,
+ ticks = axis.ticks || [],
+ labelWidth = opts.labelWidth || 0,
+ labelHeight = opts.labelHeight || 0,
+ maxWidth = labelWidth || (axis.direction == "x" ? Math.floor(surface.width / (ticks.length || 1)) : null),
+ legacyStyles = axis.direction + "Axis " + axis.direction + axis.n + "Axis",
+ layer = "flot-" + axis.direction + "-axis flot-" + axis.direction + axis.n + "-axis " + legacyStyles,
+ font = opts.font || "flot-tick-label tickLabel";
+
+ for (var i = 0; i < ticks.length; ++i) {
+
+ var t = ticks[i];
+
+ if (!t.label)
+ continue;
+
+ var info = surface.getTextInfo(layer, t.label, font, null, maxWidth);
+
+ labelWidth = Math.max(labelWidth, info.width);
+ labelHeight = Math.max(labelHeight, info.height);
+ }
+
+ axis.labelWidth = opts.labelWidth || labelWidth;
+ axis.labelHeight = opts.labelHeight || labelHeight;
+ }
+
+ function allocateAxisBoxFirstPhase(axis) {
+ // find the bounding box of the axis by looking at label
+ // widths/heights and ticks, make room by diminishing the
+ // plotOffset; this first phase only looks at one
+ // dimension per axis, the other dimension depends on the
+ // other axes so will have to wait
+
+ var lw = axis.labelWidth,
+ lh = axis.labelHeight,
+ pos = axis.options.position,
+ isXAxis = axis.direction === "x",
+ tickLength = axis.options.tickLength,
+ axisMargin = options.grid.axisMargin,
+ padding = options.grid.labelMargin,
+ innermost = true,
+ outermost = true,
+ first = true,
+ found = false;
+
+ // Determine the axis's position in its direction and on its side
+
+ $.each(isXAxis ? xaxes : yaxes, function(i, a) {
+ if (a && (a.show || a.reserveSpace)) {
+ if (a === axis) {
+ found = true;
+ } else if (a.options.position === pos) {
+ if (found) {
+ outermost = false;
+ } else {
+ innermost = false;
+ }
+ }
+ if (!found) {
+ first = false;
+ }
+ }
+ });
+
+ // The outermost axis on each side has no margin
+
+ if (outermost) {
+ axisMargin = 0;
+ }
+
+ // The ticks for the first axis in each direction stretch across
+
+ if (tickLength == null) {
+ tickLength = first ? "full" : 5;
+ }
+
+ if (!isNaN(+tickLength))
+ padding += +tickLength;
+
+ if (isXAxis) {
+ lh += padding;
+
+ if (pos == "bottom") {
+ plotOffset.bottom += lh + axisMargin;
+ axis.box = { top: surface.height - plotOffset.bottom, height: lh };
+ }
+ else {
+ axis.box = { top: plotOffset.top + axisMargin, height: lh };
+ plotOffset.top += lh + axisMargin;
+ }
+ }
+ else {
+ lw += padding;
+
+ if (pos == "left") {
+ axis.box = { left: plotOffset.left + axisMargin, width: lw };
+ plotOffset.left += lw + axisMargin;
+ }
+ else {
+ plotOffset.right += lw + axisMargin;
+ axis.box = { left: surface.width - plotOffset.right, width: lw };
+ }
+ }
+
+ // save for future reference
+ axis.position = pos;
+ axis.tickLength = tickLength;
+ axis.box.padding = padding;
+ axis.innermost = innermost;
+ }
+
+ function allocateAxisBoxSecondPhase(axis) {
+ // now that all axis boxes have been placed in one
+ // dimension, we can set the remaining dimension coordinates
+ if (axis.direction == "x") {
+ axis.box.left = plotOffset.left - axis.labelWidth / 2;
+ axis.box.width = surface.width - plotOffset.left - plotOffset.right + axis.labelWidth;
+ }
+ else {
+ axis.box.top = plotOffset.top - axis.labelHeight / 2;
+ axis.box.height = surface.height - plotOffset.bottom - plotOffset.top + axis.labelHeight;
+ }
+ }
+
+ function adjustLayoutForThingsStickingOut() {
+ // possibly adjust plot offset to ensure everything stays
+ // inside the canvas and isn't clipped off
+
+ var minMargin = options.grid.minBorderMargin,
+ axis, i;
+
+ // check stuff from the plot (FIXME: this should just read
+ // a value from the series, otherwise it's impossible to
+ // customize)
+ if (minMargin == null) {
+ minMargin = 0;
+ for (i = 0; i < series.length; ++i)
+ minMargin = Math.max(minMargin, 2 * (series[i].points.radius + series[i].points.lineWidth/2));
+ }
+
+ var margins = {
+ left: minMargin,
+ right: minMargin,
+ top: minMargin,
+ bottom: minMargin
+ };
+
+ // check axis labels, note we don't check the actual
+ // labels but instead use the overall width/height to not
+ // jump as much around with replots
+ $.each(allAxes(), function (_, axis) {
+ if (axis.reserveSpace && axis.ticks && axis.ticks.length) {
+ if (axis.direction === "x") {
+ margins.left = Math.max(margins.left, axis.labelWidth / 2);
+ margins.right = Math.max(margins.right, axis.labelWidth / 2);
+ } else {
+ margins.bottom = Math.max(margins.bottom, axis.labelHeight / 2);
+ margins.top = Math.max(margins.top, axis.labelHeight / 2);
+ }
+ }
+ });
+
+ plotOffset.left = Math.ceil(Math.max(margins.left, plotOffset.left));
+ plotOffset.right = Math.ceil(Math.max(margins.right, plotOffset.right));
+ plotOffset.top = Math.ceil(Math.max(margins.top, plotOffset.top));
+ plotOffset.bottom = Math.ceil(Math.max(margins.bottom, plotOffset.bottom));
+ }
+
+ function setupGrid() {
+ var i, axes = allAxes(), showGrid = options.grid.show;
+
+ // Initialize the plot's offset from the edge of the canvas
+
+ for (var a in plotOffset) {
+ var margin = options.grid.margin || 0;
+ plotOffset[a] = typeof margin == "number" ? margin : margin[a] || 0;
+ }
+
+ executeHooks(hooks.processOffset, [plotOffset]);
+
+ // If the grid is visible, add its border width to the offset
+
+ for (var a in plotOffset) {
+ if(typeof(options.grid.borderWidth) == "object") {
+ plotOffset[a] += showGrid ? options.grid.borderWidth[a] : 0;
+ }
+ else {
+ plotOffset[a] += showGrid ? options.grid.borderWidth : 0;
+ }
+ }
+
+ $.each(axes, function (_, axis) {
+ var axisOpts = axis.options;
+ axis.show = axisOpts.show == null ? axis.used : axisOpts.show;
+ axis.reserveSpace = axisOpts.reserveSpace == null ? axis.show : axisOpts.reserveSpace;
+ setRange(axis);
+ });
+
+ if (showGrid) {
+
+ var allocatedAxes = $.grep(axes, function (axis) {
+ return axis.show || axis.reserveSpace;
+ });
+
+ $.each(allocatedAxes, function (_, axis) {
+ // make the ticks
+ setupTickGeneration(axis);
+ setTicks(axis);
+ snapRangeToTicks(axis, axis.ticks);
+ // find labelWidth/Height for axis
+ measureTickLabels(axis);
+ });
+
+ // with all dimensions calculated, we can compute the
+ // axis bounding boxes, start from the outside
+ // (reverse order)
+ for (i = allocatedAxes.length - 1; i >= 0; --i)
+ allocateAxisBoxFirstPhase(allocatedAxes[i]);
+
+ // make sure we've got enough space for things that
+ // might stick out
+ adjustLayoutForThingsStickingOut();
+
+ $.each(allocatedAxes, function (_, axis) {
+ allocateAxisBoxSecondPhase(axis);
+ });
+ }
+
+ plotWidth = surface.width - plotOffset.left - plotOffset.right;
+ plotHeight = surface.height - plotOffset.bottom - plotOffset.top;
+
+ // now we got the proper plot dimensions, we can compute the scaling
+ $.each(axes, function (_, axis) {
+ setTransformationHelpers(axis);
+ });
+
+ if (showGrid) {
+ drawAxisLabels();
+ }
+
+ insertLegend();
+ }
+
+ function setRange(axis) {
+ var opts = axis.options,
+ min = +(opts.min != null ? opts.min : axis.datamin),
+ max = +(opts.max != null ? opts.max : axis.datamax),
+ delta = max - min;
+
+ if (delta == 0.0) {
+ // degenerate case
+ var widen = max == 0 ? 1 : 0.01;
+
+ if (opts.min == null)
+ min -= widen;
+ // always widen max if we couldn't widen min to ensure we
+ // don't fall into min == max which doesn't work
+ if (opts.max == null || opts.min != null)
+ max += widen;
+ }
+ else {
+ // consider autoscaling
+ var margin = opts.autoscaleMargin;
+ if (margin != null) {
+ if (opts.min == null) {
+ min -= delta * margin;
+ // make sure we don't go below zero if all values
+ // are positive
+ if (min < 0 && axis.datamin != null && axis.datamin >= 0)
+ min = 0;
+ }
+ if (opts.max == null) {
+ max += delta * margin;
+ if (max > 0 && axis.datamax != null && axis.datamax <= 0)
+ max = 0;
+ }
+ }
+ }
+ axis.min = min;
+ axis.max = max;
+ }
+
+ function setupTickGeneration(axis) {
+ var opts = axis.options;
+
+ // estimate number of ticks
+ var noTicks;
+ if (typeof opts.ticks == "number" && opts.ticks > 0)
+ noTicks = opts.ticks;
+ else
+ // heuristic based on the model a*sqrt(x) fitted to
+ // some data points that seemed reasonable
+ noTicks = 0.3 * Math.sqrt(axis.direction == "x" ? surface.width : surface.height);
+
+ var delta = (axis.max - axis.min) / noTicks,
+ dec = -Math.floor(Math.log(delta) / Math.LN10),
+ maxDec = opts.tickDecimals;
+
+ if (maxDec != null && dec > maxDec) {
+ dec = maxDec;
+ }
+
+ var magn = Math.pow(10, -dec),
+ norm = delta / magn, // norm is between 1.0 and 10.0
+ size;
+
+ if (norm < 1.5) {
+ size = 1;
+ } else if (norm < 3) {
+ size = 2;
+ // special case for 2.5, requires an extra decimal
+ if (norm > 2.25 && (maxDec == null || dec + 1 <= maxDec)) {
+ size = 2.5;
+ ++dec;
+ }
+ } else if (norm < 7.5) {
+ size = 5;
+ } else {
+ size = 10;
+ }
+
+ size *= magn;
+
+ if (opts.minTickSize != null && size < opts.minTickSize) {
+ size = opts.minTickSize;
+ }
+
+ axis.delta = delta;
+ axis.tickDecimals = Math.max(0, maxDec != null ? maxDec : dec);
+ axis.tickSize = opts.tickSize || size;
+
+ // Time mode was moved to a plug-in in 0.8, and since so many people use it
+ // we'll add an especially friendly reminder to make sure they included it.
+
+ if (opts.mode == "time" && !axis.tickGenerator) {
+ throw new Error("Time mode requires the flot.time plugin.");
+ }
+
+ // Flot supports base-10 axes; any other mode else is handled by a plug-in,
+ // like flot.time.js.
+
+ if (!axis.tickGenerator) {
+
+ axis.tickGenerator = function (axis) {
+
+ var ticks = [],
+ start = floorInBase(axis.min, axis.tickSize),
+ i = 0,
+ v = Number.NaN,
+ prev;
+
+ do {
+ prev = v;
+ v = start + i * axis.tickSize;
+ ticks.push(v);
+ ++i;
+ } while (v < axis.max && v != prev);
+ return ticks;
+ };
+
+ axis.tickFormatter = function (value, axis) {
+
+ var factor = axis.tickDecimals ? Math.pow(10, axis.tickDecimals) : 1;
+ var formatted = "" + Math.round(value * factor) / factor;
+
+ // If tickDecimals was specified, ensure that we have exactly that
+ // much precision; otherwise default to the value's own precision.
+
+ if (axis.tickDecimals != null) {
+ var decimal = formatted.indexOf(".");
+ var precision = decimal == -1 ? 0 : formatted.length - decimal - 1;
+ if (precision < axis.tickDecimals) {
+ return (precision ? formatted : formatted + ".") + ("" + factor).substr(1, axis.tickDecimals - precision);
+ }
+ }
+
+ return formatted;
+ };
+ }
+
+ if ($.isFunction(opts.tickFormatter))
+ axis.tickFormatter = function (v, axis) { return "" + opts.tickFormatter(v, axis); };
+
+ if (opts.alignTicksWithAxis != null) {
+ var otherAxis = (axis.direction == "x" ? xaxes : yaxes)[opts.alignTicksWithAxis - 1];
+ if (otherAxis && otherAxis.used && otherAxis != axis) {
+ // consider snapping min/max to outermost nice ticks
+ var niceTicks = axis.tickGenerator(axis);
+ if (niceTicks.length > 0) {
+ if (opts.min == null)
+ axis.min = Math.min(axis.min, niceTicks[0]);
+ if (opts.max == null && niceTicks.length > 1)
+ axis.max = Math.max(axis.max, niceTicks[niceTicks.length - 1]);
+ }
+
+ axis.tickGenerator = function (axis) {
+ // copy ticks, scaled to this axis
+ var ticks = [], v, i;
+ for (i = 0; i < otherAxis.ticks.length; ++i) {
+ v = (otherAxis.ticks[i].v - otherAxis.min) / (otherAxis.max - otherAxis.min);
+ v = axis.min + v * (axis.max - axis.min);
+ ticks.push(v);
+ }
+ return ticks;
+ };
+
+ // we might need an extra decimal since forced
+ // ticks don't necessarily fit naturally
+ if (!axis.mode && opts.tickDecimals == null) {
+ var extraDec = Math.max(0, -Math.floor(Math.log(axis.delta) / Math.LN10) + 1),
+ ts = axis.tickGenerator(axis);
+
+ // only proceed if the tick interval rounded
+ // with an extra decimal doesn't give us a
+ // zero at end
+ if (!(ts.length > 1 && /\..*0$/.test((ts[1] - ts[0]).toFixed(extraDec))))
+ axis.tickDecimals = extraDec;
+ }
+ }
+ }
+ }
+
+ function setTicks(axis) {
+ var oticks = axis.options.ticks, ticks = [];
+ if (oticks == null || (typeof oticks == "number" && oticks > 0))
+ ticks = axis.tickGenerator(axis);
+ else if (oticks) {
+ if ($.isFunction(oticks))
+ // generate the ticks
+ ticks = oticks(axis);
+ else
+ ticks = oticks;
+ }
+
+ // clean up/labelify the supplied ticks, copy them over
+ var i, v;
+ axis.ticks = [];
+ for (i = 0; i < ticks.length; ++i) {
+ var label = null;
+ var t = ticks[i];
+ if (typeof t == "object") {
+ v = +t[0];
+ if (t.length > 1)
+ label = t[1];
+ }
+ else
+ v = +t;
+ if (label == null)
+ label = axis.tickFormatter(v, axis);
+ if (!isNaN(v))
+ axis.ticks.push({ v: v, label: label });
+ }
+ }
+
+ function snapRangeToTicks(axis, ticks) {
+ if (axis.options.autoscaleMargin && ticks.length > 0) {
+ // snap to ticks
+ if (axis.options.min == null)
+ axis.min = Math.min(axis.min, ticks[0].v);
+ if (axis.options.max == null && ticks.length > 1)
+ axis.max = Math.max(axis.max, ticks[ticks.length - 1].v);
+ }
+ }
+
+ function draw() {
+
+ surface.clear();
+
+ executeHooks(hooks.drawBackground, [ctx]);
+
+ var grid = options.grid;
+
+ // draw background, if any
+ if (grid.show && grid.backgroundColor)
+ drawBackground();
+
+ if (grid.show && !grid.aboveData) {
+ drawGrid();
+ }
+
+ for (var i = 0; i < series.length; ++i) {
+ executeHooks(hooks.drawSeries, [ctx, series[i]]);
+ drawSeries(series[i]);
+ }
+
+ executeHooks(hooks.draw, [ctx]);
+
+ if (grid.show && grid.aboveData) {
+ drawGrid();
+ }
+
+ surface.render();
+
+ // A draw implies that either the axes or data have changed, so we
+ // should probably update the overlay highlights as well.
+
+ triggerRedrawOverlay();
+ }
+
+ function extractRange(ranges, coord) {
+ var axis, from, to, key, axes = allAxes();
+
+ for (var i = 0; i < axes.length; ++i) {
+ axis = axes[i];
+ if (axis.direction == coord) {
+ key = coord + axis.n + "axis";
+ if (!ranges[key] && axis.n == 1)
+ key = coord + "axis"; // support x1axis as xaxis
+ if (ranges[key]) {
+ from = ranges[key].from;
+ to = ranges[key].to;
+ break;
+ }
+ }
+ }
+
+ // backwards-compat stuff - to be removed in future
+ if (!ranges[key]) {
+ axis = coord == "x" ? xaxes[0] : yaxes[0];
+ from = ranges[coord + "1"];
+ to = ranges[coord + "2"];
+ }
+
+ // auto-reverse as an added bonus
+ if (from != null && to != null && from > to) {
+ var tmp = from;
+ from = to;
+ to = tmp;
+ }
+
+ return { from: from, to: to, axis: axis };
+ }
+
+ function drawBackground() {
+ ctx.save();
+ ctx.translate(plotOffset.left, plotOffset.top);
+
+ ctx.fillStyle = getColorOrGradient(options.grid.backgroundColor, plotHeight, 0, "rgba(255, 255, 255, 0)");
+ ctx.fillRect(0, 0, plotWidth, plotHeight);
+ ctx.restore();
+ }
+
+ function drawGrid() {
+ var i, axes, bw, bc;
+
+ ctx.save();
+ ctx.translate(plotOffset.left, plotOffset.top);
+
+ // draw markings
+ var markings = options.grid.markings;
+ if (markings) {
+ if ($.isFunction(markings)) {
+ axes = plot.getAxes();
+ // xmin etc. is backwards compatibility, to be
+ // removed in the future
+ axes.xmin = axes.xaxis.min;
+ axes.xmax = axes.xaxis.max;
+ axes.ymin = axes.yaxis.min;
+ axes.ymax = axes.yaxis.max;
+
+ markings = markings(axes);
+ }
+
+ for (i = 0; i < markings.length; ++i) {
+ var m = markings[i],
+ xrange = extractRange(m, "x"),
+ yrange = extractRange(m, "y");
+
+ // fill in missing
+ if (xrange.from == null)
+ xrange.from = xrange.axis.min;
+ if (xrange.to == null)
+ xrange.to = xrange.axis.max;
+ if (yrange.from == null)
+ yrange.from = yrange.axis.min;
+ if (yrange.to == null)
+ yrange.to = yrange.axis.max;
+
+ // clip
+ if (xrange.to < xrange.axis.min || xrange.from > xrange.axis.max ||
+ yrange.to < yrange.axis.min || yrange.from > yrange.axis.max)
+ continue;
+
+ xrange.from = Math.max(xrange.from, xrange.axis.min);
+ xrange.to = Math.min(xrange.to, xrange.axis.max);
+ yrange.from = Math.max(yrange.from, yrange.axis.min);
+ yrange.to = Math.min(yrange.to, yrange.axis.max);
+
+ var xequal = xrange.from === xrange.to,
+ yequal = yrange.from === yrange.to;
+
+ if (xequal && yequal) {
+ continue;
+ }
+
+ // then draw
+ xrange.from = Math.floor(xrange.axis.p2c(xrange.from));
+ xrange.to = Math.floor(xrange.axis.p2c(xrange.to));
+ yrange.from = Math.floor(yrange.axis.p2c(yrange.from));
+ yrange.to = Math.floor(yrange.axis.p2c(yrange.to));
+
+ if (xequal || yequal) {
+ var lineWidth = m.lineWidth || options.grid.markingsLineWidth,
+ subPixel = lineWidth % 2 ? 0.5 : 0;
+ ctx.beginPath();
+ ctx.strokeStyle = m.color || options.grid.markingsColor;
+ ctx.lineWidth = lineWidth;
+ if (xequal) {
+ ctx.moveTo(xrange.to + subPixel, yrange.from);
+ ctx.lineTo(xrange.to + subPixel, yrange.to);
+ } else {
+ ctx.moveTo(xrange.from, yrange.to + subPixel);
+ ctx.lineTo(xrange.to, yrange.to + subPixel);
+ }
+ ctx.stroke();
+ } else {
+ ctx.fillStyle = m.color || options.grid.markingsColor;
+ ctx.fillRect(xrange.from, yrange.to,
+ xrange.to - xrange.from,
+ yrange.from - yrange.to);
+ }
+ }
+ }
+
+ // draw the ticks
+ axes = allAxes();
+ bw = options.grid.borderWidth;
+
+ for (var j = 0; j < axes.length; ++j) {
+ var axis = axes[j], box = axis.box,
+ t = axis.tickLength, x, y, xoff, yoff;
+ if (!axis.show || axis.ticks.length == 0)
+ continue;
+
+ ctx.lineWidth = 1;
+
+ // find the edges
+ if (axis.direction == "x") {
+ x = 0;
+ if (t == "full")
+ y = (axis.position == "top" ? 0 : plotHeight);
+ else
+ y = box.top - plotOffset.top + (axis.position == "top" ? box.height : 0);
+ }
+ else {
+ y = 0;
+ if (t == "full")
+ x = (axis.position == "left" ? 0 : plotWidth);
+ else
+ x = box.left - plotOffset.left + (axis.position == "left" ? box.width : 0);
+ }
+
+ // draw tick bar
+ if (!axis.innermost) {
+ ctx.strokeStyle = axis.options.color;
+ ctx.beginPath();
+ xoff = yoff = 0;
+ if (axis.direction == "x")
+ xoff = plotWidth + 1;
+ else
+ yoff = plotHeight + 1;
+
+ if (ctx.lineWidth == 1) {
+ if (axis.direction == "x") {
+ y = Math.floor(y) + 0.5;
+ } else {
+ x = Math.floor(x) + 0.5;
+ }
+ }
+
+ ctx.moveTo(x, y);
+ ctx.lineTo(x + xoff, y + yoff);
+ ctx.stroke();
+ }
+
+ // draw ticks
+
+ ctx.strokeStyle = axis.options.tickColor;
+
+ ctx.beginPath();
+ for (i = 0; i < axis.ticks.length; ++i) {
+ var v = axis.ticks[i].v;
+
+ xoff = yoff = 0;
+
+ if (isNaN(v) || v < axis.min || v > axis.max
+ // skip those lying on the axes if we got a border
+ || (t == "full"
+ && ((typeof bw == "object" && bw[axis.position] > 0) || bw > 0)
+ && (v == axis.min || v == axis.max)))
+ continue;
+
+ if (axis.direction == "x") {
+ x = axis.p2c(v);
+ yoff = t == "full" ? -plotHeight : t;
+
+ if (axis.position == "top")
+ yoff = -yoff;
+ }
+ else {
+ y = axis.p2c(v);
+ xoff = t == "full" ? -plotWidth : t;
+
+ if (axis.position == "left")
+ xoff = -xoff;
+ }
+
+ if (ctx.lineWidth == 1) {
+ if (axis.direction == "x")
+ x = Math.floor(x) + 0.5;
+ else
+ y = Math.floor(y) + 0.5;
+ }
+
+ ctx.moveTo(x, y);
+ ctx.lineTo(x + xoff, y + yoff);
+ }
+
+ ctx.stroke();
+ }
+
+
+ // draw border
+ if (bw) {
+ // If either borderWidth or borderColor is an object, then draw the border
+ // line by line instead of as one rectangle
+ bc = options.grid.borderColor;
+ if(typeof bw == "object" || typeof bc == "object") {
+ if (typeof bw !== "object") {
+ bw = {top: bw, right: bw, bottom: bw, left: bw};
+ }
+ if (typeof bc !== "object") {
+ bc = {top: bc, right: bc, bottom: bc, left: bc};
+ }
+
+ if (bw.top > 0) {
+ ctx.strokeStyle = bc.top;
+ ctx.lineWidth = bw.top;
+ ctx.beginPath();
+ ctx.moveTo(0 - bw.left, 0 - bw.top/2);
+ ctx.lineTo(plotWidth, 0 - bw.top/2);
+ ctx.stroke();
+ }
+
+ if (bw.right > 0) {
+ ctx.strokeStyle = bc.right;
+ ctx.lineWidth = bw.right;
+ ctx.beginPath();
+ ctx.moveTo(plotWidth + bw.right / 2, 0 - bw.top);
+ ctx.lineTo(plotWidth + bw.right / 2, plotHeight);
+ ctx.stroke();
+ }
+
+ if (bw.bottom > 0) {
+ ctx.strokeStyle = bc.bottom;
+ ctx.lineWidth = bw.bottom;
+ ctx.beginPath();
+ ctx.moveTo(plotWidth + bw.right, plotHeight + bw.bottom / 2);
+ ctx.lineTo(0, plotHeight + bw.bottom / 2);
+ ctx.stroke();
+ }
+
+ if (bw.left > 0) {
+ ctx.strokeStyle = bc.left;
+ ctx.lineWidth = bw.left;
+ ctx.beginPath();
+ ctx.moveTo(0 - bw.left/2, plotHeight + bw.bottom);
+ ctx.lineTo(0- bw.left/2, 0);
+ ctx.stroke();
+ }
+ }
+ else {
+ ctx.lineWidth = bw;
+ ctx.strokeStyle = options.grid.borderColor;
+ ctx.strokeRect(-bw/2, -bw/2, plotWidth + bw, plotHeight + bw);
+ }
+ }
+
+ ctx.restore();
+ }
+
+ function drawAxisLabels() {
+
+ $.each(allAxes(), function (_, axis) {
+ var box = axis.box,
+ legacyStyles = axis.direction + "Axis " + axis.direction + axis.n + "Axis",
+ layer = "flot-" + axis.direction + "-axis flot-" + axis.direction + axis.n + "-axis " + legacyStyles,
+ font = axis.options.font || "flot-tick-label tickLabel",
+ tick, x, y, halign, valign;
+
+ // Remove text before checking for axis.show and ticks.length;
+ // otherwise plugins, like flot-tickrotor, that draw their own
+ // tick labels will end up with both theirs and the defaults.
+
+ surface.removeText(layer);
+
+ if (!axis.show || axis.ticks.length == 0)
+ return;
+
+ for (var i = 0; i < axis.ticks.length; ++i) {
+
+ tick = axis.ticks[i];
+ if (!tick.label || tick.v < axis.min || tick.v > axis.max)
+ continue;
+
+ if (axis.direction == "x") {
+ halign = "center";
+ x = plotOffset.left + axis.p2c(tick.v);
+ if (axis.position == "bottom") {
+ y = box.top + box.padding;
+ } else {
+ y = box.top + box.height - box.padding;
+ valign = "bottom";
+ }
+ } else {
+ valign = "middle";
+ y = plotOffset.top + axis.p2c(tick.v);
+ if (axis.position == "left") {
+ x = box.left + box.width - box.padding;
+ halign = "right";
+ } else {
+ x = box.left + box.padding;
+ }
+ }
+
+ surface.addText(layer, x, y, tick.label, font, null, null, halign, valign);
+ }
+ });
+ }
+
+ function drawSeries(series) {
+ if (series.lines.show)
+ drawSeriesLines(series);
+ if (series.bars.show)
+ drawSeriesBars(series);
+ if (series.points.show)
+ drawSeriesPoints(series);
+ }
+
+ function drawSeriesLines(series) {
+ function plotLine(datapoints, xoffset, yoffset, axisx, axisy) {
+ var points = datapoints.points,
+ ps = datapoints.pointsize,
+ prevx = null, prevy = null;
+
+ ctx.beginPath();
+ for (var i = ps; i < points.length; i += ps) {
+ var x1 = points[i - ps], y1 = points[i - ps + 1],
+ x2 = points[i], y2 = points[i + 1];
+
+ if (x1 == null || x2 == null)
+ continue;
+
+ // clip with ymin
+ if (y1 <= y2 && y1 < axisy.min) {
+ if (y2 < axisy.min)
+ continue; // line segment is outside
+ // compute new intersection point
+ x1 = (axisy.min - y1) / (y2 - y1) * (x2 - x1) + x1;
+ y1 = axisy.min;
+ }
+ else if (y2 <= y1 && y2 < axisy.min) {
+ if (y1 < axisy.min)
+ continue;
+ x2 = (axisy.min - y1) / (y2 - y1) * (x2 - x1) + x1;
+ y2 = axisy.min;
+ }
+
+ // clip with ymax
+ if (y1 >= y2 && y1 > axisy.max) {
+ if (y2 > axisy.max)
+ continue;
+ x1 = (axisy.max - y1) / (y2 - y1) * (x2 - x1) + x1;
+ y1 = axisy.max;
+ }
+ else if (y2 >= y1 && y2 > axisy.max) {
+ if (y1 > axisy.max)
+ continue;
+ x2 = (axisy.max - y1) / (y2 - y1) * (x2 - x1) + x1;
+ y2 = axisy.max;
+ }
+
+ // clip with xmin
+ if (x1 <= x2 && x1 < axisx.min) {
+ if (x2 < axisx.min)
+ continue;
+ y1 = (axisx.min - x1) / (x2 - x1) * (y2 - y1) + y1;
+ x1 = axisx.min;
+ }
+ else if (x2 <= x1 && x2 < axisx.min) {
+ if (x1 < axisx.min)
+ continue;
+ y2 = (axisx.min - x1) / (x2 - x1) * (y2 - y1) + y1;
+ x2 = axisx.min;
+ }
+
+ // clip with xmax
+ if (x1 >= x2 && x1 > axisx.max) {
+ if (x2 > axisx.max)
+ continue;
+ y1 = (axisx.max - x1) / (x2 - x1) * (y2 - y1) + y1;
+ x1 = axisx.max;
+ }
+ else if (x2 >= x1 && x2 > axisx.max) {
+ if (x1 > axisx.max)
+ continue;
+ y2 = (axisx.max - x1) / (x2 - x1) * (y2 - y1) + y1;
+ x2 = axisx.max;
+ }
+
+ if (x1 != prevx || y1 != prevy)
+ ctx.moveTo(axisx.p2c(x1) + xoffset, axisy.p2c(y1) + yoffset);
+
+ prevx = x2;
+ prevy = y2;
+ ctx.lineTo(axisx.p2c(x2) + xoffset, axisy.p2c(y2) + yoffset);
+ }
+ ctx.stroke();
+ }
+
+ function plotLineArea(datapoints, axisx, axisy) {
+ var points = datapoints.points,
+ ps = datapoints.pointsize,
+ bottom = Math.min(Math.max(0, axisy.min), axisy.max),
+ i = 0, top, areaOpen = false,
+ ypos = 1, segmentStart = 0, segmentEnd = 0;
+
+ // we process each segment in two turns, first forward
+ // direction to sketch out top, then once we hit the
+ // end we go backwards to sketch the bottom
+ while (true) {
+ if (ps > 0 && i > points.length + ps)
+ break;
+
+ i += ps; // ps is negative if going backwards
+
+ var x1 = points[i - ps],
+ y1 = points[i - ps + ypos],
+ x2 = points[i], y2 = points[i + ypos];
+
+ if (areaOpen) {
+ if (ps > 0 && x1 != null && x2 == null) {
+ // at turning point
+ segmentEnd = i;
+ ps = -ps;
+ ypos = 2;
+ continue;
+ }
+
+ if (ps < 0 && i == segmentStart + ps) {
+ // done with the reverse sweep
+ ctx.fill();
+ areaOpen = false;
+ ps = -ps;
+ ypos = 1;
+ i = segmentStart = segmentEnd + ps;
+ continue;
+ }
+ }
+
+ if (x1 == null || x2 == null)
+ continue;
+
+ // clip x values
+
+ // clip with xmin
+ if (x1 <= x2 && x1 < axisx.min) {
+ if (x2 < axisx.min)
+ continue;
+ y1 = (axisx.min - x1) / (x2 - x1) * (y2 - y1) + y1;
+ x1 = axisx.min;
+ }
+ else if (x2 <= x1 && x2 < axisx.min) {
+ if (x1 < axisx.min)
+ continue;
+ y2 = (axisx.min - x1) / (x2 - x1) * (y2 - y1) + y1;
+ x2 = axisx.min;
+ }
+
+ // clip with xmax
+ if (x1 >= x2 && x1 > axisx.max) {
+ if (x2 > axisx.max)
+ continue;
+ y1 = (axisx.max - x1) / (x2 - x1) * (y2 - y1) + y1;
+ x1 = axisx.max;
+ }
+ else if (x2 >= x1 && x2 > axisx.max) {
+ if (x1 > axisx.max)
+ continue;
+ y2 = (axisx.max - x1) / (x2 - x1) * (y2 - y1) + y1;
+ x2 = axisx.max;
+ }
+
+ if (!areaOpen) {
+ // open area
+ ctx.beginPath();
+ ctx.moveTo(axisx.p2c(x1), axisy.p2c(bottom));
+ areaOpen = true;
+ }
+
+ // now first check the case where both is outside
+ if (y1 >= axisy.max && y2 >= axisy.max) {
+ ctx.lineTo(axisx.p2c(x1), axisy.p2c(axisy.max));
+ ctx.lineTo(axisx.p2c(x2), axisy.p2c(axisy.max));
+ continue;
+ }
+ else if (y1 <= axisy.min && y2 <= axisy.min) {
+ ctx.lineTo(axisx.p2c(x1), axisy.p2c(axisy.min));
+ ctx.lineTo(axisx.p2c(x2), axisy.p2c(axisy.min));
+ continue;
+ }
+
+ // else it's a bit more complicated, there might
+ // be a flat maxed out rectangle first, then a
+ // triangular cutout or reverse; to find these
+ // keep track of the current x values
+ var x1old = x1, x2old = x2;
+
+ // clip the y values, without shortcutting, we
+ // go through all cases in turn
+
+ // clip with ymin
+ if (y1 <= y2 && y1 < axisy.min && y2 >= axisy.min) {
+ x1 = (axisy.min - y1) / (y2 - y1) * (x2 - x1) + x1;
+ y1 = axisy.min;
+ }
+ else if (y2 <= y1 && y2 < axisy.min && y1 >= axisy.min) {
+ x2 = (axisy.min - y1) / (y2 - y1) * (x2 - x1) + x1;
+ y2 = axisy.min;
+ }
+
+ // clip with ymax
+ if (y1 >= y2 && y1 > axisy.max && y2 <= axisy.max) {
+ x1 = (axisy.max - y1) / (y2 - y1) * (x2 - x1) + x1;
+ y1 = axisy.max;
+ }
+ else if (y2 >= y1 && y2 > axisy.max && y1 <= axisy.max) {
+ x2 = (axisy.max - y1) / (y2 - y1) * (x2 - x1) + x1;
+ y2 = axisy.max;
+ }
+
+ // if the x value was changed we got a rectangle
+ // to fill
+ if (x1 != x1old) {
+ ctx.lineTo(axisx.p2c(x1old), axisy.p2c(y1));
+ // it goes to (x1, y1), but we fill that below
+ }
+
+ // fill triangular section, this sometimes result
+ // in redundant points if (x1, y1) hasn't changed
+ // from previous line to, but we just ignore that
+ ctx.lineTo(axisx.p2c(x1), axisy.p2c(y1));
+ ctx.lineTo(axisx.p2c(x2), axisy.p2c(y2));
+
+ // fill the other rectangle if it's there
+ if (x2 != x2old) {
+ ctx.lineTo(axisx.p2c(x2), axisy.p2c(y2));
+ ctx.lineTo(axisx.p2c(x2old), axisy.p2c(y2));
+ }
+ }
+ }
+
+ ctx.save();
+ ctx.translate(plotOffset.left, plotOffset.top);
+ ctx.lineJoin = "round";
+
+ var lw = series.lines.lineWidth,
+ sw = series.shadowSize;
+ // FIXME: consider another form of shadow when filling is turned on
+ if (lw > 0 && sw > 0) {
+ // draw shadow as a thick and thin line with transparency
+ ctx.lineWidth = sw;
+ ctx.strokeStyle = "rgba(0,0,0,0.1)";
+ // position shadow at angle from the mid of line
+ var angle = Math.PI/18;
+ plotLine(series.datapoints, Math.sin(angle) * (lw/2 + sw/2), Math.cos(angle) * (lw/2 + sw/2), series.xaxis, series.yaxis);
+ ctx.lineWidth = sw/2;
+ plotLine(series.datapoints, Math.sin(angle) * (lw/2 + sw/4), Math.cos(angle) * (lw/2 + sw/4), series.xaxis, series.yaxis);
+ }
+
+ ctx.lineWidth = lw;
+ ctx.strokeStyle = series.color;
+ var fillStyle = getFillStyle(series.lines, series.color, 0, plotHeight);
+ if (fillStyle) {
+ ctx.fillStyle = fillStyle;
+ plotLineArea(series.datapoints, series.xaxis, series.yaxis);
+ }
+
+ if (lw > 0)
+ plotLine(series.datapoints, 0, 0, series.xaxis, series.yaxis);
+ ctx.restore();
+ }
+
+ function drawSeriesPoints(series) {
+ function plotPoints(datapoints, radius, fillStyle, offset, shadow, axisx, axisy, symbol) {
+ var points = datapoints.points, ps = datapoints.pointsize;
+
+ for (var i = 0; i < points.length; i += ps) {
+ var x = points[i], y = points[i + 1];
+ if (x == null || x < axisx.min || x > axisx.max || y < axisy.min || y > axisy.max)
+ continue;
+
+ ctx.beginPath();
+ x = axisx.p2c(x);
+ y = axisy.p2c(y) + offset;
+ if (symbol == "circle")
+ ctx.arc(x, y, radius, 0, shadow ? Math.PI : Math.PI * 2, false);
+ else
+ symbol(ctx, x, y, radius, shadow);
+ ctx.closePath();
+
+ if (fillStyle) {
+ ctx.fillStyle = fillStyle;
+ ctx.fill();
+ }
+ ctx.stroke();
+ }
+ }
+
+ ctx.save();
+ ctx.translate(plotOffset.left, plotOffset.top);
+
+ var lw = series.points.lineWidth,
+ sw = series.shadowSize,
+ radius = series.points.radius,
+ symbol = series.points.symbol;
+
+ // If the user sets the line width to 0, we change it to a very
+ // small value. A line width of 0 seems to force the default of 1.
+ // Doing the conditional here allows the shadow setting to still be
+ // optional even with a lineWidth of 0.
+
+ if( lw == 0 )
+ lw = 0.0001;
+
+ if (lw > 0 && sw > 0) {
+ // draw shadow in two steps
+ var w = sw / 2;
+ ctx.lineWidth = w;
+ ctx.strokeStyle = "rgba(0,0,0,0.1)";
+ plotPoints(series.datapoints, radius, null, w + w/2, true,
+ series.xaxis, series.yaxis, symbol);
+
+ ctx.strokeStyle = "rgba(0,0,0,0.2)";
+ plotPoints(series.datapoints, radius, null, w/2, true,
+ series.xaxis, series.yaxis, symbol);
+ }
+
+ ctx.lineWidth = lw;
+ ctx.strokeStyle = series.color;
+ plotPoints(series.datapoints, radius,
+ getFillStyle(series.points, series.color), 0, false,
+ series.xaxis, series.yaxis, symbol);
+ ctx.restore();
+ }
+
+ function drawBar(x, y, b, barLeft, barRight, fillStyleCallback, axisx, axisy, c, horizontal, lineWidth) {
+ var left, right, bottom, top,
+ drawLeft, drawRight, drawTop, drawBottom,
+ tmp;
+
+ // in horizontal mode, we start the bar from the left
+ // instead of from the bottom so it appears to be
+ // horizontal rather than vertical
+ if (horizontal) {
+ drawBottom = drawRight = drawTop = true;
+ drawLeft = false;
+ left = b;
+ right = x;
+ top = y + barLeft;
+ bottom = y + barRight;
+
+ // account for negative bars
+ if (right < left) {
+ tmp = right;
+ right = left;
+ left = tmp;
+ drawLeft = true;
+ drawRight = false;
+ }
+ }
+ else {
+ drawLeft = drawRight = drawTop = true;
+ drawBottom = false;
+ left = x + barLeft;
+ right = x + barRight;
+ bottom = b;
+ top = y;
+
+ // account for negative bars
+ if (top < bottom) {
+ tmp = top;
+ top = bottom;
+ bottom = tmp;
+ drawBottom = true;
+ drawTop = false;
+ }
+ }
+
+ // clip
+ if (right < axisx.min || left > axisx.max ||
+ top < axisy.min || bottom > axisy.max)
+ return;
+
+ if (left < axisx.min) {
+ left = axisx.min;
+ drawLeft = false;
+ }
+
+ if (right > axisx.max) {
+ right = axisx.max;
+ drawRight = false;
+ }
+
+ if (bottom < axisy.min) {
+ bottom = axisy.min;
+ drawBottom = false;
+ }
+
+ if (top > axisy.max) {
+ top = axisy.max;
+ drawTop = false;
+ }
+
+ left = axisx.p2c(left);
+ bottom = axisy.p2c(bottom);
+ right = axisx.p2c(right);
+ top = axisy.p2c(top);
+
+ // fill the bar
+ if (fillStyleCallback) {
+ c.fillStyle = fillStyleCallback(bottom, top);
+ c.fillRect(left, top, right - left, bottom - top)
+ }
+
+ // draw outline
+ if (lineWidth > 0 && (drawLeft || drawRight || drawTop || drawBottom)) {
+ c.beginPath();
+
+ // FIXME: inline moveTo is buggy with excanvas
+ c.moveTo(left, bottom);
+ if (drawLeft)
+ c.lineTo(left, top);
+ else
+ c.moveTo(left, top);
+ if (drawTop)
+ c.lineTo(right, top);
+ else
+ c.moveTo(right, top);
+ if (drawRight)
+ c.lineTo(right, bottom);
+ else
+ c.moveTo(right, bottom);
+ if (drawBottom)
+ c.lineTo(left, bottom);
+ else
+ c.moveTo(left, bottom);
+ c.stroke();
+ }
+ }
+
+ function drawSeriesBars(series) {
+ function plotBars(datapoints, barLeft, barRight, fillStyleCallback, axisx, axisy) {
+ var points = datapoints.points, ps = datapoints.pointsize;
+
+ for (var i = 0; i < points.length; i += ps) {
+ if (points[i] == null)
+ continue;
+ drawBar(points[i], points[i + 1], points[i + 2], barLeft, barRight, fillStyleCallback, axisx, axisy, ctx, series.bars.horizontal, series.bars.lineWidth);
+ }
+ }
+
+ ctx.save();
+ ctx.translate(plotOffset.left, plotOffset.top);
+
+ // FIXME: figure out a way to add shadows (for instance along the right edge)
+ ctx.lineWidth = series.bars.lineWidth;
+ ctx.strokeStyle = series.color;
+
+ var barLeft;
+
+ switch (series.bars.align) {
+ case "left":
+ barLeft = 0;
+ break;
+ case "right":
+ barLeft = -series.bars.barWidth;
+ break;
+ default:
+ barLeft = -series.bars.barWidth / 2;
+ }
+
+ var fillStyleCallback = series.bars.fill ? function (bottom, top) { return getFillStyle(series.bars, series.color, bottom, top); } : null;
+ plotBars(series.datapoints, barLeft, barLeft + series.bars.barWidth, fillStyleCallback, series.xaxis, series.yaxis);
+ ctx.restore();
+ }
+
+ function getFillStyle(filloptions, seriesColor, bottom, top) {
+ var fill = filloptions.fill;
+ if (!fill)
+ return null;
+
+ if (filloptions.fillColor)
+ return getColorOrGradient(filloptions.fillColor, bottom, top, seriesColor);
+
+ var c = $.color.parse(seriesColor);
+ c.a = typeof fill == "number" ? fill : 0.4;
+ c.normalize();
+ return c.toString();
+ }
+
+ function insertLegend() {
+
+ if (options.legend.container != null) {
+ $(options.legend.container).html("");
+ } else {
+ placeholder.find(".legend").remove();
+ }
+
+ if (!options.legend.show) {
+ return;
+ }
+
+ var fragments = [], entries = [], rowStarted = false,
+ lf = options.legend.labelFormatter, s, label;
+
+ // Build a list of legend entries, with each having a label and a color
+
+ for (var i = 0; i < series.length; ++i) {
+ s = series[i];
+ if (s.label) {
+ label = lf ? lf(s.label, s) : s.label;
+ if (label) {
+ entries.push({
+ label: label,
+ color: s.color
+ });
+ }
+ }
+ }
+
+ // Sort the legend using either the default or a custom comparator
+
+ if (options.legend.sorted) {
+ if ($.isFunction(options.legend.sorted)) {
+ entries.sort(options.legend.sorted);
+ } else if (options.legend.sorted == "reverse") {
+ entries.reverse();
+ } else {
+ var ascending = options.legend.sorted != "descending";
+ entries.sort(function(a, b) {
+ return a.label == b.label ? 0 : (
+ (a.label < b.label) != ascending ? 1 : -1 // Logical XOR
+ );
+ });
+ }
+ }
+
+ // Generate markup for the list of entries, in their final order
+
+ for (var i = 0; i < entries.length; ++i) {
+
+ var entry = entries[i];
+
+ if (i % options.legend.noColumns == 0) {
+ if (rowStarted)
+ fragments.push('');
+ fragments.push('');
+ rowStarted = true;
+ }
+
+ fragments.push(
+ ' ' +
+ '' + entry.label + ' '
+ );
+ }
+
+ if (rowStarted)
+ fragments.push(' ');
+
+ if (fragments.length == 0)
+ return;
+
+ var table = '' + fragments.join("") + '
';
+ if (options.legend.container != null)
+ $(options.legend.container).html(table);
+ else {
+ var pos = "",
+ p = options.legend.position,
+ m = options.legend.margin;
+ if (m[0] == null)
+ m = [m, m];
+ if (p.charAt(0) == "n")
+ pos += 'top:' + (m[1] + plotOffset.top) + 'px;';
+ else if (p.charAt(0) == "s")
+ pos += 'bottom:' + (m[1] + plotOffset.bottom) + 'px;';
+ if (p.charAt(1) == "e")
+ pos += 'right:' + (m[0] + plotOffset.right) + 'px;';
+ else if (p.charAt(1) == "w")
+ pos += 'left:' + (m[0] + plotOffset.left) + 'px;';
+ var legend = $('' + table.replace('style="', 'style="position:absolute;' + pos +';') + '
').appendTo(placeholder);
+ if (options.legend.backgroundOpacity != 0.0) {
+ // put in the transparent background
+ // separately to avoid blended labels and
+ // label boxes
+ var c = options.legend.backgroundColor;
+ if (c == null) {
+ c = options.grid.backgroundColor;
+ if (c && typeof c == "string")
+ c = $.color.parse(c);
+ else
+ c = $.color.extract(legend, 'background-color');
+ c.a = 1;
+ c = c.toString();
+ }
+ var div = legend.children();
+ $('
').prependTo(legend).css('opacity', options.legend.backgroundOpacity);
+ }
+ }
+ }
+
+
+ // interactive features
+
+ var highlights = [],
+ redrawTimeout = null;
+
+ // returns the data item the mouse is over, or null if none is found
+ function findNearbyItem(mouseX, mouseY, seriesFilter) {
+ var maxDistance = options.grid.mouseActiveRadius,
+ smallestDistance = maxDistance * maxDistance + 1,
+ item = null, foundPoint = false, i, j, ps;
+
+ for (i = series.length - 1; i >= 0; --i) {
+ if (!seriesFilter(series[i]))
+ continue;
+
+ var s = series[i],
+ axisx = s.xaxis,
+ axisy = s.yaxis,
+ points = s.datapoints.points,
+ mx = axisx.c2p(mouseX), // precompute some stuff to make the loop faster
+ my = axisy.c2p(mouseY),
+ maxx = maxDistance / axisx.scale,
+ maxy = maxDistance / axisy.scale;
+
+ ps = s.datapoints.pointsize;
+ // with inverse transforms, we can't use the maxx/maxy
+ // optimization, sadly
+ if (axisx.options.inverseTransform)
+ maxx = Number.MAX_VALUE;
+ if (axisy.options.inverseTransform)
+ maxy = Number.MAX_VALUE;
+
+ if (s.lines.show || s.points.show) {
+ for (j = 0; j < points.length; j += ps) {
+ var x = points[j], y = points[j + 1];
+ if (x == null)
+ continue;
+
+ // For points and lines, the cursor must be within a
+ // certain distance to the data point
+ if (x - mx > maxx || x - mx < -maxx ||
+ y - my > maxy || y - my < -maxy)
+ continue;
+
+ // We have to calculate distances in pixels, not in
+ // data units, because the scales of the axes may be different
+ var dx = Math.abs(axisx.p2c(x) - mouseX),
+ dy = Math.abs(axisy.p2c(y) - mouseY),
+ dist = dx * dx + dy * dy; // we save the sqrt
+
+ // use <= to ensure last point takes precedence
+ // (last generally means on top of)
+ if (dist < smallestDistance) {
+ smallestDistance = dist;
+ item = [i, j / ps];
+ }
+ }
+ }
+
+ if (s.bars.show && !item) { // no other point can be nearby
+
+ var barLeft, barRight;
+
+ switch (s.bars.align) {
+ case "left":
+ barLeft = 0;
+ break;
+ case "right":
+ barLeft = -s.bars.barWidth;
+ break;
+ default:
+ barLeft = -s.bars.barWidth / 2;
+ }
+
+ barRight = barLeft + s.bars.barWidth;
+
+ for (j = 0; j < points.length; j += ps) {
+ var x = points[j], y = points[j + 1], b = points[j + 2];
+ if (x == null)
+ continue;
+
+ // for a bar graph, the cursor must be inside the bar
+ if (series[i].bars.horizontal ?
+ (mx <= Math.max(b, x) && mx >= Math.min(b, x) &&
+ my >= y + barLeft && my <= y + barRight) :
+ (mx >= x + barLeft && mx <= x + barRight &&
+ my >= Math.min(b, y) && my <= Math.max(b, y)))
+ item = [i, j / ps];
+ }
+ }
+ }
+
+ if (item) {
+ i = item[0];
+ j = item[1];
+ ps = series[i].datapoints.pointsize;
+
+ return { datapoint: series[i].datapoints.points.slice(j * ps, (j + 1) * ps),
+ dataIndex: j,
+ series: series[i],
+ seriesIndex: i };
+ }
+
+ return null;
+ }
+
+ function onMouseMove(e) {
+ if (options.grid.hoverable)
+ triggerClickHoverEvent("plothover", e,
+ function (s) { return s["hoverable"] != false; });
+ }
+
+ function onMouseLeave(e) {
+ if (options.grid.hoverable)
+ triggerClickHoverEvent("plothover", e,
+ function (s) { return false; });
+ }
+
+ function onClick(e) {
+ triggerClickHoverEvent("plotclick", e,
+ function (s) { return s["clickable"] != false; });
+ }
+
+ // trigger click or hover event (they send the same parameters
+ // so we share their code)
+ function triggerClickHoverEvent(eventname, event, seriesFilter) {
+ var offset = eventHolder.offset(),
+ canvasX = event.pageX - offset.left - plotOffset.left,
+ canvasY = event.pageY - offset.top - plotOffset.top,
+ pos = canvasToAxisCoords({ left: canvasX, top: canvasY });
+
+ pos.pageX = event.pageX;
+ pos.pageY = event.pageY;
+
+ var item = findNearbyItem(canvasX, canvasY, seriesFilter);
+
+ if (item) {
+ // fill in mouse pos for any listeners out there
+ item.pageX = parseInt(item.series.xaxis.p2c(item.datapoint[0]) + offset.left + plotOffset.left, 10);
+ item.pageY = parseInt(item.series.yaxis.p2c(item.datapoint[1]) + offset.top + plotOffset.top, 10);
+ }
+
+ if (options.grid.autoHighlight) {
+ // clear auto-highlights
+ for (var i = 0; i < highlights.length; ++i) {
+ var h = highlights[i];
+ if (h.auto == eventname &&
+ !(item && h.series == item.series &&
+ h.point[0] == item.datapoint[0] &&
+ h.point[1] == item.datapoint[1]))
+ unhighlight(h.series, h.point);
+ }
+
+ if (item)
+ highlight(item.series, item.datapoint, eventname);
+ }
+
+ placeholder.trigger(eventname, [ pos, item ]);
+ }
+
+ function triggerRedrawOverlay() {
+ var t = options.interaction.redrawOverlayInterval;
+ if (t == -1) { // skip event queue
+ drawOverlay();
+ return;
+ }
+
+ if (!redrawTimeout)
+ redrawTimeout = setTimeout(drawOverlay, t);
+ }
+
+ function drawOverlay() {
+ redrawTimeout = null;
+
+ // draw highlights
+ octx.save();
+ overlay.clear();
+ octx.translate(plotOffset.left, plotOffset.top);
+
+ var i, hi;
+ for (i = 0; i < highlights.length; ++i) {
+ hi = highlights[i];
+
+ if (hi.series.bars.show)
+ drawBarHighlight(hi.series, hi.point);
+ else
+ drawPointHighlight(hi.series, hi.point);
+ }
+ octx.restore();
+
+ executeHooks(hooks.drawOverlay, [octx]);
+ }
+
+ function highlight(s, point, auto) {
+ if (typeof s == "number")
+ s = series[s];
+
+ if (typeof point == "number") {
+ var ps = s.datapoints.pointsize;
+ point = s.datapoints.points.slice(ps * point, ps * (point + 1));
+ }
+
+ var i = indexOfHighlight(s, point);
+ if (i == -1) {
+ highlights.push({ series: s, point: point, auto: auto });
+
+ triggerRedrawOverlay();
+ }
+ else if (!auto)
+ highlights[i].auto = false;
+ }
+
+ function unhighlight(s, point) {
+ if (s == null && point == null) {
+ highlights = [];
+ triggerRedrawOverlay();
+ return;
+ }
+
+ if (typeof s == "number")
+ s = series[s];
+
+ if (typeof point == "number") {
+ var ps = s.datapoints.pointsize;
+ point = s.datapoints.points.slice(ps * point, ps * (point + 1));
+ }
+
+ var i = indexOfHighlight(s, point);
+ if (i != -1) {
+ highlights.splice(i, 1);
+
+ triggerRedrawOverlay();
+ }
+ }
+
+ function indexOfHighlight(s, p) {
+ for (var i = 0; i < highlights.length; ++i) {
+ var h = highlights[i];
+ if (h.series == s && h.point[0] == p[0]
+ && h.point[1] == p[1])
+ return i;
+ }
+ return -1;
+ }
+
+ function drawPointHighlight(series, point) {
+ var x = point[0], y = point[1],
+ axisx = series.xaxis, axisy = series.yaxis,
+ highlightColor = (typeof series.highlightColor === "string") ? series.highlightColor : $.color.parse(series.color).scale('a', 0.5).toString();
+
+ if (x < axisx.min || x > axisx.max || y < axisy.min || y > axisy.max)
+ return;
+
+ var pointRadius = series.points.radius + series.points.lineWidth / 2;
+ octx.lineWidth = pointRadius;
+ octx.strokeStyle = highlightColor;
+ var radius = 1.5 * pointRadius;
+ x = axisx.p2c(x);
+ y = axisy.p2c(y);
+
+ octx.beginPath();
+ if (series.points.symbol == "circle")
+ octx.arc(x, y, radius, 0, 2 * Math.PI, false);
+ else
+ series.points.symbol(octx, x, y, radius, false);
+ octx.closePath();
+ octx.stroke();
+ }
+
+ function drawBarHighlight(series, point) {
+ var highlightColor = (typeof series.highlightColor === "string") ? series.highlightColor : $.color.parse(series.color).scale('a', 0.5).toString(),
+ fillStyle = highlightColor,
+ barLeft;
+
+ switch (series.bars.align) {
+ case "left":
+ barLeft = 0;
+ break;
+ case "right":
+ barLeft = -series.bars.barWidth;
+ break;
+ default:
+ barLeft = -series.bars.barWidth / 2;
+ }
+
+ octx.lineWidth = series.bars.lineWidth;
+ octx.strokeStyle = highlightColor;
+
+ drawBar(point[0], point[1], point[2] || 0, barLeft, barLeft + series.bars.barWidth,
+ function () { return fillStyle; }, series.xaxis, series.yaxis, octx, series.bars.horizontal, series.bars.lineWidth);
+ }
+
+ function getColorOrGradient(spec, bottom, top, defaultColor) {
+ if (typeof spec == "string")
+ return spec;
+ else {
+ // assume this is a gradient spec; IE currently only
+ // supports a simple vertical gradient properly, so that's
+ // what we support too
+ var gradient = ctx.createLinearGradient(0, top, 0, bottom);
+
+ for (var i = 0, l = spec.colors.length; i < l; ++i) {
+ var c = spec.colors[i];
+ if (typeof c != "string") {
+ var co = $.color.parse(defaultColor);
+ if (c.brightness != null)
+ co = co.scale('rgb', c.brightness);
+ if (c.opacity != null)
+ co.a *= c.opacity;
+ c = co.toString();
+ }
+ gradient.addColorStop(i / (l - 1), c);
+ }
+
+ return gradient;
+ }
+ }
+ }
+
+ // Add the plot function to the top level of the jQuery object
+
+ $.plot = function(placeholder, data, options) {
+ //var t0 = new Date();
+ var plot = new Plot($(placeholder), data, options, $.plot.plugins);
+ //(window.console ? console.log : alert)("time used (msecs): " + ((new Date()).getTime() - t0.getTime()));
+ return plot;
+ };
+
+ $.plot.version = "0.8.3";
+
+ $.plot.plugins = [];
+
+ // Also add the plot function as a chainable property
+
+ $.fn.plot = function(data, options) {
+ return this.each(function() {
+ $.plot(this, data, options);
+ });
+ };
+
+ // round to nearby lower multiple of base
+ function floorInBase(n, base) {
+ return base * Math.floor(n / base);
+ }
+
+})(jQuery);
+/* Pretty handling of time axes.
+
+Copyright (c) 2007-2014 IOLA and Ole Laursen.
+Licensed under the MIT license.
+
+Set axis.mode to "time" to enable. See the section "Time series data" in
+API.txt for details.
+
+*/
+
+(function($) {
+
+ var options = {
+ xaxis: {
+ timezone: null, // "browser" for local to the client or timezone for timezone-js
+ timeformat: null, // format string to use
+ twelveHourClock: false, // 12 or 24 time in time mode
+ monthNames: null // list of names of months
+ }
+ };
+
+ // round to nearby lower multiple of base
+
+ function floorInBase(n, base) {
+ return base * Math.floor(n / base);
+ }
+
+ // Returns a string with the date d formatted according to fmt.
+ // A subset of the Open Group's strftime format is supported.
+
+ function formatDate(d, fmt, monthNames, dayNames) {
+
+ if (typeof d.strftime == "function") {
+ return d.strftime(fmt);
+ }
+
+ var leftPad = function(n, pad) {
+ n = "" + n;
+ pad = "" + (pad == null ? "0" : pad);
+ return n.length == 1 ? pad + n : n;
+ };
+
+ var r = [];
+ var escape = false;
+ var hours = d.getHours();
+ var isAM = hours < 12;
+
+ if (monthNames == null) {
+ monthNames = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"];
+ }
+
+ if (dayNames == null) {
+ dayNames = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"];
+ }
+
+ var hours12;
+
+ if (hours > 12) {
+ hours12 = hours - 12;
+ } else if (hours == 0) {
+ hours12 = 12;
+ } else {
+ hours12 = hours;
+ }
+
+ for (var i = 0; i < fmt.length; ++i) {
+
+ var c = fmt.charAt(i);
+
+ if (escape) {
+ switch (c) {
+ case 'a': c = "" + dayNames[d.getDay()]; break;
+ case 'b': c = "" + monthNames[d.getMonth()]; break;
+ case 'd': c = leftPad(d.getDate()); break;
+ case 'e': c = leftPad(d.getDate(), " "); break;
+ case 'h': // For back-compat with 0.7; remove in 1.0
+ case 'H': c = leftPad(hours); break;
+ case 'I': c = leftPad(hours12); break;
+ case 'l': c = leftPad(hours12, " "); break;
+ case 'm': c = leftPad(d.getMonth() + 1); break;
+ case 'M': c = leftPad(d.getMinutes()); break;
+ // quarters not in Open Group's strftime specification
+ case 'q':
+ c = "" + (Math.floor(d.getMonth() / 3) + 1); break;
+ case 'S': c = leftPad(d.getSeconds()); break;
+ case 'y': c = leftPad(d.getFullYear() % 100); break;
+ case 'Y': c = "" + d.getFullYear(); break;
+ case 'p': c = (isAM) ? ("" + "am") : ("" + "pm"); break;
+ case 'P': c = (isAM) ? ("" + "AM") : ("" + "PM"); break;
+ case 'w': c = "" + d.getDay(); break;
+ }
+ r.push(c);
+ escape = false;
+ } else {
+ if (c == "%") {
+ escape = true;
+ } else {
+ r.push(c);
+ }
+ }
+ }
+
+ return r.join("");
+ }
+
+ // To have a consistent view of time-based data independent of which time
+ // zone the client happens to be in we need a date-like object independent
+ // of time zones. This is done through a wrapper that only calls the UTC
+ // versions of the accessor methods.
+
+ function makeUtcWrapper(d) {
+
+ function addProxyMethod(sourceObj, sourceMethod, targetObj, targetMethod) {
+ sourceObj[sourceMethod] = function() {
+ return targetObj[targetMethod].apply(targetObj, arguments);
+ };
+ };
+
+ var utc = {
+ date: d
+ };
+
+ // support strftime, if found
+
+ if (d.strftime != undefined) {
+ addProxyMethod(utc, "strftime", d, "strftime");
+ }
+
+ addProxyMethod(utc, "getTime", d, "getTime");
+ addProxyMethod(utc, "setTime", d, "setTime");
+
+ var props = ["Date", "Day", "FullYear", "Hours", "Milliseconds", "Minutes", "Month", "Seconds"];
+
+ for (var p = 0; p < props.length; p++) {
+ addProxyMethod(utc, "get" + props[p], d, "getUTC" + props[p]);
+ addProxyMethod(utc, "set" + props[p], d, "setUTC" + props[p]);
+ }
+
+ return utc;
+ };
+
+ // select time zone strategy. This returns a date-like object tied to the
+ // desired timezone
+
+ function dateGenerator(ts, opts) {
+ if (opts.timezone == "browser") {
+ return new Date(ts);
+ } else if (!opts.timezone || opts.timezone == "utc") {
+ return makeUtcWrapper(new Date(ts));
+ } else if (typeof timezoneJS != "undefined" && typeof timezoneJS.Date != "undefined") {
+ var d = new timezoneJS.Date();
+ // timezone-js is fickle, so be sure to set the time zone before
+ // setting the time.
+ d.setTimezone(opts.timezone);
+ d.setTime(ts);
+ return d;
+ } else {
+ return makeUtcWrapper(new Date(ts));
+ }
+ }
+
+ // map of app. size of time units in milliseconds
+
+ var timeUnitSize = {
+ "second": 1000,
+ "minute": 60 * 1000,
+ "hour": 60 * 60 * 1000,
+ "day": 24 * 60 * 60 * 1000,
+ "month": 30 * 24 * 60 * 60 * 1000,
+ "quarter": 3 * 30 * 24 * 60 * 60 * 1000,
+ "year": 365.2425 * 24 * 60 * 60 * 1000
+ };
+
+ // the allowed tick sizes, after 1 year we use
+ // an integer algorithm
+
+ var baseSpec = [
+ [1, "second"], [2, "second"], [5, "second"], [10, "second"],
+ [30, "second"],
+ [1, "minute"], [2, "minute"], [5, "minute"], [10, "minute"],
+ [30, "minute"],
+ [1, "hour"], [2, "hour"], [4, "hour"],
+ [8, "hour"], [12, "hour"],
+ [1, "day"], [2, "day"], [3, "day"],
+ [0.25, "month"], [0.5, "month"], [1, "month"],
+ [2, "month"]
+ ];
+
+ // we don't know which variant(s) we'll need yet, but generating both is
+ // cheap
+
+ var specMonths = baseSpec.concat([[3, "month"], [6, "month"],
+ [1, "year"]]);
+ var specQuarters = baseSpec.concat([[1, "quarter"], [2, "quarter"],
+ [1, "year"]]);
+
+ function init(plot) {
+ plot.hooks.processOptions.push(function (plot, options) {
+ $.each(plot.getAxes(), function(axisName, axis) {
+
+ var opts = axis.options;
+
+ if (opts.mode == "time") {
+ axis.tickGenerator = function(axis) {
+
+ var ticks = [];
+ var d = dateGenerator(axis.min, opts);
+ var minSize = 0;
+
+ // make quarter use a possibility if quarters are
+ // mentioned in either of these options
+
+ var spec = (opts.tickSize && opts.tickSize[1] ===
+ "quarter") ||
+ (opts.minTickSize && opts.minTickSize[1] ===
+ "quarter") ? specQuarters : specMonths;
+
+ if (opts.minTickSize != null) {
+ if (typeof opts.tickSize == "number") {
+ minSize = opts.tickSize;
+ } else {
+ minSize = opts.minTickSize[0] * timeUnitSize[opts.minTickSize[1]];
+ }
+ }
+
+ for (var i = 0; i < spec.length - 1; ++i) {
+ if (axis.delta < (spec[i][0] * timeUnitSize[spec[i][1]]
+ + spec[i + 1][0] * timeUnitSize[spec[i + 1][1]]) / 2
+ && spec[i][0] * timeUnitSize[spec[i][1]] >= minSize) {
+ break;
+ }
+ }
+
+ var size = spec[i][0];
+ var unit = spec[i][1];
+
+ // special-case the possibility of several years
+
+ if (unit == "year") {
+
+ // if given a minTickSize in years, just use it,
+ // ensuring that it's an integer
+
+ if (opts.minTickSize != null && opts.minTickSize[1] == "year") {
+ size = Math.floor(opts.minTickSize[0]);
+ } else {
+
+ var magn = Math.pow(10, Math.floor(Math.log(axis.delta / timeUnitSize.year) / Math.LN10));
+ var norm = (axis.delta / timeUnitSize.year) / magn;
+
+ if (norm < 1.5) {
+ size = 1;
+ } else if (norm < 3) {
+ size = 2;
+ } else if (norm < 7.5) {
+ size = 5;
+ } else {
+ size = 10;
+ }
+
+ size *= magn;
+ }
+
+ // minimum size for years is 1
+
+ if (size < 1) {
+ size = 1;
+ }
+ }
+
+ axis.tickSize = opts.tickSize || [size, unit];
+ var tickSize = axis.tickSize[0];
+ unit = axis.tickSize[1];
+
+ var step = tickSize * timeUnitSize[unit];
+
+ if (unit == "second") {
+ d.setSeconds(floorInBase(d.getSeconds(), tickSize));
+ } else if (unit == "minute") {
+ d.setMinutes(floorInBase(d.getMinutes(), tickSize));
+ } else if (unit == "hour") {
+ d.setHours(floorInBase(d.getHours(), tickSize));
+ } else if (unit == "month") {
+ d.setMonth(floorInBase(d.getMonth(), tickSize));
+ } else if (unit == "quarter") {
+ d.setMonth(3 * floorInBase(d.getMonth() / 3,
+ tickSize));
+ } else if (unit == "year") {
+ d.setFullYear(floorInBase(d.getFullYear(), tickSize));
+ }
+
+ // reset smaller components
+
+ d.setMilliseconds(0);
+
+ if (step >= timeUnitSize.minute) {
+ d.setSeconds(0);
+ }
+ if (step >= timeUnitSize.hour) {
+ d.setMinutes(0);
+ }
+ if (step >= timeUnitSize.day) {
+ d.setHours(0);
+ }
+ if (step >= timeUnitSize.day * 4) {
+ d.setDate(1);
+ }
+ if (step >= timeUnitSize.month * 2) {
+ d.setMonth(floorInBase(d.getMonth(), 3));
+ }
+ if (step >= timeUnitSize.quarter * 2) {
+ d.setMonth(floorInBase(d.getMonth(), 6));
+ }
+ if (step >= timeUnitSize.year) {
+ d.setMonth(0);
+ }
+
+ var carry = 0;
+ var v = Number.NaN;
+ var prev;
+
+ do {
+
+ prev = v;
+ v = d.getTime();
+ ticks.push(v);
+
+ if (unit == "month" || unit == "quarter") {
+ if (tickSize < 1) {
+
+ // a bit complicated - we'll divide the
+ // month/quarter up but we need to take
+ // care of fractions so we don't end up in
+ // the middle of a day
+
+ d.setDate(1);
+ var start = d.getTime();
+ d.setMonth(d.getMonth() +
+ (unit == "quarter" ? 3 : 1));
+ var end = d.getTime();
+ d.setTime(v + carry * timeUnitSize.hour + (end - start) * tickSize);
+ carry = d.getHours();
+ d.setHours(0);
+ } else {
+ d.setMonth(d.getMonth() +
+ tickSize * (unit == "quarter" ? 3 : 1));
+ }
+ } else if (unit == "year") {
+ d.setFullYear(d.getFullYear() + tickSize);
+ } else {
+ d.setTime(v + step);
+ }
+ } while (v < axis.max && v != prev);
+
+ return ticks;
+ };
+
+ axis.tickFormatter = function (v, axis) {
+
+ var d = dateGenerator(v, axis.options);
+
+ // first check global format
+
+ if (opts.timeformat != null) {
+ return formatDate(d, opts.timeformat, opts.monthNames, opts.dayNames);
+ }
+
+ // possibly use quarters if quarters are mentioned in
+ // any of these places
+
+ var useQuarters = (axis.options.tickSize &&
+ axis.options.tickSize[1] == "quarter") ||
+ (axis.options.minTickSize &&
+ axis.options.minTickSize[1] == "quarter");
+
+ var t = axis.tickSize[0] * timeUnitSize[axis.tickSize[1]];
+ var span = axis.max - axis.min;
+ var suffix = (opts.twelveHourClock) ? " %p" : "";
+ var hourCode = (opts.twelveHourClock) ? "%I" : "%H";
+ var fmt;
+
+ if (t < timeUnitSize.minute) {
+ fmt = hourCode + ":%M:%S" + suffix;
+ } else if (t < timeUnitSize.day) {
+ if (span < 2 * timeUnitSize.day) {
+ fmt = hourCode + ":%M" + suffix;
+ } else {
+ fmt = "%b %d " + hourCode + ":%M" + suffix;
+ }
+ } else if (t < timeUnitSize.month) {
+ fmt = "%b %d";
+ } else if ((useQuarters && t < timeUnitSize.quarter) ||
+ (!useQuarters && t < timeUnitSize.year)) {
+ if (span < timeUnitSize.year) {
+ fmt = "%b";
+ } else {
+ fmt = "%b %Y";
+ }
+ } else if (useQuarters && t < timeUnitSize.year) {
+ if (span < timeUnitSize.year) {
+ fmt = "Q%q";
+ } else {
+ fmt = "Q%q %Y";
+ }
+ } else {
+ fmt = "%Y";
+ }
+
+ var rt = formatDate(d, fmt, opts.monthNames, opts.dayNames);
+
+ return rt;
+ };
+ }
+ });
+ });
+ }
+
+ $.plot.plugins.push({
+ init: init,
+ options: options,
+ name: 'time',
+ version: '1.0'
+ });
+
+ // Time-axis support used to be in Flot core, which exposed the
+ // formatDate function on the plot object. Various plugins depend
+ // on the function, so we need to re-expose it here.
+
+ $.plot.formatDate = formatDate;
+ $.plot.dateGenerator = dateGenerator;
+
+})(jQuery);
diff --git a/djangoproject/static/js/lib/jquery-flot/jquery.flot.crosshair.js b/djangoproject/static/js/lib/jquery-flot/jquery.flot.crosshair.js
new file mode 100644
index 000000000..5111695e3
--- /dev/null
+++ b/djangoproject/static/js/lib/jquery-flot/jquery.flot.crosshair.js
@@ -0,0 +1,176 @@
+/* Flot plugin for showing crosshairs when the mouse hovers over the plot.
+
+Copyright (c) 2007-2014 IOLA and Ole Laursen.
+Licensed under the MIT license.
+
+The plugin supports these options:
+
+ crosshair: {
+ mode: null or "x" or "y" or "xy"
+ color: color
+ lineWidth: number
+ }
+
+Set the mode to one of "x", "y" or "xy". The "x" mode enables a vertical
+crosshair that lets you trace the values on the x axis, "y" enables a
+horizontal crosshair and "xy" enables them both. "color" is the color of the
+crosshair (default is "rgba(170, 0, 0, 0.80)"), "lineWidth" is the width of
+the drawn lines (default is 1).
+
+The plugin also adds four public methods:
+
+ - setCrosshair( pos )
+
+ Set the position of the crosshair. Note that this is cleared if the user
+ moves the mouse. "pos" is in coordinates of the plot and should be on the
+ form { x: xpos, y: ypos } (you can use x2/x3/... if you're using multiple
+ axes), which is coincidentally the same format as what you get from a
+ "plothover" event. If "pos" is null, the crosshair is cleared.
+
+ - clearCrosshair()
+
+ Clear the crosshair.
+
+ - lockCrosshair(pos)
+
+ Cause the crosshair to lock to the current location, no longer updating if
+ the user moves the mouse. Optionally supply a position (passed on to
+ setCrosshair()) to move it to.
+
+ Example usage:
+
+ var myFlot = $.plot( $("#graph"), ..., { crosshair: { mode: "x" } } };
+ $("#graph").bind( "plothover", function ( evt, position, item ) {
+ if ( item ) {
+ // Lock the crosshair to the data point being hovered
+ myFlot.lockCrosshair({
+ x: item.datapoint[ 0 ],
+ y: item.datapoint[ 1 ]
+ });
+ } else {
+ // Return normal crosshair operation
+ myFlot.unlockCrosshair();
+ }
+ });
+
+ - unlockCrosshair()
+
+ Free the crosshair to move again after locking it.
+*/
+
+(function ($) {
+ var options = {
+ crosshair: {
+ mode: null, // one of null, "x", "y" or "xy",
+ color: "rgba(170, 0, 0, 0.80)",
+ lineWidth: 1
+ }
+ };
+
+ function init(plot) {
+ // position of crosshair in pixels
+ var crosshair = { x: -1, y: -1, locked: false };
+
+ plot.setCrosshair = function setCrosshair(pos) {
+ if (!pos)
+ crosshair.x = -1;
+ else {
+ var o = plot.p2c(pos);
+ crosshair.x = Math.max(0, Math.min(o.left, plot.width()));
+ crosshair.y = Math.max(0, Math.min(o.top, plot.height()));
+ }
+
+ plot.triggerRedrawOverlay();
+ };
+
+ plot.clearCrosshair = plot.setCrosshair; // passes null for pos
+
+ plot.lockCrosshair = function lockCrosshair(pos) {
+ if (pos)
+ plot.setCrosshair(pos);
+ crosshair.locked = true;
+ };
+
+ plot.unlockCrosshair = function unlockCrosshair() {
+ crosshair.locked = false;
+ };
+
+ function onMouseOut(e) {
+ if (crosshair.locked)
+ return;
+
+ if (crosshair.x != -1) {
+ crosshair.x = -1;
+ plot.triggerRedrawOverlay();
+ }
+ }
+
+ function onMouseMove(e) {
+ if (crosshair.locked)
+ return;
+
+ if (plot.getSelection && plot.getSelection()) {
+ crosshair.x = -1; // hide the crosshair while selecting
+ return;
+ }
+
+ var offset = plot.offset();
+ crosshair.x = Math.max(0, Math.min(e.pageX - offset.left, plot.width()));
+ crosshair.y = Math.max(0, Math.min(e.pageY - offset.top, plot.height()));
+ plot.triggerRedrawOverlay();
+ }
+
+ plot.hooks.bindEvents.push(function (plot, eventHolder) {
+ if (!plot.getOptions().crosshair.mode)
+ return;
+
+ eventHolder.mouseout(onMouseOut);
+ eventHolder.mousemove(onMouseMove);
+ });
+
+ plot.hooks.drawOverlay.push(function (plot, ctx) {
+ var c = plot.getOptions().crosshair;
+ if (!c.mode)
+ return;
+
+ var plotOffset = plot.getPlotOffset();
+
+ ctx.save();
+ ctx.translate(plotOffset.left, plotOffset.top);
+
+ if (crosshair.x != -1) {
+ var adj = plot.getOptions().crosshair.lineWidth % 2 ? 0.5 : 0;
+
+ ctx.strokeStyle = c.color;
+ ctx.lineWidth = c.lineWidth;
+ ctx.lineJoin = "round";
+
+ ctx.beginPath();
+ if (c.mode.indexOf("x") != -1) {
+ var drawX = Math.floor(crosshair.x) + adj;
+ ctx.moveTo(drawX, 0);
+ ctx.lineTo(drawX, plot.height());
+ }
+ if (c.mode.indexOf("y") != -1) {
+ var drawY = Math.floor(crosshair.y) + adj;
+ ctx.moveTo(0, drawY);
+ ctx.lineTo(plot.width(), drawY);
+ }
+ ctx.stroke();
+ }
+ ctx.restore();
+ });
+
+ plot.hooks.shutdown.push(function (plot, eventHolder) {
+ eventHolder.unbind("mouseout", onMouseOut);
+ eventHolder.unbind("mousemove", onMouseMove);
+ });
+ }
+
+ $.plot.plugins.push({
+ init: init,
+ options: options,
+ name: 'crosshair',
+ version: '1.0'
+ });
+})(jQuery);
diff --git a/djangoproject/static/js/lib/jquery-flot/jquery.flot.errorbars.js b/djangoproject/static/js/lib/jquery-flot/jquery.flot.errorbars.js
new file mode 100644
index 000000000..2583d5c20
--- /dev/null
+++ b/djangoproject/static/js/lib/jquery-flot/jquery.flot.errorbars.js
@@ -0,0 +1,353 @@
+/* Flot plugin for plotting error bars.
+
+Copyright (c) 2007-2014 IOLA and Ole Laursen.
+Licensed under the MIT license.
+
+Error bars are used to show standard deviation and other statistical
+properties in a plot.
+
+* Created by Rui Pereira - rui (dot) pereira (at) gmail (dot) com
+
+This plugin allows you to plot error-bars over points. Set "errorbars" inside
+the points series to the axis name over which there will be error values in
+your data array (*even* if you do not intend to plot them later, by setting
+"show: null" on xerr/yerr).
+
+The plugin supports these options:
+
+ series: {
+ points: {
+ errorbars: "x" or "y" or "xy",
+ xerr: {
+ show: null/false or true,
+ asymmetric: null/false or true,
+ upperCap: null or "-" or function,
+ lowerCap: null or "-" or function,
+ color: null or color,
+ radius: null or number
+ },
+ yerr: { same options as xerr }
+ }
+ }
+
+Each data point array is expected to be of the type:
+
+ "x" [ x, y, xerr ]
+ "y" [ x, y, yerr ]
+ "xy" [ x, y, xerr, yerr ]
+
+Where xerr becomes xerr_lower,xerr_upper for the asymmetric error case, and
+equivalently for yerr. Eg., a datapoint for the "xy" case with symmetric
+error-bars on X and asymmetric on Y would be:
+
+ [ x, y, xerr, yerr_lower, yerr_upper ]
+
+By default no end caps are drawn. Setting upperCap and/or lowerCap to "-" will
+draw a small cap perpendicular to the error bar. They can also be set to a
+user-defined drawing function, with (ctx, x, y, radius) as parameters, as eg.
+
+ function drawSemiCircle( ctx, x, y, radius ) {
+ ctx.beginPath();
+ ctx.arc( x, y, radius, 0, Math.PI, false );
+ ctx.moveTo( x - radius, y );
+ ctx.lineTo( x + radius, y );
+ ctx.stroke();
+ }
+
+Color and radius both default to the same ones of the points series if not
+set. The independent radius parameter on xerr/yerr is useful for the case when
+we may want to add error-bars to a line, without showing the interconnecting
+points (with radius: 0), and still showing end caps on the error-bars.
+shadowSize and lineWidth are derived as well from the points series.
+
+*/
+
+(function ($) {
+ var options = {
+ series: {
+ points: {
+ errorbars: null, //should be 'x', 'y' or 'xy'
+ xerr: { err: 'x', show: null, asymmetric: null, upperCap: null, lowerCap: null, color: null, radius: null},
+ yerr: { err: 'y', show: null, asymmetric: null, upperCap: null, lowerCap: null, color: null, radius: null}
+ }
+ }
+ };
+
+ function processRawData(plot, series, data, datapoints){
+ if (!series.points.errorbars)
+ return;
+
+ // x,y values
+ var format = [
+ { x: true, number: true, required: true },
+ { y: true, number: true, required: true }
+ ];
+
+ var errors = series.points.errorbars;
+ // error bars - first X then Y
+ if (errors == 'x' || errors == 'xy') {
+ // lower / upper error
+ if (series.points.xerr.asymmetric) {
+ format.push({ x: true, number: true, required: true });
+ format.push({ x: true, number: true, required: true });
+ } else
+ format.push({ x: true, number: true, required: true });
+ }
+ if (errors == 'y' || errors == 'xy') {
+ // lower / upper error
+ if (series.points.yerr.asymmetric) {
+ format.push({ y: true, number: true, required: true });
+ format.push({ y: true, number: true, required: true });
+ } else
+ format.push({ y: true, number: true, required: true });
+ }
+ datapoints.format = format;
+ }
+
+ function parseErrors(series, i){
+
+ var points = series.datapoints.points;
+
+ // read errors from points array
+ var exl = null,
+ exu = null,
+ eyl = null,
+ eyu = null;
+ var xerr = series.points.xerr,
+ yerr = series.points.yerr;
+
+ var eb = series.points.errorbars;
+ // error bars - first X
+ if (eb == 'x' || eb == 'xy') {
+ if (xerr.asymmetric) {
+ exl = points[i + 2];
+ exu = points[i + 3];
+ if (eb == 'xy')
+ if (yerr.asymmetric){
+ eyl = points[i + 4];
+ eyu = points[i + 5];
+ } else eyl = points[i + 4];
+ } else {
+ exl = points[i + 2];
+ if (eb == 'xy')
+ if (yerr.asymmetric) {
+ eyl = points[i + 3];
+ eyu = points[i + 4];
+ } else eyl = points[i + 3];
+ }
+ // only Y
+ } else if (eb == 'y')
+ if (yerr.asymmetric) {
+ eyl = points[i + 2];
+ eyu = points[i + 3];
+ } else eyl = points[i + 2];
+
+ // symmetric errors?
+ if (exu == null) exu = exl;
+ if (eyu == null) eyu = eyl;
+
+ var errRanges = [exl, exu, eyl, eyu];
+ // nullify if not showing
+ if (!xerr.show){
+ errRanges[0] = null;
+ errRanges[1] = null;
+ }
+ if (!yerr.show){
+ errRanges[2] = null;
+ errRanges[3] = null;
+ }
+ return errRanges;
+ }
+
+ function drawSeriesErrors(plot, ctx, s){
+
+ var points = s.datapoints.points,
+ ps = s.datapoints.pointsize,
+ ax = [s.xaxis, s.yaxis],
+ radius = s.points.radius,
+ err = [s.points.xerr, s.points.yerr];
+
+ //sanity check, in case some inverted axis hack is applied to flot
+ var invertX = false;
+ if (ax[0].p2c(ax[0].max) < ax[0].p2c(ax[0].min)) {
+ invertX = true;
+ var tmp = err[0].lowerCap;
+ err[0].lowerCap = err[0].upperCap;
+ err[0].upperCap = tmp;
+ }
+
+ var invertY = false;
+ if (ax[1].p2c(ax[1].min) < ax[1].p2c(ax[1].max)) {
+ invertY = true;
+ var tmp = err[1].lowerCap;
+ err[1].lowerCap = err[1].upperCap;
+ err[1].upperCap = tmp;
+ }
+
+ for (var i = 0; i < s.datapoints.points.length; i += ps) {
+
+ //parse
+ var errRanges = parseErrors(s, i);
+
+ //cycle xerr & yerr
+ for (var e = 0; e < err.length; e++){
+
+ var minmax = [ax[e].min, ax[e].max];
+
+ //draw this error?
+ if (errRanges[e * err.length]){
+
+ //data coordinates
+ var x = points[i],
+ y = points[i + 1];
+
+ //errorbar ranges
+ var upper = [x, y][e] + errRanges[e * err.length + 1],
+ lower = [x, y][e] - errRanges[e * err.length];
+
+ //points outside of the canvas
+ if (err[e].err == 'x')
+ if (y > ax[1].max || y < ax[1].min || upper < ax[0].min || lower > ax[0].max)
+ continue;
+ if (err[e].err == 'y')
+ if (x > ax[0].max || x < ax[0].min || upper < ax[1].min || lower > ax[1].max)
+ continue;
+
+ // prevent errorbars getting out of the canvas
+ var drawUpper = true,
+ drawLower = true;
+
+ if (upper > minmax[1]) {
+ drawUpper = false;
+ upper = minmax[1];
+ }
+ if (lower < minmax[0]) {
+ drawLower = false;
+ lower = minmax[0];
+ }
+
+ //sanity check, in case some inverted axis hack is applied to flot
+ if ((err[e].err == 'x' && invertX) || (err[e].err == 'y' && invertY)) {
+ //swap coordinates
+ var tmp = lower;
+ lower = upper;
+ upper = tmp;
+ tmp = drawLower;
+ drawLower = drawUpper;
+ drawUpper = tmp;
+ tmp = minmax[0];
+ minmax[0] = minmax[1];
+ minmax[1] = tmp;
+ }
+
+ // convert to pixels
+ x = ax[0].p2c(x),
+ y = ax[1].p2c(y),
+ upper = ax[e].p2c(upper);
+ lower = ax[e].p2c(lower);
+ minmax[0] = ax[e].p2c(minmax[0]);
+ minmax[1] = ax[e].p2c(minmax[1]);
+
+ //same style as points by default
+ var lw = err[e].lineWidth ? err[e].lineWidth : s.points.lineWidth,
+ sw = s.points.shadowSize != null ? s.points.shadowSize : s.shadowSize;
+
+ //shadow as for points
+ if (lw > 0 && sw > 0) {
+ var w = sw / 2;
+ ctx.lineWidth = w;
+ ctx.strokeStyle = "rgba(0,0,0,0.1)";
+ drawError(ctx, err[e], x, y, upper, lower, drawUpper, drawLower, radius, w + w/2, minmax);
+
+ ctx.strokeStyle = "rgba(0,0,0,0.2)";
+ drawError(ctx, err[e], x, y, upper, lower, drawUpper, drawLower, radius, w/2, minmax);
+ }
+
+ ctx.strokeStyle = err[e].color? err[e].color: s.color;
+ ctx.lineWidth = lw;
+ //draw it
+ drawError(ctx, err[e], x, y, upper, lower, drawUpper, drawLower, radius, 0, minmax);
+ }
+ }
+ }
+ }
+
+ function drawError(ctx,err,x,y,upper,lower,drawUpper,drawLower,radius,offset,minmax){
+
+ //shadow offset
+ y += offset;
+ upper += offset;
+ lower += offset;
+
+ // error bar - avoid plotting over circles
+ if (err.err == 'x'){
+ if (upper > x + radius) drawPath(ctx, [[upper,y],[Math.max(x + radius,minmax[0]),y]]);
+ else drawUpper = false;
+ if (lower < x - radius) drawPath(ctx, [[Math.min(x - radius,minmax[1]),y],[lower,y]] );
+ else drawLower = false;
+ }
+ else {
+ if (upper < y - radius) drawPath(ctx, [[x,upper],[x,Math.min(y - radius,minmax[0])]] );
+ else drawUpper = false;
+ if (lower > y + radius) drawPath(ctx, [[x,Math.max(y + radius,minmax[1])],[x,lower]] );
+ else drawLower = false;
+ }
+
+ //internal radius value in errorbar, allows to plot radius 0 points and still keep proper sized caps
+ //this is a way to get errorbars on lines without visible connecting dots
+ radius = err.radius != null? err.radius: radius;
+
+ // upper cap
+ if (drawUpper) {
+ if (err.upperCap == '-'){
+ if (err.err=='x') drawPath(ctx, [[upper,y - radius],[upper,y + radius]] );
+ else drawPath(ctx, [[x - radius,upper],[x + radius,upper]] );
+ } else if ($.isFunction(err.upperCap)){
+ if (err.err=='x') err.upperCap(ctx, upper, y, radius);
+ else err.upperCap(ctx, x, upper, radius);
+ }
+ }
+ // lower cap
+ if (drawLower) {
+ if (err.lowerCap == '-'){
+ if (err.err=='x') drawPath(ctx, [[lower,y - radius],[lower,y + radius]] );
+ else drawPath(ctx, [[x - radius,lower],[x + radius,lower]] );
+ } else if ($.isFunction(err.lowerCap)){
+ if (err.err=='x') err.lowerCap(ctx, lower, y, radius);
+ else err.lowerCap(ctx, x, lower, radius);
+ }
+ }
+ }
+
+ function drawPath(ctx, pts){
+ ctx.beginPath();
+ ctx.moveTo(pts[0][0], pts[0][1]);
+ for (var p=1; p < pts.length; p++)
+ ctx.lineTo(pts[p][0], pts[p][1]);
+ ctx.stroke();
+ }
+
+ function draw(plot, ctx){
+ var plotOffset = plot.getPlotOffset();
+
+ ctx.save();
+ ctx.translate(plotOffset.left, plotOffset.top);
+ $.each(plot.getData(), function (i, s) {
+ if (s.points.errorbars && (s.points.xerr.show || s.points.yerr.show))
+ drawSeriesErrors(plot, ctx, s);
+ });
+ ctx.restore();
+ }
+
+ function init(plot) {
+ plot.hooks.processRawData.push(processRawData);
+ plot.hooks.draw.push(draw);
+ }
+
+ $.plot.plugins.push({
+ init: init,
+ options: options,
+ name: 'errorbars',
+ version: '1.0'
+ });
+})(jQuery);
diff --git a/djangoproject/static/js/lib/jquery-flot/jquery.flot.fillbetween.js b/djangoproject/static/js/lib/jquery-flot/jquery.flot.fillbetween.js
new file mode 100644
index 000000000..18b15d26d
--- /dev/null
+++ b/djangoproject/static/js/lib/jquery-flot/jquery.flot.fillbetween.js
@@ -0,0 +1,226 @@
+/* Flot plugin for computing bottoms for filled line and bar charts.
+
+Copyright (c) 2007-2014 IOLA and Ole Laursen.
+Licensed under the MIT license.
+
+The case: you've got two series that you want to fill the area between. In Flot
+terms, you need to use one as the fill bottom of the other. You can specify the
+bottom of each data point as the third coordinate manually, or you can use this
+plugin to compute it for you.
+
+In order to name the other series, you need to give it an id, like this:
+
+ var dataset = [
+ { data: [ ... ], id: "foo" } , // use default bottom
+ { data: [ ... ], fillBetween: "foo" }, // use first dataset as bottom
+ ];
+
+ $.plot($("#placeholder"), dataset, { lines: { show: true, fill: true }});
+
+As a convenience, if the id given is a number that doesn't appear as an id in
+the series, it is interpreted as the index in the array instead (so fillBetween:
+0 can also mean the first series).
+
+Internally, the plugin modifies the datapoints in each series. For line series,
+extra data points might be inserted through interpolation. Note that at points
+where the bottom line is not defined (due to a null point or start/end of line),
+the current line will show a gap too. The algorithm comes from the
+jquery.flot.stack.js plugin, possibly some code could be shared.
+
+*/
+
+(function ( $ ) {
+
+ var options = {
+ series: {
+ fillBetween: null // or number
+ }
+ };
+
+ function init( plot ) {
+
+ function findBottomSeries( s, allseries ) {
+
+ var i;
+
+ for ( i = 0; i < allseries.length; ++i ) {
+ if ( allseries[ i ].id === s.fillBetween ) {
+ return allseries[ i ];
+ }
+ }
+
+ if ( typeof s.fillBetween === "number" ) {
+ if ( s.fillBetween < 0 || s.fillBetween >= allseries.length ) {
+ return null;
+ }
+ return allseries[ s.fillBetween ];
+ }
+
+ return null;
+ }
+
+ function computeFillBottoms( plot, s, datapoints ) {
+
+ if ( s.fillBetween == null ) {
+ return;
+ }
+
+ var other = findBottomSeries( s, plot.getData() );
+
+ if ( !other ) {
+ return;
+ }
+
+ var ps = datapoints.pointsize,
+ points = datapoints.points,
+ otherps = other.datapoints.pointsize,
+ otherpoints = other.datapoints.points,
+ newpoints = [],
+ px, py, intery, qx, qy, bottom,
+ withlines = s.lines.show,
+ withbottom = ps > 2 && datapoints.format[2].y,
+ withsteps = withlines && s.lines.steps,
+ fromgap = true,
+ i = 0,
+ j = 0,
+ l, m;
+
+ while ( true ) {
+
+ if ( i >= points.length ) {
+ break;
+ }
+
+ l = newpoints.length;
+
+ if ( points[ i ] == null ) {
+
+ // copy gaps
+
+ for ( m = 0; m < ps; ++m ) {
+ newpoints.push( points[ i + m ] );
+ }
+
+ i += ps;
+
+ } else if ( j >= otherpoints.length ) {
+
+ // for lines, we can't use the rest of the points
+
+ if ( !withlines ) {
+ for ( m = 0; m < ps; ++m ) {
+ newpoints.push( points[ i + m ] );
+ }
+ }
+
+ i += ps;
+
+ } else if ( otherpoints[ j ] == null ) {
+
+ // oops, got a gap
+
+ for ( m = 0; m < ps; ++m ) {
+ newpoints.push( null );
+ }
+
+ fromgap = true;
+ j += otherps;
+
+ } else {
+
+ // cases where we actually got two points
+
+ px = points[ i ];
+ py = points[ i + 1 ];
+ qx = otherpoints[ j ];
+ qy = otherpoints[ j + 1 ];
+ bottom = 0;
+
+ if ( px === qx ) {
+
+ for ( m = 0; m < ps; ++m ) {
+ newpoints.push( points[ i + m ] );
+ }
+
+ //newpoints[ l + 1 ] += qy;
+ bottom = qy;
+
+ i += ps;
+ j += otherps;
+
+ } else if ( px > qx ) {
+
+ // we got past point below, might need to
+ // insert interpolated extra point
+
+ if ( withlines && i > 0 && points[ i - ps ] != null ) {
+ intery = py + ( points[ i - ps + 1 ] - py ) * ( qx - px ) / ( points[ i - ps ] - px );
+ newpoints.push( qx );
+ newpoints.push( intery );
+ for ( m = 2; m < ps; ++m ) {
+ newpoints.push( points[ i + m ] );
+ }
+ bottom = qy;
+ }
+
+ j += otherps;
+
+ } else { // px < qx
+
+ // if we come from a gap, we just skip this point
+
+ if ( fromgap && withlines ) {
+ i += ps;
+ continue;
+ }
+
+ for ( m = 0; m < ps; ++m ) {
+ newpoints.push( points[ i + m ] );
+ }
+
+ // we might be able to interpolate a point below,
+ // this can give us a better y
+
+ if ( withlines && j > 0 && otherpoints[ j - otherps ] != null ) {
+ bottom = qy + ( otherpoints[ j - otherps + 1 ] - qy ) * ( px - qx ) / ( otherpoints[ j - otherps ] - qx );
+ }
+
+ //newpoints[l + 1] += bottom;
+
+ i += ps;
+ }
+
+ fromgap = false;
+
+ if ( l !== newpoints.length && withbottom ) {
+ newpoints[ l + 2 ] = bottom;
+ }
+ }
+
+ // maintain the line steps invariant
+
+ if ( withsteps && l !== newpoints.length && l > 0 &&
+ newpoints[ l ] !== null &&
+ newpoints[ l ] !== newpoints[ l - ps ] &&
+ newpoints[ l + 1 ] !== newpoints[ l - ps + 1 ] ) {
+ for (m = 0; m < ps; ++m) {
+ newpoints[ l + ps + m ] = newpoints[ l + m ];
+ }
+ newpoints[ l + 1 ] = newpoints[ l - ps + 1 ];
+ }
+ }
+
+ datapoints.points = newpoints;
+ }
+
+ plot.hooks.processDatapoints.push( computeFillBottoms );
+ }
+
+ $.plot.plugins.push({
+ init: init,
+ options: options,
+ name: "fillbetween",
+ version: "1.0"
+ });
+
+})(jQuery);
diff --git a/djangoproject/static/js/lib/jquery-flot/jquery.flot.image.js b/djangoproject/static/js/lib/jquery-flot/jquery.flot.image.js
new file mode 100644
index 000000000..625a03571
--- /dev/null
+++ b/djangoproject/static/js/lib/jquery-flot/jquery.flot.image.js
@@ -0,0 +1,241 @@
+/* Flot plugin for plotting images.
+
+Copyright (c) 2007-2014 IOLA and Ole Laursen.
+Licensed under the MIT license.
+
+The data syntax is [ [ image, x1, y1, x2, y2 ], ... ] where (x1, y1) and
+(x2, y2) are where you intend the two opposite corners of the image to end up
+in the plot. Image must be a fully loaded Javascript image (you can make one
+with new Image()). If the image is not complete, it's skipped when plotting.
+
+There are two helpers included for retrieving images. The easiest work the way
+that you put in URLs instead of images in the data, like this:
+
+ [ "myimage.png", 0, 0, 10, 10 ]
+
+Then call $.plot.image.loadData( data, options, callback ) where data and
+options are the same as you pass in to $.plot. This loads the images, replaces
+the URLs in the data with the corresponding images and calls "callback" when
+all images are loaded (or failed loading). In the callback, you can then call
+$.plot with the data set. See the included example.
+
+A more low-level helper, $.plot.image.load(urls, callback) is also included.
+Given a list of URLs, it calls callback with an object mapping from URL to
+Image object when all images are loaded or have failed loading.
+
+The plugin supports these options:
+
+ series: {
+ images: {
+ show: boolean
+ anchor: "corner" or "center"
+ alpha: [ 0, 1 ]
+ }
+ }
+
+They can be specified for a specific series:
+
+ $.plot( $("#placeholder"), [{
+ data: [ ... ],
+ images: { ... }
+ ])
+
+Note that because the data format is different from usual data points, you
+can't use images with anything else in a specific data series.
+
+Setting "anchor" to "center" causes the pixels in the image to be anchored at
+the corner pixel centers inside of at the pixel corners, effectively letting
+half a pixel stick out to each side in the plot.
+
+A possible future direction could be support for tiling for large images (like
+Google Maps).
+
+*/
+
+(function ($) {
+ var options = {
+ series: {
+ images: {
+ show: false,
+ alpha: 1,
+ anchor: "corner" // or "center"
+ }
+ }
+ };
+
+ $.plot.image = {};
+
+ $.plot.image.loadDataImages = function (series, options, callback) {
+ var urls = [], points = [];
+
+ var defaultShow = options.series.images.show;
+
+ $.each(series, function (i, s) {
+ if (!(defaultShow || s.images.show))
+ return;
+
+ if (s.data)
+ s = s.data;
+
+ $.each(s, function (i, p) {
+ if (typeof p[0] == "string") {
+ urls.push(p[0]);
+ points.push(p);
+ }
+ });
+ });
+
+ $.plot.image.load(urls, function (loadedImages) {
+ $.each(points, function (i, p) {
+ var url = p[0];
+ if (loadedImages[url])
+ p[0] = loadedImages[url];
+ });
+
+ callback();
+ });
+ }
+
+ $.plot.image.load = function (urls, callback) {
+ var missing = urls.length, loaded = {};
+ if (missing == 0)
+ callback({});
+
+ $.each(urls, function (i, url) {
+ var handler = function () {
+ --missing;
+
+ loaded[url] = this;
+
+ if (missing == 0)
+ callback(loaded);
+ };
+
+ $(' ').load(handler).error(handler).attr('src', url);
+ });
+ };
+
+ function drawSeries(plot, ctx, series) {
+ var plotOffset = plot.getPlotOffset();
+
+ if (!series.images || !series.images.show)
+ return;
+
+ var points = series.datapoints.points,
+ ps = series.datapoints.pointsize;
+
+ for (var i = 0; i < points.length; i += ps) {
+ var img = points[i],
+ x1 = points[i + 1], y1 = points[i + 2],
+ x2 = points[i + 3], y2 = points[i + 4],
+ xaxis = series.xaxis, yaxis = series.yaxis,
+ tmp;
+
+ // actually we should check img.complete, but it
+ // appears to be a somewhat unreliable indicator in
+ // IE6 (false even after load event)
+ if (!img || img.width <= 0 || img.height <= 0)
+ continue;
+
+ if (x1 > x2) {
+ tmp = x2;
+ x2 = x1;
+ x1 = tmp;
+ }
+ if (y1 > y2) {
+ tmp = y2;
+ y2 = y1;
+ y1 = tmp;
+ }
+
+ // if the anchor is at the center of the pixel, expand the
+ // image by 1/2 pixel in each direction
+ if (series.images.anchor == "center") {
+ tmp = 0.5 * (x2-x1) / (img.width - 1);
+ x1 -= tmp;
+ x2 += tmp;
+ tmp = 0.5 * (y2-y1) / (img.height - 1);
+ y1 -= tmp;
+ y2 += tmp;
+ }
+
+ // clip
+ if (x1 == x2 || y1 == y2 ||
+ x1 >= xaxis.max || x2 <= xaxis.min ||
+ y1 >= yaxis.max || y2 <= yaxis.min)
+ continue;
+
+ var sx1 = 0, sy1 = 0, sx2 = img.width, sy2 = img.height;
+ if (x1 < xaxis.min) {
+ sx1 += (sx2 - sx1) * (xaxis.min - x1) / (x2 - x1);
+ x1 = xaxis.min;
+ }
+
+ if (x2 > xaxis.max) {
+ sx2 += (sx2 - sx1) * (xaxis.max - x2) / (x2 - x1);
+ x2 = xaxis.max;
+ }
+
+ if (y1 < yaxis.min) {
+ sy2 += (sy1 - sy2) * (yaxis.min - y1) / (y2 - y1);
+ y1 = yaxis.min;
+ }
+
+ if (y2 > yaxis.max) {
+ sy1 += (sy1 - sy2) * (yaxis.max - y2) / (y2 - y1);
+ y2 = yaxis.max;
+ }
+
+ x1 = xaxis.p2c(x1);
+ x2 = xaxis.p2c(x2);
+ y1 = yaxis.p2c(y1);
+ y2 = yaxis.p2c(y2);
+
+ // the transformation may have swapped us
+ if (x1 > x2) {
+ tmp = x2;
+ x2 = x1;
+ x1 = tmp;
+ }
+ if (y1 > y2) {
+ tmp = y2;
+ y2 = y1;
+ y1 = tmp;
+ }
+
+ tmp = ctx.globalAlpha;
+ ctx.globalAlpha *= series.images.alpha;
+ ctx.drawImage(img,
+ sx1, sy1, sx2 - sx1, sy2 - sy1,
+ x1 + plotOffset.left, y1 + plotOffset.top,
+ x2 - x1, y2 - y1);
+ ctx.globalAlpha = tmp;
+ }
+ }
+
+ function processRawData(plot, series, data, datapoints) {
+ if (!series.images.show)
+ return;
+
+ // format is Image, x1, y1, x2, y2 (opposite corners)
+ datapoints.format = [
+ { required: true },
+ { x: true, number: true, required: true },
+ { y: true, number: true, required: true },
+ { x: true, number: true, required: true },
+ { y: true, number: true, required: true }
+ ];
+ }
+
+ function init(plot) {
+ plot.hooks.processRawData.push(processRawData);
+ plot.hooks.drawSeries.push(drawSeries);
+ }
+
+ $.plot.plugins.push({
+ init: init,
+ options: options,
+ name: 'image',
+ version: '1.1'
+ });
+})(jQuery);
diff --git a/djangoproject/static/js/lib/jquery-flot/jquery.flot.js b/djangoproject/static/js/lib/jquery-flot/jquery.flot.js
new file mode 100644
index 000000000..39f3e4cf3
--- /dev/null
+++ b/djangoproject/static/js/lib/jquery-flot/jquery.flot.js
@@ -0,0 +1,3168 @@
+/* Javascript plotting library for jQuery, version 0.8.3.
+
+Copyright (c) 2007-2014 IOLA and Ole Laursen.
+Licensed under the MIT license.
+
+*/
+
+// first an inline dependency, jquery.colorhelpers.js, we inline it here
+// for convenience
+
+/* Plugin for jQuery for working with colors.
+ *
+ * Version 1.1.
+ *
+ * Inspiration from jQuery color animation plugin by John Resig.
+ *
+ * Released under the MIT license by Ole Laursen, October 2009.
+ *
+ * Examples:
+ *
+ * $.color.parse("#fff").scale('rgb', 0.25).add('a', -0.5).toString()
+ * var c = $.color.extract($("#mydiv"), 'background-color');
+ * console.log(c.r, c.g, c.b, c.a);
+ * $.color.make(100, 50, 25, 0.4).toString() // returns "rgba(100,50,25,0.4)"
+ *
+ * Note that .scale() and .add() return the same modified object
+ * instead of making a new one.
+ *
+ * V. 1.1: Fix error handling so e.g. parsing an empty string does
+ * produce a color rather than just crashing.
+ */
+(function($){$.color={};$.color.make=function(r,g,b,a){var o={};o.r=r||0;o.g=g||0;o.b=b||0;o.a=a!=null?a:1;o.add=function(c,d){for(var i=0;i=1){return"rgb("+[o.r,o.g,o.b].join(",")+")"}else{return"rgba("+[o.r,o.g,o.b,o.a].join(",")+")"}};o.normalize=function(){function clamp(min,value,max){return valuemax?max:value}o.r=clamp(0,parseInt(o.r),255);o.g=clamp(0,parseInt(o.g),255);o.b=clamp(0,parseInt(o.b),255);o.a=clamp(0,o.a,1);return o};o.clone=function(){return $.color.make(o.r,o.b,o.g,o.a)};return o.normalize()};$.color.extract=function(elem,css){var c;do{c=elem.css(css).toLowerCase();if(c!=""&&c!="transparent")break;elem=elem.parent()}while(elem.length&&!$.nodeName(elem.get(0),"body"));if(c=="rgba(0, 0, 0, 0)")c="transparent";return $.color.parse(c)};$.color.parse=function(str){var res,m=$.color.make;if(res=/rgb\(\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*\)/.exec(str))return m(parseInt(res[1],10),parseInt(res[2],10),parseInt(res[3],10));if(res=/rgba\(\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*,\s*([0-9]+(?:\.[0-9]+)?)\s*\)/.exec(str))return m(parseInt(res[1],10),parseInt(res[2],10),parseInt(res[3],10),parseFloat(res[4]));if(res=/rgb\(\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\%\s*\)/.exec(str))return m(parseFloat(res[1])*2.55,parseFloat(res[2])*2.55,parseFloat(res[3])*2.55);if(res=/rgba\(\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\s*\)/.exec(str))return m(parseFloat(res[1])*2.55,parseFloat(res[2])*2.55,parseFloat(res[3])*2.55,parseFloat(res[4]));if(res=/#([a-fA-F0-9]{2})([a-fA-F0-9]{2})([a-fA-F0-9]{2})/.exec(str))return m(parseInt(res[1],16),parseInt(res[2],16),parseInt(res[3],16));if(res=/#([a-fA-F0-9])([a-fA-F0-9])([a-fA-F0-9])/.exec(str))return m(parseInt(res[1]+res[1],16),parseInt(res[2]+res[2],16),parseInt(res[3]+res[3],16));var name=$.trim(str).toLowerCase();if(name=="transparent")return m(255,255,255,0);else{res=lookupColors[name]||[0,0,0];return m(res[0],res[1],res[2])}};var lookupColors={aqua:[0,255,255],azure:[240,255,255],beige:[245,245,220],black:[0,0,0],blue:[0,0,255],brown:[165,42,42],cyan:[0,255,255],darkblue:[0,0,139],darkcyan:[0,139,139],darkgrey:[169,169,169],darkgreen:[0,100,0],darkkhaki:[189,183,107],darkmagenta:[139,0,139],darkolivegreen:[85,107,47],darkorange:[255,140,0],darkorchid:[153,50,204],darkred:[139,0,0],darksalmon:[233,150,122],darkviolet:[148,0,211],fuchsia:[255,0,255],gold:[255,215,0],green:[0,128,0],indigo:[75,0,130],khaki:[240,230,140],lightblue:[173,216,230],lightcyan:[224,255,255],lightgreen:[144,238,144],lightgrey:[211,211,211],lightpink:[255,182,193],lightyellow:[255,255,224],lime:[0,255,0],magenta:[255,0,255],maroon:[128,0,0],navy:[0,0,128],olive:[128,128,0],orange:[255,165,0],pink:[255,192,203],purple:[128,0,128],violet:[128,0,128],red:[255,0,0],silver:[192,192,192],white:[255,255,255],yellow:[255,255,0]}})(jQuery);
+
+// the actual Flot code
+(function($) {
+
+ // Cache the prototype hasOwnProperty for faster access
+
+ var hasOwnProperty = Object.prototype.hasOwnProperty;
+
+ // A shim to provide 'detach' to jQuery versions prior to 1.4. Using a DOM
+ // operation produces the same effect as detach, i.e. removing the element
+ // without touching its jQuery data.
+
+ // Do not merge this into Flot 0.9, since it requires jQuery 1.4.4+.
+
+ if (!$.fn.detach) {
+ $.fn.detach = function() {
+ return this.each(function() {
+ if (this.parentNode) {
+ this.parentNode.removeChild( this );
+ }
+ });
+ };
+ }
+
+ ///////////////////////////////////////////////////////////////////////////
+ // The Canvas object is a wrapper around an HTML5 tag.
+ //
+ // @constructor
+ // @param {string} cls List of classes to apply to the canvas.
+ // @param {element} container Element onto which to append the canvas.
+ //
+ // Requiring a container is a little iffy, but unfortunately canvas
+ // operations don't work unless the canvas is attached to the DOM.
+
+ function Canvas(cls, container) {
+
+ var element = container.children("." + cls)[0];
+
+ if (element == null) {
+
+ element = document.createElement("canvas");
+ element.className = cls;
+
+ $(element).css({ direction: "ltr", position: "absolute", left: 0, top: 0 })
+ .appendTo(container);
+
+ // If HTML5 Canvas isn't available, fall back to [Ex|Flash]canvas
+
+ if (!element.getContext) {
+ if (window.G_vmlCanvasManager) {
+ element = window.G_vmlCanvasManager.initElement(element);
+ } else {
+ throw new Error("Canvas is not available. If you're using IE with a fall-back such as Excanvas, then there's either a mistake in your conditional include, or the page has no DOCTYPE and is rendering in Quirks Mode.");
+ }
+ }
+ }
+
+ this.element = element;
+
+ var context = this.context = element.getContext("2d");
+
+ // Determine the screen's ratio of physical to device-independent
+ // pixels. This is the ratio between the canvas width that the browser
+ // advertises and the number of pixels actually present in that space.
+
+ // The iPhone 4, for example, has a device-independent width of 320px,
+ // but its screen is actually 640px wide. It therefore has a pixel
+ // ratio of 2, while most normal devices have a ratio of 1.
+
+ var devicePixelRatio = window.devicePixelRatio || 1,
+ backingStoreRatio =
+ context.webkitBackingStorePixelRatio ||
+ context.mozBackingStorePixelRatio ||
+ context.msBackingStorePixelRatio ||
+ context.oBackingStorePixelRatio ||
+ context.backingStorePixelRatio || 1;
+
+ this.pixelRatio = devicePixelRatio / backingStoreRatio;
+
+ // Size the canvas to match the internal dimensions of its container
+
+ this.resize(container.width(), container.height());
+
+ // Collection of HTML div layers for text overlaid onto the canvas
+
+ this.textContainer = null;
+ this.text = {};
+
+ // Cache of text fragments and metrics, so we can avoid expensively
+ // re-calculating them when the plot is re-rendered in a loop.
+
+ this._textCache = {};
+ }
+
+ // Resizes the canvas to the given dimensions.
+ //
+ // @param {number} width New width of the canvas, in pixels.
+ // @param {number} width New height of the canvas, in pixels.
+
+ Canvas.prototype.resize = function(width, height) {
+
+ if (width <= 0 || height <= 0) {
+ throw new Error("Invalid dimensions for plot, width = " + width + ", height = " + height);
+ }
+
+ var element = this.element,
+ context = this.context,
+ pixelRatio = this.pixelRatio;
+
+ // Resize the canvas, increasing its density based on the display's
+ // pixel ratio; basically giving it more pixels without increasing the
+ // size of its element, to take advantage of the fact that retina
+ // displays have that many more pixels in the same advertised space.
+
+ // Resizing should reset the state (excanvas seems to be buggy though)
+
+ if (this.width != width) {
+ element.width = width * pixelRatio;
+ element.style.width = width + "px";
+ this.width = width;
+ }
+
+ if (this.height != height) {
+ element.height = height * pixelRatio;
+ element.style.height = height + "px";
+ this.height = height;
+ }
+
+ // Save the context, so we can reset in case we get replotted. The
+ // restore ensure that we're really back at the initial state, and
+ // should be safe even if we haven't saved the initial state yet.
+
+ context.restore();
+ context.save();
+
+ // Scale the coordinate space to match the display density; so even though we
+ // may have twice as many pixels, we still want lines and other drawing to
+ // appear at the same size; the extra pixels will just make them crisper.
+
+ context.scale(pixelRatio, pixelRatio);
+ };
+
+ // Clears the entire canvas area, not including any overlaid HTML text
+
+ Canvas.prototype.clear = function() {
+ this.context.clearRect(0, 0, this.width, this.height);
+ };
+
+ // Finishes rendering the canvas, including managing the text overlay.
+
+ Canvas.prototype.render = function() {
+
+ var cache = this._textCache;
+
+ // For each text layer, add elements marked as active that haven't
+ // already been rendered, and remove those that are no longer active.
+
+ for (var layerKey in cache) {
+ if (hasOwnProperty.call(cache, layerKey)) {
+
+ var layer = this.getTextLayer(layerKey),
+ layerCache = cache[layerKey];
+
+ layer.hide();
+
+ for (var styleKey in layerCache) {
+ if (hasOwnProperty.call(layerCache, styleKey)) {
+ var styleCache = layerCache[styleKey];
+ for (var key in styleCache) {
+ if (hasOwnProperty.call(styleCache, key)) {
+
+ var positions = styleCache[key].positions;
+
+ for (var i = 0, position; position = positions[i]; i++) {
+ if (position.active) {
+ if (!position.rendered) {
+ layer.append(position.element);
+ position.rendered = true;
+ }
+ } else {
+ positions.splice(i--, 1);
+ if (position.rendered) {
+ position.element.detach();
+ }
+ }
+ }
+
+ if (positions.length == 0) {
+ delete styleCache[key];
+ }
+ }
+ }
+ }
+ }
+
+ layer.show();
+ }
+ }
+ };
+
+ // Creates (if necessary) and returns the text overlay container.
+ //
+ // @param {string} classes String of space-separated CSS classes used to
+ // uniquely identify the text layer.
+ // @return {object} The jQuery-wrapped text-layer div.
+
+ Canvas.prototype.getTextLayer = function(classes) {
+
+ var layer = this.text[classes];
+
+ // Create the text layer if it doesn't exist
+
+ if (layer == null) {
+
+ // Create the text layer container, if it doesn't exist
+
+ if (this.textContainer == null) {
+ this.textContainer = $("
")
+ .css({
+ position: "absolute",
+ top: 0,
+ left: 0,
+ bottom: 0,
+ right: 0,
+ 'font-size': "smaller",
+ color: "#545454"
+ })
+ .insertAfter(this.element);
+ }
+
+ layer = this.text[classes] = $("
")
+ .addClass(classes)
+ .css({
+ position: "absolute",
+ top: 0,
+ left: 0,
+ bottom: 0,
+ right: 0
+ })
+ .appendTo(this.textContainer);
+ }
+
+ return layer;
+ };
+
+ // Creates (if necessary) and returns a text info object.
+ //
+ // The object looks like this:
+ //
+ // {
+ // width: Width of the text's wrapper div.
+ // height: Height of the text's wrapper div.
+ // element: The jQuery-wrapped HTML div containing the text.
+ // positions: Array of positions at which this text is drawn.
+ // }
+ //
+ // The positions array contains objects that look like this:
+ //
+ // {
+ // active: Flag indicating whether the text should be visible.
+ // rendered: Flag indicating whether the text is currently visible.
+ // element: The jQuery-wrapped HTML div containing the text.
+ // x: X coordinate at which to draw the text.
+ // y: Y coordinate at which to draw the text.
+ // }
+ //
+ // Each position after the first receives a clone of the original element.
+ //
+ // The idea is that that the width, height, and general 'identity' of the
+ // text is constant no matter where it is placed; the placements are a
+ // secondary property.
+ //
+ // Canvas maintains a cache of recently-used text info objects; getTextInfo
+ // either returns the cached element or creates a new entry.
+ //
+ // @param {string} layer A string of space-separated CSS classes uniquely
+ // identifying the layer containing this text.
+ // @param {string} text Text string to retrieve info for.
+ // @param {(string|object)=} font Either a string of space-separated CSS
+ // classes or a font-spec object, defining the text's font and style.
+ // @param {number=} angle Angle at which to rotate the text, in degrees.
+ // Angle is currently unused, it will be implemented in the future.
+ // @param {number=} width Maximum width of the text before it wraps.
+ // @return {object} a text info object.
+
+ Canvas.prototype.getTextInfo = function(layer, text, font, angle, width) {
+
+ var textStyle, layerCache, styleCache, info;
+
+ // Cast the value to a string, in case we were given a number or such
+
+ text = "" + text;
+
+ // If the font is a font-spec object, generate a CSS font definition
+
+ if (typeof font === "object") {
+ textStyle = font.style + " " + font.variant + " " + font.weight + " " + font.size + "px/" + font.lineHeight + "px " + font.family;
+ } else {
+ textStyle = font;
+ }
+
+ // Retrieve (or create) the cache for the text's layer and styles
+
+ layerCache = this._textCache[layer];
+
+ if (layerCache == null) {
+ layerCache = this._textCache[layer] = {};
+ }
+
+ styleCache = layerCache[textStyle];
+
+ if (styleCache == null) {
+ styleCache = layerCache[textStyle] = {};
+ }
+
+ info = styleCache[text];
+
+ // If we can't find a matching element in our cache, create a new one
+
+ if (info == null) {
+
+ var element = $("
").html(text)
+ .css({
+ position: "absolute",
+ 'max-width': width,
+ top: -9999
+ })
+ .appendTo(this.getTextLayer(layer));
+
+ if (typeof font === "object") {
+ element.css({
+ font: textStyle,
+ color: font.color
+ });
+ } else if (typeof font === "string") {
+ element.addClass(font);
+ }
+
+ info = styleCache[text] = {
+ width: element.outerWidth(true),
+ height: element.outerHeight(true),
+ element: element,
+ positions: []
+ };
+
+ element.detach();
+ }
+
+ return info;
+ };
+
+ // Adds a text string to the canvas text overlay.
+ //
+ // The text isn't drawn immediately; it is marked as rendering, which will
+ // result in its addition to the canvas on the next render pass.
+ //
+ // @param {string} layer A string of space-separated CSS classes uniquely
+ // identifying the layer containing this text.
+ // @param {number} x X coordinate at which to draw the text.
+ // @param {number} y Y coordinate at which to draw the text.
+ // @param {string} text Text string to draw.
+ // @param {(string|object)=} font Either a string of space-separated CSS
+ // classes or a font-spec object, defining the text's font and style.
+ // @param {number=} angle Angle at which to rotate the text, in degrees.
+ // Angle is currently unused, it will be implemented in the future.
+ // @param {number=} width Maximum width of the text before it wraps.
+ // @param {string=} halign Horizontal alignment of the text; either "left",
+ // "center" or "right".
+ // @param {string=} valign Vertical alignment of the text; either "top",
+ // "middle" or "bottom".
+
+ Canvas.prototype.addText = function(layer, x, y, text, font, angle, width, halign, valign) {
+
+ var info = this.getTextInfo(layer, text, font, angle, width),
+ positions = info.positions;
+
+ // Tweak the div's position to match the text's alignment
+
+ if (halign == "center") {
+ x -= info.width / 2;
+ } else if (halign == "right") {
+ x -= info.width;
+ }
+
+ if (valign == "middle") {
+ y -= info.height / 2;
+ } else if (valign == "bottom") {
+ y -= info.height;
+ }
+
+ // Determine whether this text already exists at this position.
+ // If so, mark it for inclusion in the next render pass.
+
+ for (var i = 0, position; position = positions[i]; i++) {
+ if (position.x == x && position.y == y) {
+ position.active = true;
+ return;
+ }
+ }
+
+ // If the text doesn't exist at this position, create a new entry
+
+ // For the very first position we'll re-use the original element,
+ // while for subsequent ones we'll clone it.
+
+ position = {
+ active: true,
+ rendered: false,
+ element: positions.length ? info.element.clone() : info.element,
+ x: x,
+ y: y
+ };
+
+ positions.push(position);
+
+ // Move the element to its final position within the container
+
+ position.element.css({
+ top: Math.round(y),
+ left: Math.round(x),
+ 'text-align': halign // In case the text wraps
+ });
+ };
+
+ // Removes one or more text strings from the canvas text overlay.
+ //
+ // If no parameters are given, all text within the layer is removed.
+ //
+ // Note that the text is not immediately removed; it is simply marked as
+ // inactive, which will result in its removal on the next render pass.
+ // This avoids the performance penalty for 'clear and redraw' behavior,
+ // where we potentially get rid of all text on a layer, but will likely
+ // add back most or all of it later, as when redrawing axes, for example.
+ //
+ // @param {string} layer A string of space-separated CSS classes uniquely
+ // identifying the layer containing this text.
+ // @param {number=} x X coordinate of the text.
+ // @param {number=} y Y coordinate of the text.
+ // @param {string=} text Text string to remove.
+ // @param {(string|object)=} font Either a string of space-separated CSS
+ // classes or a font-spec object, defining the text's font and style.
+ // @param {number=} angle Angle at which the text is rotated, in degrees.
+ // Angle is currently unused, it will be implemented in the future.
+
+ Canvas.prototype.removeText = function(layer, x, y, text, font, angle) {
+ if (text == null) {
+ var layerCache = this._textCache[layer];
+ if (layerCache != null) {
+ for (var styleKey in layerCache) {
+ if (hasOwnProperty.call(layerCache, styleKey)) {
+ var styleCache = layerCache[styleKey];
+ for (var key in styleCache) {
+ if (hasOwnProperty.call(styleCache, key)) {
+ var positions = styleCache[key].positions;
+ for (var i = 0, position; position = positions[i]; i++) {
+ position.active = false;
+ }
+ }
+ }
+ }
+ }
+ }
+ } else {
+ var positions = this.getTextInfo(layer, text, font, angle).positions;
+ for (var i = 0, position; position = positions[i]; i++) {
+ if (position.x == x && position.y == y) {
+ position.active = false;
+ }
+ }
+ }
+ };
+
+ ///////////////////////////////////////////////////////////////////////////
+ // The top-level container for the entire plot.
+
+ function Plot(placeholder, data_, options_, plugins) {
+ // data is on the form:
+ // [ series1, series2 ... ]
+ // where series is either just the data as [ [x1, y1], [x2, y2], ... ]
+ // or { data: [ [x1, y1], [x2, y2], ... ], label: "some label", ... }
+
+ var series = [],
+ options = {
+ // the color theme used for graphs
+ colors: ["#edc240", "#afd8f8", "#cb4b4b", "#4da74d", "#9440ed"],
+ legend: {
+ show: true,
+ noColumns: 1, // number of colums in legend table
+ labelFormatter: null, // fn: string -> string
+ labelBoxBorderColor: "#ccc", // border color for the little label boxes
+ container: null, // container (as jQuery object) to put legend in, null means default on top of graph
+ position: "ne", // position of default legend container within plot
+ margin: 5, // distance from grid edge to default legend container within plot
+ backgroundColor: null, // null means auto-detect
+ backgroundOpacity: 0.85, // set to 0 to avoid background
+ sorted: null // default to no legend sorting
+ },
+ xaxis: {
+ show: null, // null = auto-detect, true = always, false = never
+ position: "bottom", // or "top"
+ mode: null, // null or "time"
+ font: null, // null (derived from CSS in placeholder) or object like { size: 11, lineHeight: 13, style: "italic", weight: "bold", family: "sans-serif", variant: "small-caps" }
+ color: null, // base color, labels, ticks
+ tickColor: null, // possibly different color of ticks, e.g. "rgba(0,0,0,0.15)"
+ transform: null, // null or f: number -> number to transform axis
+ inverseTransform: null, // if transform is set, this should be the inverse function
+ min: null, // min. value to show, null means set automatically
+ max: null, // max. value to show, null means set automatically
+ autoscaleMargin: null, // margin in % to add if auto-setting min/max
+ ticks: null, // either [1, 3] or [[1, "a"], 3] or (fn: axis info -> ticks) or app. number of ticks for auto-ticks
+ tickFormatter: null, // fn: number -> string
+ labelWidth: null, // size of tick labels in pixels
+ labelHeight: null,
+ reserveSpace: null, // whether to reserve space even if axis isn't shown
+ tickLength: null, // size in pixels of ticks, or "full" for whole line
+ alignTicksWithAxis: null, // axis number or null for no sync
+ tickDecimals: null, // no. of decimals, null means auto
+ tickSize: null, // number or [number, "unit"]
+ minTickSize: null // number or [number, "unit"]
+ },
+ yaxis: {
+ autoscaleMargin: 0.02,
+ position: "left" // or "right"
+ },
+ xaxes: [],
+ yaxes: [],
+ series: {
+ points: {
+ show: false,
+ radius: 3,
+ lineWidth: 2, // in pixels
+ fill: true,
+ fillColor: "#ffffff",
+ symbol: "circle" // or callback
+ },
+ lines: {
+ // we don't put in show: false so we can see
+ // whether lines were actively disabled
+ lineWidth: 2, // in pixels
+ fill: false,
+ fillColor: null,
+ steps: false
+ // Omit 'zero', so we can later default its value to
+ // match that of the 'fill' option.
+ },
+ bars: {
+ show: false,
+ lineWidth: 2, // in pixels
+ barWidth: 1, // in units of the x axis
+ fill: true,
+ fillColor: null,
+ align: "left", // "left", "right", or "center"
+ horizontal: false,
+ zero: true
+ },
+ shadowSize: 3,
+ highlightColor: null
+ },
+ grid: {
+ show: true,
+ aboveData: false,
+ color: "#545454", // primary color used for outline and labels
+ backgroundColor: null, // null for transparent, else color
+ borderColor: null, // set if different from the grid color
+ tickColor: null, // color for the ticks, e.g. "rgba(0,0,0,0.15)"
+ margin: 0, // distance from the canvas edge to the grid
+ labelMargin: 5, // in pixels
+ axisMargin: 8, // in pixels
+ borderWidth: 2, // in pixels
+ minBorderMargin: null, // in pixels, null means taken from points radius
+ markings: null, // array of ranges or fn: axes -> array of ranges
+ markingsColor: "#f4f4f4",
+ markingsLineWidth: 2,
+ // interactive stuff
+ clickable: false,
+ hoverable: false,
+ autoHighlight: true, // highlight in case mouse is near
+ mouseActiveRadius: 10 // how far the mouse can be away to activate an item
+ },
+ interaction: {
+ redrawOverlayInterval: 1000/60 // time between updates, -1 means in same flow
+ },
+ hooks: {}
+ },
+ surface = null, // the canvas for the plot itself
+ overlay = null, // canvas for interactive stuff on top of plot
+ eventHolder = null, // jQuery object that events should be bound to
+ ctx = null, octx = null,
+ xaxes = [], yaxes = [],
+ plotOffset = { left: 0, right: 0, top: 0, bottom: 0},
+ plotWidth = 0, plotHeight = 0,
+ hooks = {
+ processOptions: [],
+ processRawData: [],
+ processDatapoints: [],
+ processOffset: [],
+ drawBackground: [],
+ drawSeries: [],
+ draw: [],
+ bindEvents: [],
+ drawOverlay: [],
+ shutdown: []
+ },
+ plot = this;
+
+ // public functions
+ plot.setData = setData;
+ plot.setupGrid = setupGrid;
+ plot.draw = draw;
+ plot.getPlaceholder = function() { return placeholder; };
+ plot.getCanvas = function() { return surface.element; };
+ plot.getPlotOffset = function() { return plotOffset; };
+ plot.width = function () { return plotWidth; };
+ plot.height = function () { return plotHeight; };
+ plot.offset = function () {
+ var o = eventHolder.offset();
+ o.left += plotOffset.left;
+ o.top += plotOffset.top;
+ return o;
+ };
+ plot.getData = function () { return series; };
+ plot.getAxes = function () {
+ var res = {}, i;
+ $.each(xaxes.concat(yaxes), function (_, axis) {
+ if (axis)
+ res[axis.direction + (axis.n != 1 ? axis.n : "") + "axis"] = axis;
+ });
+ return res;
+ };
+ plot.getXAxes = function () { return xaxes; };
+ plot.getYAxes = function () { return yaxes; };
+ plot.c2p = canvasToAxisCoords;
+ plot.p2c = axisToCanvasCoords;
+ plot.getOptions = function () { return options; };
+ plot.highlight = highlight;
+ plot.unhighlight = unhighlight;
+ plot.triggerRedrawOverlay = triggerRedrawOverlay;
+ plot.pointOffset = function(point) {
+ return {
+ left: parseInt(xaxes[axisNumber(point, "x") - 1].p2c(+point.x) + plotOffset.left, 10),
+ top: parseInt(yaxes[axisNumber(point, "y") - 1].p2c(+point.y) + plotOffset.top, 10)
+ };
+ };
+ plot.shutdown = shutdown;
+ plot.destroy = function () {
+ shutdown();
+ placeholder.removeData("plot").empty();
+
+ series = [];
+ options = null;
+ surface = null;
+ overlay = null;
+ eventHolder = null;
+ ctx = null;
+ octx = null;
+ xaxes = [];
+ yaxes = [];
+ hooks = null;
+ highlights = [];
+ plot = null;
+ };
+ plot.resize = function () {
+ var width = placeholder.width(),
+ height = placeholder.height();
+ surface.resize(width, height);
+ overlay.resize(width, height);
+ };
+
+ // public attributes
+ plot.hooks = hooks;
+
+ // initialize
+ initPlugins(plot);
+ parseOptions(options_);
+ setupCanvases();
+ setData(data_);
+ setupGrid();
+ draw();
+ bindEvents();
+
+
+ function executeHooks(hook, args) {
+ args = [plot].concat(args);
+ for (var i = 0; i < hook.length; ++i)
+ hook[i].apply(this, args);
+ }
+
+ function initPlugins() {
+
+ // References to key classes, allowing plugins to modify them
+
+ var classes = {
+ Canvas: Canvas
+ };
+
+ for (var i = 0; i < plugins.length; ++i) {
+ var p = plugins[i];
+ p.init(plot, classes);
+ if (p.options)
+ $.extend(true, options, p.options);
+ }
+ }
+
+ function parseOptions(opts) {
+
+ $.extend(true, options, opts);
+
+ // $.extend merges arrays, rather than replacing them. When less
+ // colors are provided than the size of the default palette, we
+ // end up with those colors plus the remaining defaults, which is
+ // not expected behavior; avoid it by replacing them here.
+
+ if (opts && opts.colors) {
+ options.colors = opts.colors;
+ }
+
+ if (options.xaxis.color == null)
+ options.xaxis.color = $.color.parse(options.grid.color).scale('a', 0.22).toString();
+ if (options.yaxis.color == null)
+ options.yaxis.color = $.color.parse(options.grid.color).scale('a', 0.22).toString();
+
+ if (options.xaxis.tickColor == null) // grid.tickColor for back-compatibility
+ options.xaxis.tickColor = options.grid.tickColor || options.xaxis.color;
+ if (options.yaxis.tickColor == null) // grid.tickColor for back-compatibility
+ options.yaxis.tickColor = options.grid.tickColor || options.yaxis.color;
+
+ if (options.grid.borderColor == null)
+ options.grid.borderColor = options.grid.color;
+ if (options.grid.tickColor == null)
+ options.grid.tickColor = $.color.parse(options.grid.color).scale('a', 0.22).toString();
+
+ // Fill in defaults for axis options, including any unspecified
+ // font-spec fields, if a font-spec was provided.
+
+ // If no x/y axis options were provided, create one of each anyway,
+ // since the rest of the code assumes that they exist.
+
+ var i, axisOptions, axisCount,
+ fontSize = placeholder.css("font-size"),
+ fontSizeDefault = fontSize ? +fontSize.replace("px", "") : 13,
+ fontDefaults = {
+ style: placeholder.css("font-style"),
+ size: Math.round(0.8 * fontSizeDefault),
+ variant: placeholder.css("font-variant"),
+ weight: placeholder.css("font-weight"),
+ family: placeholder.css("font-family")
+ };
+
+ axisCount = options.xaxes.length || 1;
+ for (i = 0; i < axisCount; ++i) {
+
+ axisOptions = options.xaxes[i];
+ if (axisOptions && !axisOptions.tickColor) {
+ axisOptions.tickColor = axisOptions.color;
+ }
+
+ axisOptions = $.extend(true, {}, options.xaxis, axisOptions);
+ options.xaxes[i] = axisOptions;
+
+ if (axisOptions.font) {
+ axisOptions.font = $.extend({}, fontDefaults, axisOptions.font);
+ if (!axisOptions.font.color) {
+ axisOptions.font.color = axisOptions.color;
+ }
+ if (!axisOptions.font.lineHeight) {
+ axisOptions.font.lineHeight = Math.round(axisOptions.font.size * 1.15);
+ }
+ }
+ }
+
+ axisCount = options.yaxes.length || 1;
+ for (i = 0; i < axisCount; ++i) {
+
+ axisOptions = options.yaxes[i];
+ if (axisOptions && !axisOptions.tickColor) {
+ axisOptions.tickColor = axisOptions.color;
+ }
+
+ axisOptions = $.extend(true, {}, options.yaxis, axisOptions);
+ options.yaxes[i] = axisOptions;
+
+ if (axisOptions.font) {
+ axisOptions.font = $.extend({}, fontDefaults, axisOptions.font);
+ if (!axisOptions.font.color) {
+ axisOptions.font.color = axisOptions.color;
+ }
+ if (!axisOptions.font.lineHeight) {
+ axisOptions.font.lineHeight = Math.round(axisOptions.font.size * 1.15);
+ }
+ }
+ }
+
+ // backwards compatibility, to be removed in future
+ if (options.xaxis.noTicks && options.xaxis.ticks == null)
+ options.xaxis.ticks = options.xaxis.noTicks;
+ if (options.yaxis.noTicks && options.yaxis.ticks == null)
+ options.yaxis.ticks = options.yaxis.noTicks;
+ if (options.x2axis) {
+ options.xaxes[1] = $.extend(true, {}, options.xaxis, options.x2axis);
+ options.xaxes[1].position = "top";
+ // Override the inherit to allow the axis to auto-scale
+ if (options.x2axis.min == null) {
+ options.xaxes[1].min = null;
+ }
+ if (options.x2axis.max == null) {
+ options.xaxes[1].max = null;
+ }
+ }
+ if (options.y2axis) {
+ options.yaxes[1] = $.extend(true, {}, options.yaxis, options.y2axis);
+ options.yaxes[1].position = "right";
+ // Override the inherit to allow the axis to auto-scale
+ if (options.y2axis.min == null) {
+ options.yaxes[1].min = null;
+ }
+ if (options.y2axis.max == null) {
+ options.yaxes[1].max = null;
+ }
+ }
+ if (options.grid.coloredAreas)
+ options.grid.markings = options.grid.coloredAreas;
+ if (options.grid.coloredAreasColor)
+ options.grid.markingsColor = options.grid.coloredAreasColor;
+ if (options.lines)
+ $.extend(true, options.series.lines, options.lines);
+ if (options.points)
+ $.extend(true, options.series.points, options.points);
+ if (options.bars)
+ $.extend(true, options.series.bars, options.bars);
+ if (options.shadowSize != null)
+ options.series.shadowSize = options.shadowSize;
+ if (options.highlightColor != null)
+ options.series.highlightColor = options.highlightColor;
+
+ // save options on axes for future reference
+ for (i = 0; i < options.xaxes.length; ++i)
+ getOrCreateAxis(xaxes, i + 1).options = options.xaxes[i];
+ for (i = 0; i < options.yaxes.length; ++i)
+ getOrCreateAxis(yaxes, i + 1).options = options.yaxes[i];
+
+ // add hooks from options
+ for (var n in hooks)
+ if (options.hooks[n] && options.hooks[n].length)
+ hooks[n] = hooks[n].concat(options.hooks[n]);
+
+ executeHooks(hooks.processOptions, [options]);
+ }
+
+ function setData(d) {
+ series = parseData(d);
+ fillInSeriesOptions();
+ processData();
+ }
+
+ function parseData(d) {
+ var res = [];
+ for (var i = 0; i < d.length; ++i) {
+ var s = $.extend(true, {}, options.series);
+
+ if (d[i].data != null) {
+ s.data = d[i].data; // move the data instead of deep-copy
+ delete d[i].data;
+
+ $.extend(true, s, d[i]);
+
+ d[i].data = s.data;
+ }
+ else
+ s.data = d[i];
+ res.push(s);
+ }
+
+ return res;
+ }
+
+ function axisNumber(obj, coord) {
+ var a = obj[coord + "axis"];
+ if (typeof a == "object") // if we got a real axis, extract number
+ a = a.n;
+ if (typeof a != "number")
+ a = 1; // default to first axis
+ return a;
+ }
+
+ function allAxes() {
+ // return flat array without annoying null entries
+ return $.grep(xaxes.concat(yaxes), function (a) { return a; });
+ }
+
+ function canvasToAxisCoords(pos) {
+ // return an object with x/y corresponding to all used axes
+ var res = {}, i, axis;
+ for (i = 0; i < xaxes.length; ++i) {
+ axis = xaxes[i];
+ if (axis && axis.used)
+ res["x" + axis.n] = axis.c2p(pos.left);
+ }
+
+ for (i = 0; i < yaxes.length; ++i) {
+ axis = yaxes[i];
+ if (axis && axis.used)
+ res["y" + axis.n] = axis.c2p(pos.top);
+ }
+
+ if (res.x1 !== undefined)
+ res.x = res.x1;
+ if (res.y1 !== undefined)
+ res.y = res.y1;
+
+ return res;
+ }
+
+ function axisToCanvasCoords(pos) {
+ // get canvas coords from the first pair of x/y found in pos
+ var res = {}, i, axis, key;
+
+ for (i = 0; i < xaxes.length; ++i) {
+ axis = xaxes[i];
+ if (axis && axis.used) {
+ key = "x" + axis.n;
+ if (pos[key] == null && axis.n == 1)
+ key = "x";
+
+ if (pos[key] != null) {
+ res.left = axis.p2c(pos[key]);
+ break;
+ }
+ }
+ }
+
+ for (i = 0; i < yaxes.length; ++i) {
+ axis = yaxes[i];
+ if (axis && axis.used) {
+ key = "y" + axis.n;
+ if (pos[key] == null && axis.n == 1)
+ key = "y";
+
+ if (pos[key] != null) {
+ res.top = axis.p2c(pos[key]);
+ break;
+ }
+ }
+ }
+
+ return res;
+ }
+
+ function getOrCreateAxis(axes, number) {
+ if (!axes[number - 1])
+ axes[number - 1] = {
+ n: number, // save the number for future reference
+ direction: axes == xaxes ? "x" : "y",
+ options: $.extend(true, {}, axes == xaxes ? options.xaxis : options.yaxis)
+ };
+
+ return axes[number - 1];
+ }
+
+ function fillInSeriesOptions() {
+
+ var neededColors = series.length, maxIndex = -1, i;
+
+ // Subtract the number of series that already have fixed colors or
+ // color indexes from the number that we still need to generate.
+
+ for (i = 0; i < series.length; ++i) {
+ var sc = series[i].color;
+ if (sc != null) {
+ neededColors--;
+ if (typeof sc == "number" && sc > maxIndex) {
+ maxIndex = sc;
+ }
+ }
+ }
+
+ // If any of the series have fixed color indexes, then we need to
+ // generate at least as many colors as the highest index.
+
+ if (neededColors <= maxIndex) {
+ neededColors = maxIndex + 1;
+ }
+
+ // Generate all the colors, using first the option colors and then
+ // variations on those colors once they're exhausted.
+
+ var c, colors = [], colorPool = options.colors,
+ colorPoolSize = colorPool.length, variation = 0;
+
+ for (i = 0; i < neededColors; i++) {
+
+ c = $.color.parse(colorPool[i % colorPoolSize] || "#666");
+
+ // Each time we exhaust the colors in the pool we adjust
+ // a scaling factor used to produce more variations on
+ // those colors. The factor alternates negative/positive
+ // to produce lighter/darker colors.
+
+ // Reset the variation after every few cycles, or else
+ // it will end up producing only white or black colors.
+
+ if (i % colorPoolSize == 0 && i) {
+ if (variation >= 0) {
+ if (variation < 0.5) {
+ variation = -variation - 0.2;
+ } else variation = 0;
+ } else variation = -variation;
+ }
+
+ colors[i] = c.scale('rgb', 1 + variation);
+ }
+
+ // Finalize the series options, filling in their colors
+
+ var colori = 0, s;
+ for (i = 0; i < series.length; ++i) {
+ s = series[i];
+
+ // assign colors
+ if (s.color == null) {
+ s.color = colors[colori].toString();
+ ++colori;
+ }
+ else if (typeof s.color == "number")
+ s.color = colors[s.color].toString();
+
+ // turn on lines automatically in case nothing is set
+ if (s.lines.show == null) {
+ var v, show = true;
+ for (v in s)
+ if (s[v] && s[v].show) {
+ show = false;
+ break;
+ }
+ if (show)
+ s.lines.show = true;
+ }
+
+ // If nothing was provided for lines.zero, default it to match
+ // lines.fill, since areas by default should extend to zero.
+
+ if (s.lines.zero == null) {
+ s.lines.zero = !!s.lines.fill;
+ }
+
+ // setup axes
+ s.xaxis = getOrCreateAxis(xaxes, axisNumber(s, "x"));
+ s.yaxis = getOrCreateAxis(yaxes, axisNumber(s, "y"));
+ }
+ }
+
+ function processData() {
+ var topSentry = Number.POSITIVE_INFINITY,
+ bottomSentry = Number.NEGATIVE_INFINITY,
+ fakeInfinity = Number.MAX_VALUE,
+ i, j, k, m, length,
+ s, points, ps, x, y, axis, val, f, p,
+ data, format;
+
+ function updateAxis(axis, min, max) {
+ if (min < axis.datamin && min != -fakeInfinity)
+ axis.datamin = min;
+ if (max > axis.datamax && max != fakeInfinity)
+ axis.datamax = max;
+ }
+
+ $.each(allAxes(), function (_, axis) {
+ // init axis
+ axis.datamin = topSentry;
+ axis.datamax = bottomSentry;
+ axis.used = false;
+ });
+
+ for (i = 0; i < series.length; ++i) {
+ s = series[i];
+ s.datapoints = { points: [] };
+
+ executeHooks(hooks.processRawData, [ s, s.data, s.datapoints ]);
+ }
+
+ // first pass: clean and copy data
+ for (i = 0; i < series.length; ++i) {
+ s = series[i];
+
+ data = s.data;
+ format = s.datapoints.format;
+
+ if (!format) {
+ format = [];
+ // find out how to copy
+ format.push({ x: true, number: true, required: true });
+ format.push({ y: true, number: true, required: true });
+
+ if (s.bars.show || (s.lines.show && s.lines.fill)) {
+ var autoscale = !!((s.bars.show && s.bars.zero) || (s.lines.show && s.lines.zero));
+ format.push({ y: true, number: true, required: false, defaultValue: 0, autoscale: autoscale });
+ if (s.bars.horizontal) {
+ delete format[format.length - 1].y;
+ format[format.length - 1].x = true;
+ }
+ }
+
+ s.datapoints.format = format;
+ }
+
+ if (s.datapoints.pointsize != null)
+ continue; // already filled in
+
+ s.datapoints.pointsize = format.length;
+
+ ps = s.datapoints.pointsize;
+ points = s.datapoints.points;
+
+ var insertSteps = s.lines.show && s.lines.steps;
+ s.xaxis.used = s.yaxis.used = true;
+
+ for (j = k = 0; j < data.length; ++j, k += ps) {
+ p = data[j];
+
+ var nullify = p == null;
+ if (!nullify) {
+ for (m = 0; m < ps; ++m) {
+ val = p[m];
+ f = format[m];
+
+ if (f) {
+ if (f.number && val != null) {
+ val = +val; // convert to number
+ if (isNaN(val))
+ val = null;
+ else if (val == Infinity)
+ val = fakeInfinity;
+ else if (val == -Infinity)
+ val = -fakeInfinity;
+ }
+
+ if (val == null) {
+ if (f.required)
+ nullify = true;
+
+ if (f.defaultValue != null)
+ val = f.defaultValue;
+ }
+ }
+
+ points[k + m] = val;
+ }
+ }
+
+ if (nullify) {
+ for (m = 0; m < ps; ++m) {
+ val = points[k + m];
+ if (val != null) {
+ f = format[m];
+ // extract min/max info
+ if (f.autoscale !== false) {
+ if (f.x) {
+ updateAxis(s.xaxis, val, val);
+ }
+ if (f.y) {
+ updateAxis(s.yaxis, val, val);
+ }
+ }
+ }
+ points[k + m] = null;
+ }
+ }
+ else {
+ // a little bit of line specific stuff that
+ // perhaps shouldn't be here, but lacking
+ // better means...
+ if (insertSteps && k > 0
+ && points[k - ps] != null
+ && points[k - ps] != points[k]
+ && points[k - ps + 1] != points[k + 1]) {
+ // copy the point to make room for a middle point
+ for (m = 0; m < ps; ++m)
+ points[k + ps + m] = points[k + m];
+
+ // middle point has same y
+ points[k + 1] = points[k - ps + 1];
+
+ // we've added a point, better reflect that
+ k += ps;
+ }
+ }
+ }
+ }
+
+ // give the hooks a chance to run
+ for (i = 0; i < series.length; ++i) {
+ s = series[i];
+
+ executeHooks(hooks.processDatapoints, [ s, s.datapoints]);
+ }
+
+ // second pass: find datamax/datamin for auto-scaling
+ for (i = 0; i < series.length; ++i) {
+ s = series[i];
+ points = s.datapoints.points;
+ ps = s.datapoints.pointsize;
+ format = s.datapoints.format;
+
+ var xmin = topSentry, ymin = topSentry,
+ xmax = bottomSentry, ymax = bottomSentry;
+
+ for (j = 0; j < points.length; j += ps) {
+ if (points[j] == null)
+ continue;
+
+ for (m = 0; m < ps; ++m) {
+ val = points[j + m];
+ f = format[m];
+ if (!f || f.autoscale === false || val == fakeInfinity || val == -fakeInfinity)
+ continue;
+
+ if (f.x) {
+ if (val < xmin)
+ xmin = val;
+ if (val > xmax)
+ xmax = val;
+ }
+ if (f.y) {
+ if (val < ymin)
+ ymin = val;
+ if (val > ymax)
+ ymax = val;
+ }
+ }
+ }
+
+ if (s.bars.show) {
+ // make sure we got room for the bar on the dancing floor
+ var delta;
+
+ switch (s.bars.align) {
+ case "left":
+ delta = 0;
+ break;
+ case "right":
+ delta = -s.bars.barWidth;
+ break;
+ default:
+ delta = -s.bars.barWidth / 2;
+ }
+
+ if (s.bars.horizontal) {
+ ymin += delta;
+ ymax += delta + s.bars.barWidth;
+ }
+ else {
+ xmin += delta;
+ xmax += delta + s.bars.barWidth;
+ }
+ }
+
+ updateAxis(s.xaxis, xmin, xmax);
+ updateAxis(s.yaxis, ymin, ymax);
+ }
+
+ $.each(allAxes(), function (_, axis) {
+ if (axis.datamin == topSentry)
+ axis.datamin = null;
+ if (axis.datamax == bottomSentry)
+ axis.datamax = null;
+ });
+ }
+
+ function setupCanvases() {
+
+ // Make sure the placeholder is clear of everything except canvases
+ // from a previous plot in this container that we'll try to re-use.
+
+ placeholder.css("padding", 0) // padding messes up the positioning
+ .children().filter(function(){
+ return !$(this).hasClass("flot-overlay") && !$(this).hasClass('flot-base');
+ }).remove();
+
+ if (placeholder.css("position") == 'static')
+ placeholder.css("position", "relative"); // for positioning labels and overlay
+
+ surface = new Canvas("flot-base", placeholder);
+ overlay = new Canvas("flot-overlay", placeholder); // overlay canvas for interactive features
+
+ ctx = surface.context;
+ octx = overlay.context;
+
+ // define which element we're listening for events on
+ eventHolder = $(overlay.element).unbind();
+
+ // If we're re-using a plot object, shut down the old one
+
+ var existing = placeholder.data("plot");
+
+ if (existing) {
+ existing.shutdown();
+ overlay.clear();
+ }
+
+ // save in case we get replotted
+ placeholder.data("plot", plot);
+ }
+
+ function bindEvents() {
+ // bind events
+ if (options.grid.hoverable) {
+ eventHolder.mousemove(onMouseMove);
+
+ // Use bind, rather than .mouseleave, because we officially
+ // still support jQuery 1.2.6, which doesn't define a shortcut
+ // for mouseenter or mouseleave. This was a bug/oversight that
+ // was fixed somewhere around 1.3.x. We can return to using
+ // .mouseleave when we drop support for 1.2.6.
+
+ eventHolder.bind("mouseleave", onMouseLeave);
+ }
+
+ if (options.grid.clickable)
+ eventHolder.click(onClick);
+
+ executeHooks(hooks.bindEvents, [eventHolder]);
+ }
+
+ function shutdown() {
+ if (redrawTimeout)
+ clearTimeout(redrawTimeout);
+
+ eventHolder.unbind("mousemove", onMouseMove);
+ eventHolder.unbind("mouseleave", onMouseLeave);
+ eventHolder.unbind("click", onClick);
+
+ executeHooks(hooks.shutdown, [eventHolder]);
+ }
+
+ function setTransformationHelpers(axis) {
+ // set helper functions on the axis, assumes plot area
+ // has been computed already
+
+ function identity(x) { return x; }
+
+ var s, m, t = axis.options.transform || identity,
+ it = axis.options.inverseTransform;
+
+ // precompute how much the axis is scaling a point
+ // in canvas space
+ if (axis.direction == "x") {
+ s = axis.scale = plotWidth / Math.abs(t(axis.max) - t(axis.min));
+ m = Math.min(t(axis.max), t(axis.min));
+ }
+ else {
+ s = axis.scale = plotHeight / Math.abs(t(axis.max) - t(axis.min));
+ s = -s;
+ m = Math.max(t(axis.max), t(axis.min));
+ }
+
+ // data point to canvas coordinate
+ if (t == identity) // slight optimization
+ axis.p2c = function (p) { return (p - m) * s; };
+ else
+ axis.p2c = function (p) { return (t(p) - m) * s; };
+ // canvas coordinate to data point
+ if (!it)
+ axis.c2p = function (c) { return m + c / s; };
+ else
+ axis.c2p = function (c) { return it(m + c / s); };
+ }
+
+ function measureTickLabels(axis) {
+
+ var opts = axis.options,
+ ticks = axis.ticks || [],
+ labelWidth = opts.labelWidth || 0,
+ labelHeight = opts.labelHeight || 0,
+ maxWidth = labelWidth || (axis.direction == "x" ? Math.floor(surface.width / (ticks.length || 1)) : null),
+ legacyStyles = axis.direction + "Axis " + axis.direction + axis.n + "Axis",
+ layer = "flot-" + axis.direction + "-axis flot-" + axis.direction + axis.n + "-axis " + legacyStyles,
+ font = opts.font || "flot-tick-label tickLabel";
+
+ for (var i = 0; i < ticks.length; ++i) {
+
+ var t = ticks[i];
+
+ if (!t.label)
+ continue;
+
+ var info = surface.getTextInfo(layer, t.label, font, null, maxWidth);
+
+ labelWidth = Math.max(labelWidth, info.width);
+ labelHeight = Math.max(labelHeight, info.height);
+ }
+
+ axis.labelWidth = opts.labelWidth || labelWidth;
+ axis.labelHeight = opts.labelHeight || labelHeight;
+ }
+
+ function allocateAxisBoxFirstPhase(axis) {
+ // find the bounding box of the axis by looking at label
+ // widths/heights and ticks, make room by diminishing the
+ // plotOffset; this first phase only looks at one
+ // dimension per axis, the other dimension depends on the
+ // other axes so will have to wait
+
+ var lw = axis.labelWidth,
+ lh = axis.labelHeight,
+ pos = axis.options.position,
+ isXAxis = axis.direction === "x",
+ tickLength = axis.options.tickLength,
+ axisMargin = options.grid.axisMargin,
+ padding = options.grid.labelMargin,
+ innermost = true,
+ outermost = true,
+ first = true,
+ found = false;
+
+ // Determine the axis's position in its direction and on its side
+
+ $.each(isXAxis ? xaxes : yaxes, function(i, a) {
+ if (a && (a.show || a.reserveSpace)) {
+ if (a === axis) {
+ found = true;
+ } else if (a.options.position === pos) {
+ if (found) {
+ outermost = false;
+ } else {
+ innermost = false;
+ }
+ }
+ if (!found) {
+ first = false;
+ }
+ }
+ });
+
+ // The outermost axis on each side has no margin
+
+ if (outermost) {
+ axisMargin = 0;
+ }
+
+ // The ticks for the first axis in each direction stretch across
+
+ if (tickLength == null) {
+ tickLength = first ? "full" : 5;
+ }
+
+ if (!isNaN(+tickLength))
+ padding += +tickLength;
+
+ if (isXAxis) {
+ lh += padding;
+
+ if (pos == "bottom") {
+ plotOffset.bottom += lh + axisMargin;
+ axis.box = { top: surface.height - plotOffset.bottom, height: lh };
+ }
+ else {
+ axis.box = { top: plotOffset.top + axisMargin, height: lh };
+ plotOffset.top += lh + axisMargin;
+ }
+ }
+ else {
+ lw += padding;
+
+ if (pos == "left") {
+ axis.box = { left: plotOffset.left + axisMargin, width: lw };
+ plotOffset.left += lw + axisMargin;
+ }
+ else {
+ plotOffset.right += lw + axisMargin;
+ axis.box = { left: surface.width - plotOffset.right, width: lw };
+ }
+ }
+
+ // save for future reference
+ axis.position = pos;
+ axis.tickLength = tickLength;
+ axis.box.padding = padding;
+ axis.innermost = innermost;
+ }
+
+ function allocateAxisBoxSecondPhase(axis) {
+ // now that all axis boxes have been placed in one
+ // dimension, we can set the remaining dimension coordinates
+ if (axis.direction == "x") {
+ axis.box.left = plotOffset.left - axis.labelWidth / 2;
+ axis.box.width = surface.width - plotOffset.left - plotOffset.right + axis.labelWidth;
+ }
+ else {
+ axis.box.top = plotOffset.top - axis.labelHeight / 2;
+ axis.box.height = surface.height - plotOffset.bottom - plotOffset.top + axis.labelHeight;
+ }
+ }
+
+ function adjustLayoutForThingsStickingOut() {
+ // possibly adjust plot offset to ensure everything stays
+ // inside the canvas and isn't clipped off
+
+ var minMargin = options.grid.minBorderMargin,
+ axis, i;
+
+ // check stuff from the plot (FIXME: this should just read
+ // a value from the series, otherwise it's impossible to
+ // customize)
+ if (minMargin == null) {
+ minMargin = 0;
+ for (i = 0; i < series.length; ++i)
+ minMargin = Math.max(minMargin, 2 * (series[i].points.radius + series[i].points.lineWidth/2));
+ }
+
+ var margins = {
+ left: minMargin,
+ right: minMargin,
+ top: minMargin,
+ bottom: minMargin
+ };
+
+ // check axis labels, note we don't check the actual
+ // labels but instead use the overall width/height to not
+ // jump as much around with replots
+ $.each(allAxes(), function (_, axis) {
+ if (axis.reserveSpace && axis.ticks && axis.ticks.length) {
+ if (axis.direction === "x") {
+ margins.left = Math.max(margins.left, axis.labelWidth / 2);
+ margins.right = Math.max(margins.right, axis.labelWidth / 2);
+ } else {
+ margins.bottom = Math.max(margins.bottom, axis.labelHeight / 2);
+ margins.top = Math.max(margins.top, axis.labelHeight / 2);
+ }
+ }
+ });
+
+ plotOffset.left = Math.ceil(Math.max(margins.left, plotOffset.left));
+ plotOffset.right = Math.ceil(Math.max(margins.right, plotOffset.right));
+ plotOffset.top = Math.ceil(Math.max(margins.top, plotOffset.top));
+ plotOffset.bottom = Math.ceil(Math.max(margins.bottom, plotOffset.bottom));
+ }
+
+ function setupGrid() {
+ var i, axes = allAxes(), showGrid = options.grid.show;
+
+ // Initialize the plot's offset from the edge of the canvas
+
+ for (var a in plotOffset) {
+ var margin = options.grid.margin || 0;
+ plotOffset[a] = typeof margin == "number" ? margin : margin[a] || 0;
+ }
+
+ executeHooks(hooks.processOffset, [plotOffset]);
+
+ // If the grid is visible, add its border width to the offset
+
+ for (var a in plotOffset) {
+ if(typeof(options.grid.borderWidth) == "object") {
+ plotOffset[a] += showGrid ? options.grid.borderWidth[a] : 0;
+ }
+ else {
+ plotOffset[a] += showGrid ? options.grid.borderWidth : 0;
+ }
+ }
+
+ $.each(axes, function (_, axis) {
+ var axisOpts = axis.options;
+ axis.show = axisOpts.show == null ? axis.used : axisOpts.show;
+ axis.reserveSpace = axisOpts.reserveSpace == null ? axis.show : axisOpts.reserveSpace;
+ setRange(axis);
+ });
+
+ if (showGrid) {
+
+ var allocatedAxes = $.grep(axes, function (axis) {
+ return axis.show || axis.reserveSpace;
+ });
+
+ $.each(allocatedAxes, function (_, axis) {
+ // make the ticks
+ setupTickGeneration(axis);
+ setTicks(axis);
+ snapRangeToTicks(axis, axis.ticks);
+ // find labelWidth/Height for axis
+ measureTickLabels(axis);
+ });
+
+ // with all dimensions calculated, we can compute the
+ // axis bounding boxes, start from the outside
+ // (reverse order)
+ for (i = allocatedAxes.length - 1; i >= 0; --i)
+ allocateAxisBoxFirstPhase(allocatedAxes[i]);
+
+ // make sure we've got enough space for things that
+ // might stick out
+ adjustLayoutForThingsStickingOut();
+
+ $.each(allocatedAxes, function (_, axis) {
+ allocateAxisBoxSecondPhase(axis);
+ });
+ }
+
+ plotWidth = surface.width - plotOffset.left - plotOffset.right;
+ plotHeight = surface.height - plotOffset.bottom - plotOffset.top;
+
+ // now we got the proper plot dimensions, we can compute the scaling
+ $.each(axes, function (_, axis) {
+ setTransformationHelpers(axis);
+ });
+
+ if (showGrid) {
+ drawAxisLabels();
+ }
+
+ insertLegend();
+ }
+
+ function setRange(axis) {
+ var opts = axis.options,
+ min = +(opts.min != null ? opts.min : axis.datamin),
+ max = +(opts.max != null ? opts.max : axis.datamax),
+ delta = max - min;
+
+ if (delta == 0.0) {
+ // degenerate case
+ var widen = max == 0 ? 1 : 0.01;
+
+ if (opts.min == null)
+ min -= widen;
+ // always widen max if we couldn't widen min to ensure we
+ // don't fall into min == max which doesn't work
+ if (opts.max == null || opts.min != null)
+ max += widen;
+ }
+ else {
+ // consider autoscaling
+ var margin = opts.autoscaleMargin;
+ if (margin != null) {
+ if (opts.min == null) {
+ min -= delta * margin;
+ // make sure we don't go below zero if all values
+ // are positive
+ if (min < 0 && axis.datamin != null && axis.datamin >= 0)
+ min = 0;
+ }
+ if (opts.max == null) {
+ max += delta * margin;
+ if (max > 0 && axis.datamax != null && axis.datamax <= 0)
+ max = 0;
+ }
+ }
+ }
+ axis.min = min;
+ axis.max = max;
+ }
+
+ function setupTickGeneration(axis) {
+ var opts = axis.options;
+
+ // estimate number of ticks
+ var noTicks;
+ if (typeof opts.ticks == "number" && opts.ticks > 0)
+ noTicks = opts.ticks;
+ else
+ // heuristic based on the model a*sqrt(x) fitted to
+ // some data points that seemed reasonable
+ noTicks = 0.3 * Math.sqrt(axis.direction == "x" ? surface.width : surface.height);
+
+ var delta = (axis.max - axis.min) / noTicks,
+ dec = -Math.floor(Math.log(delta) / Math.LN10),
+ maxDec = opts.tickDecimals;
+
+ if (maxDec != null && dec > maxDec) {
+ dec = maxDec;
+ }
+
+ var magn = Math.pow(10, -dec),
+ norm = delta / magn, // norm is between 1.0 and 10.0
+ size;
+
+ if (norm < 1.5) {
+ size = 1;
+ } else if (norm < 3) {
+ size = 2;
+ // special case for 2.5, requires an extra decimal
+ if (norm > 2.25 && (maxDec == null || dec + 1 <= maxDec)) {
+ size = 2.5;
+ ++dec;
+ }
+ } else if (norm < 7.5) {
+ size = 5;
+ } else {
+ size = 10;
+ }
+
+ size *= magn;
+
+ if (opts.minTickSize != null && size < opts.minTickSize) {
+ size = opts.minTickSize;
+ }
+
+ axis.delta = delta;
+ axis.tickDecimals = Math.max(0, maxDec != null ? maxDec : dec);
+ axis.tickSize = opts.tickSize || size;
+
+ // Time mode was moved to a plug-in in 0.8, and since so many people use it
+ // we'll add an especially friendly reminder to make sure they included it.
+
+ if (opts.mode == "time" && !axis.tickGenerator) {
+ throw new Error("Time mode requires the flot.time plugin.");
+ }
+
+ // Flot supports base-10 axes; any other mode else is handled by a plug-in,
+ // like flot.time.js.
+
+ if (!axis.tickGenerator) {
+
+ axis.tickGenerator = function (axis) {
+
+ var ticks = [],
+ start = floorInBase(axis.min, axis.tickSize),
+ i = 0,
+ v = Number.NaN,
+ prev;
+
+ do {
+ prev = v;
+ v = start + i * axis.tickSize;
+ ticks.push(v);
+ ++i;
+ } while (v < axis.max && v != prev);
+ return ticks;
+ };
+
+ axis.tickFormatter = function (value, axis) {
+
+ var factor = axis.tickDecimals ? Math.pow(10, axis.tickDecimals) : 1;
+ var formatted = "" + Math.round(value * factor) / factor;
+
+ // If tickDecimals was specified, ensure that we have exactly that
+ // much precision; otherwise default to the value's own precision.
+
+ if (axis.tickDecimals != null) {
+ var decimal = formatted.indexOf(".");
+ var precision = decimal == -1 ? 0 : formatted.length - decimal - 1;
+ if (precision < axis.tickDecimals) {
+ return (precision ? formatted : formatted + ".") + ("" + factor).substr(1, axis.tickDecimals - precision);
+ }
+ }
+
+ return formatted;
+ };
+ }
+
+ if ($.isFunction(opts.tickFormatter))
+ axis.tickFormatter = function (v, axis) { return "" + opts.tickFormatter(v, axis); };
+
+ if (opts.alignTicksWithAxis != null) {
+ var otherAxis = (axis.direction == "x" ? xaxes : yaxes)[opts.alignTicksWithAxis - 1];
+ if (otherAxis && otherAxis.used && otherAxis != axis) {
+ // consider snapping min/max to outermost nice ticks
+ var niceTicks = axis.tickGenerator(axis);
+ if (niceTicks.length > 0) {
+ if (opts.min == null)
+ axis.min = Math.min(axis.min, niceTicks[0]);
+ if (opts.max == null && niceTicks.length > 1)
+ axis.max = Math.max(axis.max, niceTicks[niceTicks.length - 1]);
+ }
+
+ axis.tickGenerator = function (axis) {
+ // copy ticks, scaled to this axis
+ var ticks = [], v, i;
+ for (i = 0; i < otherAxis.ticks.length; ++i) {
+ v = (otherAxis.ticks[i].v - otherAxis.min) / (otherAxis.max - otherAxis.min);
+ v = axis.min + v * (axis.max - axis.min);
+ ticks.push(v);
+ }
+ return ticks;
+ };
+
+ // we might need an extra decimal since forced
+ // ticks don't necessarily fit naturally
+ if (!axis.mode && opts.tickDecimals == null) {
+ var extraDec = Math.max(0, -Math.floor(Math.log(axis.delta) / Math.LN10) + 1),
+ ts = axis.tickGenerator(axis);
+
+ // only proceed if the tick interval rounded
+ // with an extra decimal doesn't give us a
+ // zero at end
+ if (!(ts.length > 1 && /\..*0$/.test((ts[1] - ts[0]).toFixed(extraDec))))
+ axis.tickDecimals = extraDec;
+ }
+ }
+ }
+ }
+
+ function setTicks(axis) {
+ var oticks = axis.options.ticks, ticks = [];
+ if (oticks == null || (typeof oticks == "number" && oticks > 0))
+ ticks = axis.tickGenerator(axis);
+ else if (oticks) {
+ if ($.isFunction(oticks))
+ // generate the ticks
+ ticks = oticks(axis);
+ else
+ ticks = oticks;
+ }
+
+ // clean up/labelify the supplied ticks, copy them over
+ var i, v;
+ axis.ticks = [];
+ for (i = 0; i < ticks.length; ++i) {
+ var label = null;
+ var t = ticks[i];
+ if (typeof t == "object") {
+ v = +t[0];
+ if (t.length > 1)
+ label = t[1];
+ }
+ else
+ v = +t;
+ if (label == null)
+ label = axis.tickFormatter(v, axis);
+ if (!isNaN(v))
+ axis.ticks.push({ v: v, label: label });
+ }
+ }
+
+ function snapRangeToTicks(axis, ticks) {
+ if (axis.options.autoscaleMargin && ticks.length > 0) {
+ // snap to ticks
+ if (axis.options.min == null)
+ axis.min = Math.min(axis.min, ticks[0].v);
+ if (axis.options.max == null && ticks.length > 1)
+ axis.max = Math.max(axis.max, ticks[ticks.length - 1].v);
+ }
+ }
+
+ function draw() {
+
+ surface.clear();
+
+ executeHooks(hooks.drawBackground, [ctx]);
+
+ var grid = options.grid;
+
+ // draw background, if any
+ if (grid.show && grid.backgroundColor)
+ drawBackground();
+
+ if (grid.show && !grid.aboveData) {
+ drawGrid();
+ }
+
+ for (var i = 0; i < series.length; ++i) {
+ executeHooks(hooks.drawSeries, [ctx, series[i]]);
+ drawSeries(series[i]);
+ }
+
+ executeHooks(hooks.draw, [ctx]);
+
+ if (grid.show && grid.aboveData) {
+ drawGrid();
+ }
+
+ surface.render();
+
+ // A draw implies that either the axes or data have changed, so we
+ // should probably update the overlay highlights as well.
+
+ triggerRedrawOverlay();
+ }
+
+ function extractRange(ranges, coord) {
+ var axis, from, to, key, axes = allAxes();
+
+ for (var i = 0; i < axes.length; ++i) {
+ axis = axes[i];
+ if (axis.direction == coord) {
+ key = coord + axis.n + "axis";
+ if (!ranges[key] && axis.n == 1)
+ key = coord + "axis"; // support x1axis as xaxis
+ if (ranges[key]) {
+ from = ranges[key].from;
+ to = ranges[key].to;
+ break;
+ }
+ }
+ }
+
+ // backwards-compat stuff - to be removed in future
+ if (!ranges[key]) {
+ axis = coord == "x" ? xaxes[0] : yaxes[0];
+ from = ranges[coord + "1"];
+ to = ranges[coord + "2"];
+ }
+
+ // auto-reverse as an added bonus
+ if (from != null && to != null && from > to) {
+ var tmp = from;
+ from = to;
+ to = tmp;
+ }
+
+ return { from: from, to: to, axis: axis };
+ }
+
+ function drawBackground() {
+ ctx.save();
+ ctx.translate(plotOffset.left, plotOffset.top);
+
+ ctx.fillStyle = getColorOrGradient(options.grid.backgroundColor, plotHeight, 0, "rgba(255, 255, 255, 0)");
+ ctx.fillRect(0, 0, plotWidth, plotHeight);
+ ctx.restore();
+ }
+
+ function drawGrid() {
+ var i, axes, bw, bc;
+
+ ctx.save();
+ ctx.translate(plotOffset.left, plotOffset.top);
+
+ // draw markings
+ var markings = options.grid.markings;
+ if (markings) {
+ if ($.isFunction(markings)) {
+ axes = plot.getAxes();
+ // xmin etc. is backwards compatibility, to be
+ // removed in the future
+ axes.xmin = axes.xaxis.min;
+ axes.xmax = axes.xaxis.max;
+ axes.ymin = axes.yaxis.min;
+ axes.ymax = axes.yaxis.max;
+
+ markings = markings(axes);
+ }
+
+ for (i = 0; i < markings.length; ++i) {
+ var m = markings[i],
+ xrange = extractRange(m, "x"),
+ yrange = extractRange(m, "y");
+
+ // fill in missing
+ if (xrange.from == null)
+ xrange.from = xrange.axis.min;
+ if (xrange.to == null)
+ xrange.to = xrange.axis.max;
+ if (yrange.from == null)
+ yrange.from = yrange.axis.min;
+ if (yrange.to == null)
+ yrange.to = yrange.axis.max;
+
+ // clip
+ if (xrange.to < xrange.axis.min || xrange.from > xrange.axis.max ||
+ yrange.to < yrange.axis.min || yrange.from > yrange.axis.max)
+ continue;
+
+ xrange.from = Math.max(xrange.from, xrange.axis.min);
+ xrange.to = Math.min(xrange.to, xrange.axis.max);
+ yrange.from = Math.max(yrange.from, yrange.axis.min);
+ yrange.to = Math.min(yrange.to, yrange.axis.max);
+
+ var xequal = xrange.from === xrange.to,
+ yequal = yrange.from === yrange.to;
+
+ if (xequal && yequal) {
+ continue;
+ }
+
+ // then draw
+ xrange.from = Math.floor(xrange.axis.p2c(xrange.from));
+ xrange.to = Math.floor(xrange.axis.p2c(xrange.to));
+ yrange.from = Math.floor(yrange.axis.p2c(yrange.from));
+ yrange.to = Math.floor(yrange.axis.p2c(yrange.to));
+
+ if (xequal || yequal) {
+ var lineWidth = m.lineWidth || options.grid.markingsLineWidth,
+ subPixel = lineWidth % 2 ? 0.5 : 0;
+ ctx.beginPath();
+ ctx.strokeStyle = m.color || options.grid.markingsColor;
+ ctx.lineWidth = lineWidth;
+ if (xequal) {
+ ctx.moveTo(xrange.to + subPixel, yrange.from);
+ ctx.lineTo(xrange.to + subPixel, yrange.to);
+ } else {
+ ctx.moveTo(xrange.from, yrange.to + subPixel);
+ ctx.lineTo(xrange.to, yrange.to + subPixel);
+ }
+ ctx.stroke();
+ } else {
+ ctx.fillStyle = m.color || options.grid.markingsColor;
+ ctx.fillRect(xrange.from, yrange.to,
+ xrange.to - xrange.from,
+ yrange.from - yrange.to);
+ }
+ }
+ }
+
+ // draw the ticks
+ axes = allAxes();
+ bw = options.grid.borderWidth;
+
+ for (var j = 0; j < axes.length; ++j) {
+ var axis = axes[j], box = axis.box,
+ t = axis.tickLength, x, y, xoff, yoff;
+ if (!axis.show || axis.ticks.length == 0)
+ continue;
+
+ ctx.lineWidth = 1;
+
+ // find the edges
+ if (axis.direction == "x") {
+ x = 0;
+ if (t == "full")
+ y = (axis.position == "top" ? 0 : plotHeight);
+ else
+ y = box.top - plotOffset.top + (axis.position == "top" ? box.height : 0);
+ }
+ else {
+ y = 0;
+ if (t == "full")
+ x = (axis.position == "left" ? 0 : plotWidth);
+ else
+ x = box.left - plotOffset.left + (axis.position == "left" ? box.width : 0);
+ }
+
+ // draw tick bar
+ if (!axis.innermost) {
+ ctx.strokeStyle = axis.options.color;
+ ctx.beginPath();
+ xoff = yoff = 0;
+ if (axis.direction == "x")
+ xoff = plotWidth + 1;
+ else
+ yoff = plotHeight + 1;
+
+ if (ctx.lineWidth == 1) {
+ if (axis.direction == "x") {
+ y = Math.floor(y) + 0.5;
+ } else {
+ x = Math.floor(x) + 0.5;
+ }
+ }
+
+ ctx.moveTo(x, y);
+ ctx.lineTo(x + xoff, y + yoff);
+ ctx.stroke();
+ }
+
+ // draw ticks
+
+ ctx.strokeStyle = axis.options.tickColor;
+
+ ctx.beginPath();
+ for (i = 0; i < axis.ticks.length; ++i) {
+ var v = axis.ticks[i].v;
+
+ xoff = yoff = 0;
+
+ if (isNaN(v) || v < axis.min || v > axis.max
+ // skip those lying on the axes if we got a border
+ || (t == "full"
+ && ((typeof bw == "object" && bw[axis.position] > 0) || bw > 0)
+ && (v == axis.min || v == axis.max)))
+ continue;
+
+ if (axis.direction == "x") {
+ x = axis.p2c(v);
+ yoff = t == "full" ? -plotHeight : t;
+
+ if (axis.position == "top")
+ yoff = -yoff;
+ }
+ else {
+ y = axis.p2c(v);
+ xoff = t == "full" ? -plotWidth : t;
+
+ if (axis.position == "left")
+ xoff = -xoff;
+ }
+
+ if (ctx.lineWidth == 1) {
+ if (axis.direction == "x")
+ x = Math.floor(x) + 0.5;
+ else
+ y = Math.floor(y) + 0.5;
+ }
+
+ ctx.moveTo(x, y);
+ ctx.lineTo(x + xoff, y + yoff);
+ }
+
+ ctx.stroke();
+ }
+
+
+ // draw border
+ if (bw) {
+ // If either borderWidth or borderColor is an object, then draw the border
+ // line by line instead of as one rectangle
+ bc = options.grid.borderColor;
+ if(typeof bw == "object" || typeof bc == "object") {
+ if (typeof bw !== "object") {
+ bw = {top: bw, right: bw, bottom: bw, left: bw};
+ }
+ if (typeof bc !== "object") {
+ bc = {top: bc, right: bc, bottom: bc, left: bc};
+ }
+
+ if (bw.top > 0) {
+ ctx.strokeStyle = bc.top;
+ ctx.lineWidth = bw.top;
+ ctx.beginPath();
+ ctx.moveTo(0 - bw.left, 0 - bw.top/2);
+ ctx.lineTo(plotWidth, 0 - bw.top/2);
+ ctx.stroke();
+ }
+
+ if (bw.right > 0) {
+ ctx.strokeStyle = bc.right;
+ ctx.lineWidth = bw.right;
+ ctx.beginPath();
+ ctx.moveTo(plotWidth + bw.right / 2, 0 - bw.top);
+ ctx.lineTo(plotWidth + bw.right / 2, plotHeight);
+ ctx.stroke();
+ }
+
+ if (bw.bottom > 0) {
+ ctx.strokeStyle = bc.bottom;
+ ctx.lineWidth = bw.bottom;
+ ctx.beginPath();
+ ctx.moveTo(plotWidth + bw.right, plotHeight + bw.bottom / 2);
+ ctx.lineTo(0, plotHeight + bw.bottom / 2);
+ ctx.stroke();
+ }
+
+ if (bw.left > 0) {
+ ctx.strokeStyle = bc.left;
+ ctx.lineWidth = bw.left;
+ ctx.beginPath();
+ ctx.moveTo(0 - bw.left/2, plotHeight + bw.bottom);
+ ctx.lineTo(0- bw.left/2, 0);
+ ctx.stroke();
+ }
+ }
+ else {
+ ctx.lineWidth = bw;
+ ctx.strokeStyle = options.grid.borderColor;
+ ctx.strokeRect(-bw/2, -bw/2, plotWidth + bw, plotHeight + bw);
+ }
+ }
+
+ ctx.restore();
+ }
+
+ function drawAxisLabels() {
+
+ $.each(allAxes(), function (_, axis) {
+ var box = axis.box,
+ legacyStyles = axis.direction + "Axis " + axis.direction + axis.n + "Axis",
+ layer = "flot-" + axis.direction + "-axis flot-" + axis.direction + axis.n + "-axis " + legacyStyles,
+ font = axis.options.font || "flot-tick-label tickLabel",
+ tick, x, y, halign, valign;
+
+ // Remove text before checking for axis.show and ticks.length;
+ // otherwise plugins, like flot-tickrotor, that draw their own
+ // tick labels will end up with both theirs and the defaults.
+
+ surface.removeText(layer);
+
+ if (!axis.show || axis.ticks.length == 0)
+ return;
+
+ for (var i = 0; i < axis.ticks.length; ++i) {
+
+ tick = axis.ticks[i];
+ if (!tick.label || tick.v < axis.min || tick.v > axis.max)
+ continue;
+
+ if (axis.direction == "x") {
+ halign = "center";
+ x = plotOffset.left + axis.p2c(tick.v);
+ if (axis.position == "bottom") {
+ y = box.top + box.padding;
+ } else {
+ y = box.top + box.height - box.padding;
+ valign = "bottom";
+ }
+ } else {
+ valign = "middle";
+ y = plotOffset.top + axis.p2c(tick.v);
+ if (axis.position == "left") {
+ x = box.left + box.width - box.padding;
+ halign = "right";
+ } else {
+ x = box.left + box.padding;
+ }
+ }
+
+ surface.addText(layer, x, y, tick.label, font, null, null, halign, valign);
+ }
+ });
+ }
+
+ function drawSeries(series) {
+ if (series.lines.show)
+ drawSeriesLines(series);
+ if (series.bars.show)
+ drawSeriesBars(series);
+ if (series.points.show)
+ drawSeriesPoints(series);
+ }
+
+ function drawSeriesLines(series) {
+ function plotLine(datapoints, xoffset, yoffset, axisx, axisy) {
+ var points = datapoints.points,
+ ps = datapoints.pointsize,
+ prevx = null, prevy = null;
+
+ ctx.beginPath();
+ for (var i = ps; i < points.length; i += ps) {
+ var x1 = points[i - ps], y1 = points[i - ps + 1],
+ x2 = points[i], y2 = points[i + 1];
+
+ if (x1 == null || x2 == null)
+ continue;
+
+ // clip with ymin
+ if (y1 <= y2 && y1 < axisy.min) {
+ if (y2 < axisy.min)
+ continue; // line segment is outside
+ // compute new intersection point
+ x1 = (axisy.min - y1) / (y2 - y1) * (x2 - x1) + x1;
+ y1 = axisy.min;
+ }
+ else if (y2 <= y1 && y2 < axisy.min) {
+ if (y1 < axisy.min)
+ continue;
+ x2 = (axisy.min - y1) / (y2 - y1) * (x2 - x1) + x1;
+ y2 = axisy.min;
+ }
+
+ // clip with ymax
+ if (y1 >= y2 && y1 > axisy.max) {
+ if (y2 > axisy.max)
+ continue;
+ x1 = (axisy.max - y1) / (y2 - y1) * (x2 - x1) + x1;
+ y1 = axisy.max;
+ }
+ else if (y2 >= y1 && y2 > axisy.max) {
+ if (y1 > axisy.max)
+ continue;
+ x2 = (axisy.max - y1) / (y2 - y1) * (x2 - x1) + x1;
+ y2 = axisy.max;
+ }
+
+ // clip with xmin
+ if (x1 <= x2 && x1 < axisx.min) {
+ if (x2 < axisx.min)
+ continue;
+ y1 = (axisx.min - x1) / (x2 - x1) * (y2 - y1) + y1;
+ x1 = axisx.min;
+ }
+ else if (x2 <= x1 && x2 < axisx.min) {
+ if (x1 < axisx.min)
+ continue;
+ y2 = (axisx.min - x1) / (x2 - x1) * (y2 - y1) + y1;
+ x2 = axisx.min;
+ }
+
+ // clip with xmax
+ if (x1 >= x2 && x1 > axisx.max) {
+ if (x2 > axisx.max)
+ continue;
+ y1 = (axisx.max - x1) / (x2 - x1) * (y2 - y1) + y1;
+ x1 = axisx.max;
+ }
+ else if (x2 >= x1 && x2 > axisx.max) {
+ if (x1 > axisx.max)
+ continue;
+ y2 = (axisx.max - x1) / (x2 - x1) * (y2 - y1) + y1;
+ x2 = axisx.max;
+ }
+
+ if (x1 != prevx || y1 != prevy)
+ ctx.moveTo(axisx.p2c(x1) + xoffset, axisy.p2c(y1) + yoffset);
+
+ prevx = x2;
+ prevy = y2;
+ ctx.lineTo(axisx.p2c(x2) + xoffset, axisy.p2c(y2) + yoffset);
+ }
+ ctx.stroke();
+ }
+
+ function plotLineArea(datapoints, axisx, axisy) {
+ var points = datapoints.points,
+ ps = datapoints.pointsize,
+ bottom = Math.min(Math.max(0, axisy.min), axisy.max),
+ i = 0, top, areaOpen = false,
+ ypos = 1, segmentStart = 0, segmentEnd = 0;
+
+ // we process each segment in two turns, first forward
+ // direction to sketch out top, then once we hit the
+ // end we go backwards to sketch the bottom
+ while (true) {
+ if (ps > 0 && i > points.length + ps)
+ break;
+
+ i += ps; // ps is negative if going backwards
+
+ var x1 = points[i - ps],
+ y1 = points[i - ps + ypos],
+ x2 = points[i], y2 = points[i + ypos];
+
+ if (areaOpen) {
+ if (ps > 0 && x1 != null && x2 == null) {
+ // at turning point
+ segmentEnd = i;
+ ps = -ps;
+ ypos = 2;
+ continue;
+ }
+
+ if (ps < 0 && i == segmentStart + ps) {
+ // done with the reverse sweep
+ ctx.fill();
+ areaOpen = false;
+ ps = -ps;
+ ypos = 1;
+ i = segmentStart = segmentEnd + ps;
+ continue;
+ }
+ }
+
+ if (x1 == null || x2 == null)
+ continue;
+
+ // clip x values
+
+ // clip with xmin
+ if (x1 <= x2 && x1 < axisx.min) {
+ if (x2 < axisx.min)
+ continue;
+ y1 = (axisx.min - x1) / (x2 - x1) * (y2 - y1) + y1;
+ x1 = axisx.min;
+ }
+ else if (x2 <= x1 && x2 < axisx.min) {
+ if (x1 < axisx.min)
+ continue;
+ y2 = (axisx.min - x1) / (x2 - x1) * (y2 - y1) + y1;
+ x2 = axisx.min;
+ }
+
+ // clip with xmax
+ if (x1 >= x2 && x1 > axisx.max) {
+ if (x2 > axisx.max)
+ continue;
+ y1 = (axisx.max - x1) / (x2 - x1) * (y2 - y1) + y1;
+ x1 = axisx.max;
+ }
+ else if (x2 >= x1 && x2 > axisx.max) {
+ if (x1 > axisx.max)
+ continue;
+ y2 = (axisx.max - x1) / (x2 - x1) * (y2 - y1) + y1;
+ x2 = axisx.max;
+ }
+
+ if (!areaOpen) {
+ // open area
+ ctx.beginPath();
+ ctx.moveTo(axisx.p2c(x1), axisy.p2c(bottom));
+ areaOpen = true;
+ }
+
+ // now first check the case where both is outside
+ if (y1 >= axisy.max && y2 >= axisy.max) {
+ ctx.lineTo(axisx.p2c(x1), axisy.p2c(axisy.max));
+ ctx.lineTo(axisx.p2c(x2), axisy.p2c(axisy.max));
+ continue;
+ }
+ else if (y1 <= axisy.min && y2 <= axisy.min) {
+ ctx.lineTo(axisx.p2c(x1), axisy.p2c(axisy.min));
+ ctx.lineTo(axisx.p2c(x2), axisy.p2c(axisy.min));
+ continue;
+ }
+
+ // else it's a bit more complicated, there might
+ // be a flat maxed out rectangle first, then a
+ // triangular cutout or reverse; to find these
+ // keep track of the current x values
+ var x1old = x1, x2old = x2;
+
+ // clip the y values, without shortcutting, we
+ // go through all cases in turn
+
+ // clip with ymin
+ if (y1 <= y2 && y1 < axisy.min && y2 >= axisy.min) {
+ x1 = (axisy.min - y1) / (y2 - y1) * (x2 - x1) + x1;
+ y1 = axisy.min;
+ }
+ else if (y2 <= y1 && y2 < axisy.min && y1 >= axisy.min) {
+ x2 = (axisy.min - y1) / (y2 - y1) * (x2 - x1) + x1;
+ y2 = axisy.min;
+ }
+
+ // clip with ymax
+ if (y1 >= y2 && y1 > axisy.max && y2 <= axisy.max) {
+ x1 = (axisy.max - y1) / (y2 - y1) * (x2 - x1) + x1;
+ y1 = axisy.max;
+ }
+ else if (y2 >= y1 && y2 > axisy.max && y1 <= axisy.max) {
+ x2 = (axisy.max - y1) / (y2 - y1) * (x2 - x1) + x1;
+ y2 = axisy.max;
+ }
+
+ // if the x value was changed we got a rectangle
+ // to fill
+ if (x1 != x1old) {
+ ctx.lineTo(axisx.p2c(x1old), axisy.p2c(y1));
+ // it goes to (x1, y1), but we fill that below
+ }
+
+ // fill triangular section, this sometimes result
+ // in redundant points if (x1, y1) hasn't changed
+ // from previous line to, but we just ignore that
+ ctx.lineTo(axisx.p2c(x1), axisy.p2c(y1));
+ ctx.lineTo(axisx.p2c(x2), axisy.p2c(y2));
+
+ // fill the other rectangle if it's there
+ if (x2 != x2old) {
+ ctx.lineTo(axisx.p2c(x2), axisy.p2c(y2));
+ ctx.lineTo(axisx.p2c(x2old), axisy.p2c(y2));
+ }
+ }
+ }
+
+ ctx.save();
+ ctx.translate(plotOffset.left, plotOffset.top);
+ ctx.lineJoin = "round";
+
+ var lw = series.lines.lineWidth,
+ sw = series.shadowSize;
+ // FIXME: consider another form of shadow when filling is turned on
+ if (lw > 0 && sw > 0) {
+ // draw shadow as a thick and thin line with transparency
+ ctx.lineWidth = sw;
+ ctx.strokeStyle = "rgba(0,0,0,0.1)";
+ // position shadow at angle from the mid of line
+ var angle = Math.PI/18;
+ plotLine(series.datapoints, Math.sin(angle) * (lw/2 + sw/2), Math.cos(angle) * (lw/2 + sw/2), series.xaxis, series.yaxis);
+ ctx.lineWidth = sw/2;
+ plotLine(series.datapoints, Math.sin(angle) * (lw/2 + sw/4), Math.cos(angle) * (lw/2 + sw/4), series.xaxis, series.yaxis);
+ }
+
+ ctx.lineWidth = lw;
+ ctx.strokeStyle = series.color;
+ var fillStyle = getFillStyle(series.lines, series.color, 0, plotHeight);
+ if (fillStyle) {
+ ctx.fillStyle = fillStyle;
+ plotLineArea(series.datapoints, series.xaxis, series.yaxis);
+ }
+
+ if (lw > 0)
+ plotLine(series.datapoints, 0, 0, series.xaxis, series.yaxis);
+ ctx.restore();
+ }
+
+ function drawSeriesPoints(series) {
+ function plotPoints(datapoints, radius, fillStyle, offset, shadow, axisx, axisy, symbol) {
+ var points = datapoints.points, ps = datapoints.pointsize;
+
+ for (var i = 0; i < points.length; i += ps) {
+ var x = points[i], y = points[i + 1];
+ if (x == null || x < axisx.min || x > axisx.max || y < axisy.min || y > axisy.max)
+ continue;
+
+ ctx.beginPath();
+ x = axisx.p2c(x);
+ y = axisy.p2c(y) + offset;
+ if (symbol == "circle")
+ ctx.arc(x, y, radius, 0, shadow ? Math.PI : Math.PI * 2, false);
+ else
+ symbol(ctx, x, y, radius, shadow);
+ ctx.closePath();
+
+ if (fillStyle) {
+ ctx.fillStyle = fillStyle;
+ ctx.fill();
+ }
+ ctx.stroke();
+ }
+ }
+
+ ctx.save();
+ ctx.translate(plotOffset.left, plotOffset.top);
+
+ var lw = series.points.lineWidth,
+ sw = series.shadowSize,
+ radius = series.points.radius,
+ symbol = series.points.symbol;
+
+ // If the user sets the line width to 0, we change it to a very
+ // small value. A line width of 0 seems to force the default of 1.
+ // Doing the conditional here allows the shadow setting to still be
+ // optional even with a lineWidth of 0.
+
+ if( lw == 0 )
+ lw = 0.0001;
+
+ if (lw > 0 && sw > 0) {
+ // draw shadow in two steps
+ var w = sw / 2;
+ ctx.lineWidth = w;
+ ctx.strokeStyle = "rgba(0,0,0,0.1)";
+ plotPoints(series.datapoints, radius, null, w + w/2, true,
+ series.xaxis, series.yaxis, symbol);
+
+ ctx.strokeStyle = "rgba(0,0,0,0.2)";
+ plotPoints(series.datapoints, radius, null, w/2, true,
+ series.xaxis, series.yaxis, symbol);
+ }
+
+ ctx.lineWidth = lw;
+ ctx.strokeStyle = series.color;
+ plotPoints(series.datapoints, radius,
+ getFillStyle(series.points, series.color), 0, false,
+ series.xaxis, series.yaxis, symbol);
+ ctx.restore();
+ }
+
+ function drawBar(x, y, b, barLeft, barRight, fillStyleCallback, axisx, axisy, c, horizontal, lineWidth) {
+ var left, right, bottom, top,
+ drawLeft, drawRight, drawTop, drawBottom,
+ tmp;
+
+ // in horizontal mode, we start the bar from the left
+ // instead of from the bottom so it appears to be
+ // horizontal rather than vertical
+ if (horizontal) {
+ drawBottom = drawRight = drawTop = true;
+ drawLeft = false;
+ left = b;
+ right = x;
+ top = y + barLeft;
+ bottom = y + barRight;
+
+ // account for negative bars
+ if (right < left) {
+ tmp = right;
+ right = left;
+ left = tmp;
+ drawLeft = true;
+ drawRight = false;
+ }
+ }
+ else {
+ drawLeft = drawRight = drawTop = true;
+ drawBottom = false;
+ left = x + barLeft;
+ right = x + barRight;
+ bottom = b;
+ top = y;
+
+ // account for negative bars
+ if (top < bottom) {
+ tmp = top;
+ top = bottom;
+ bottom = tmp;
+ drawBottom = true;
+ drawTop = false;
+ }
+ }
+
+ // clip
+ if (right < axisx.min || left > axisx.max ||
+ top < axisy.min || bottom > axisy.max)
+ return;
+
+ if (left < axisx.min) {
+ left = axisx.min;
+ drawLeft = false;
+ }
+
+ if (right > axisx.max) {
+ right = axisx.max;
+ drawRight = false;
+ }
+
+ if (bottom < axisy.min) {
+ bottom = axisy.min;
+ drawBottom = false;
+ }
+
+ if (top > axisy.max) {
+ top = axisy.max;
+ drawTop = false;
+ }
+
+ left = axisx.p2c(left);
+ bottom = axisy.p2c(bottom);
+ right = axisx.p2c(right);
+ top = axisy.p2c(top);
+
+ // fill the bar
+ if (fillStyleCallback) {
+ c.fillStyle = fillStyleCallback(bottom, top);
+ c.fillRect(left, top, right - left, bottom - top)
+ }
+
+ // draw outline
+ if (lineWidth > 0 && (drawLeft || drawRight || drawTop || drawBottom)) {
+ c.beginPath();
+
+ // FIXME: inline moveTo is buggy with excanvas
+ c.moveTo(left, bottom);
+ if (drawLeft)
+ c.lineTo(left, top);
+ else
+ c.moveTo(left, top);
+ if (drawTop)
+ c.lineTo(right, top);
+ else
+ c.moveTo(right, top);
+ if (drawRight)
+ c.lineTo(right, bottom);
+ else
+ c.moveTo(right, bottom);
+ if (drawBottom)
+ c.lineTo(left, bottom);
+ else
+ c.moveTo(left, bottom);
+ c.stroke();
+ }
+ }
+
+ function drawSeriesBars(series) {
+ function plotBars(datapoints, barLeft, barRight, fillStyleCallback, axisx, axisy) {
+ var points = datapoints.points, ps = datapoints.pointsize;
+
+ for (var i = 0; i < points.length; i += ps) {
+ if (points[i] == null)
+ continue;
+ drawBar(points[i], points[i + 1], points[i + 2], barLeft, barRight, fillStyleCallback, axisx, axisy, ctx, series.bars.horizontal, series.bars.lineWidth);
+ }
+ }
+
+ ctx.save();
+ ctx.translate(plotOffset.left, plotOffset.top);
+
+ // FIXME: figure out a way to add shadows (for instance along the right edge)
+ ctx.lineWidth = series.bars.lineWidth;
+ ctx.strokeStyle = series.color;
+
+ var barLeft;
+
+ switch (series.bars.align) {
+ case "left":
+ barLeft = 0;
+ break;
+ case "right":
+ barLeft = -series.bars.barWidth;
+ break;
+ default:
+ barLeft = -series.bars.barWidth / 2;
+ }
+
+ var fillStyleCallback = series.bars.fill ? function (bottom, top) { return getFillStyle(series.bars, series.color, bottom, top); } : null;
+ plotBars(series.datapoints, barLeft, barLeft + series.bars.barWidth, fillStyleCallback, series.xaxis, series.yaxis);
+ ctx.restore();
+ }
+
+ function getFillStyle(filloptions, seriesColor, bottom, top) {
+ var fill = filloptions.fill;
+ if (!fill)
+ return null;
+
+ if (filloptions.fillColor)
+ return getColorOrGradient(filloptions.fillColor, bottom, top, seriesColor);
+
+ var c = $.color.parse(seriesColor);
+ c.a = typeof fill == "number" ? fill : 0.4;
+ c.normalize();
+ return c.toString();
+ }
+
+ function insertLegend() {
+
+ if (options.legend.container != null) {
+ $(options.legend.container).html("");
+ } else {
+ placeholder.find(".legend").remove();
+ }
+
+ if (!options.legend.show) {
+ return;
+ }
+
+ var fragments = [], entries = [], rowStarted = false,
+ lf = options.legend.labelFormatter, s, label;
+
+ // Build a list of legend entries, with each having a label and a color
+
+ for (var i = 0; i < series.length; ++i) {
+ s = series[i];
+ if (s.label) {
+ label = lf ? lf(s.label, s) : s.label;
+ if (label) {
+ entries.push({
+ label: label,
+ color: s.color
+ });
+ }
+ }
+ }
+
+ // Sort the legend using either the default or a custom comparator
+
+ if (options.legend.sorted) {
+ if ($.isFunction(options.legend.sorted)) {
+ entries.sort(options.legend.sorted);
+ } else if (options.legend.sorted == "reverse") {
+ entries.reverse();
+ } else {
+ var ascending = options.legend.sorted != "descending";
+ entries.sort(function(a, b) {
+ return a.label == b.label ? 0 : (
+ (a.label < b.label) != ascending ? 1 : -1 // Logical XOR
+ );
+ });
+ }
+ }
+
+ // Generate markup for the list of entries, in their final order
+
+ for (var i = 0; i < entries.length; ++i) {
+
+ var entry = entries[i];
+
+ if (i % options.legend.noColumns == 0) {
+ if (rowStarted)
+ fragments.push('');
+ fragments.push('');
+ rowStarted = true;
+ }
+
+ fragments.push(
+ ' ' +
+ '' + entry.label + ' '
+ );
+ }
+
+ if (rowStarted)
+ fragments.push(' ');
+
+ if (fragments.length == 0)
+ return;
+
+ var table = '' + fragments.join("") + '
';
+ if (options.legend.container != null)
+ $(options.legend.container).html(table);
+ else {
+ var pos = "",
+ p = options.legend.position,
+ m = options.legend.margin;
+ if (m[0] == null)
+ m = [m, m];
+ if (p.charAt(0) == "n")
+ pos += 'top:' + (m[1] + plotOffset.top) + 'px;';
+ else if (p.charAt(0) == "s")
+ pos += 'bottom:' + (m[1] + plotOffset.bottom) + 'px;';
+ if (p.charAt(1) == "e")
+ pos += 'right:' + (m[0] + plotOffset.right) + 'px;';
+ else if (p.charAt(1) == "w")
+ pos += 'left:' + (m[0] + plotOffset.left) + 'px;';
+ var legend = $('' + table.replace('style="', 'style="position:absolute;' + pos +';') + '
').appendTo(placeholder);
+ if (options.legend.backgroundOpacity != 0.0) {
+ // put in the transparent background
+ // separately to avoid blended labels and
+ // label boxes
+ var c = options.legend.backgroundColor;
+ if (c == null) {
+ c = options.grid.backgroundColor;
+ if (c && typeof c == "string")
+ c = $.color.parse(c);
+ else
+ c = $.color.extract(legend, 'background-color');
+ c.a = 1;
+ c = c.toString();
+ }
+ var div = legend.children();
+ $('
').prependTo(legend).css('opacity', options.legend.backgroundOpacity);
+ }
+ }
+ }
+
+
+ // interactive features
+
+ var highlights = [],
+ redrawTimeout = null;
+
+ // returns the data item the mouse is over, or null if none is found
+ function findNearbyItem(mouseX, mouseY, seriesFilter) {
+ var maxDistance = options.grid.mouseActiveRadius,
+ smallestDistance = maxDistance * maxDistance + 1,
+ item = null, foundPoint = false, i, j, ps;
+
+ for (i = series.length - 1; i >= 0; --i) {
+ if (!seriesFilter(series[i]))
+ continue;
+
+ var s = series[i],
+ axisx = s.xaxis,
+ axisy = s.yaxis,
+ points = s.datapoints.points,
+ mx = axisx.c2p(mouseX), // precompute some stuff to make the loop faster
+ my = axisy.c2p(mouseY),
+ maxx = maxDistance / axisx.scale,
+ maxy = maxDistance / axisy.scale;
+
+ ps = s.datapoints.pointsize;
+ // with inverse transforms, we can't use the maxx/maxy
+ // optimization, sadly
+ if (axisx.options.inverseTransform)
+ maxx = Number.MAX_VALUE;
+ if (axisy.options.inverseTransform)
+ maxy = Number.MAX_VALUE;
+
+ if (s.lines.show || s.points.show) {
+ for (j = 0; j < points.length; j += ps) {
+ var x = points[j], y = points[j + 1];
+ if (x == null)
+ continue;
+
+ // For points and lines, the cursor must be within a
+ // certain distance to the data point
+ if (x - mx > maxx || x - mx < -maxx ||
+ y - my > maxy || y - my < -maxy)
+ continue;
+
+ // We have to calculate distances in pixels, not in
+ // data units, because the scales of the axes may be different
+ var dx = Math.abs(axisx.p2c(x) - mouseX),
+ dy = Math.abs(axisy.p2c(y) - mouseY),
+ dist = dx * dx + dy * dy; // we save the sqrt
+
+ // use <= to ensure last point takes precedence
+ // (last generally means on top of)
+ if (dist < smallestDistance) {
+ smallestDistance = dist;
+ item = [i, j / ps];
+ }
+ }
+ }
+
+ if (s.bars.show && !item) { // no other point can be nearby
+
+ var barLeft, barRight;
+
+ switch (s.bars.align) {
+ case "left":
+ barLeft = 0;
+ break;
+ case "right":
+ barLeft = -s.bars.barWidth;
+ break;
+ default:
+ barLeft = -s.bars.barWidth / 2;
+ }
+
+ barRight = barLeft + s.bars.barWidth;
+
+ for (j = 0; j < points.length; j += ps) {
+ var x = points[j], y = points[j + 1], b = points[j + 2];
+ if (x == null)
+ continue;
+
+ // for a bar graph, the cursor must be inside the bar
+ if (series[i].bars.horizontal ?
+ (mx <= Math.max(b, x) && mx >= Math.min(b, x) &&
+ my >= y + barLeft && my <= y + barRight) :
+ (mx >= x + barLeft && mx <= x + barRight &&
+ my >= Math.min(b, y) && my <= Math.max(b, y)))
+ item = [i, j / ps];
+ }
+ }
+ }
+
+ if (item) {
+ i = item[0];
+ j = item[1];
+ ps = series[i].datapoints.pointsize;
+
+ return { datapoint: series[i].datapoints.points.slice(j * ps, (j + 1) * ps),
+ dataIndex: j,
+ series: series[i],
+ seriesIndex: i };
+ }
+
+ return null;
+ }
+
+ function onMouseMove(e) {
+ if (options.grid.hoverable)
+ triggerClickHoverEvent("plothover", e,
+ function (s) { return s["hoverable"] != false; });
+ }
+
+ function onMouseLeave(e) {
+ if (options.grid.hoverable)
+ triggerClickHoverEvent("plothover", e,
+ function (s) { return false; });
+ }
+
+ function onClick(e) {
+ triggerClickHoverEvent("plotclick", e,
+ function (s) { return s["clickable"] != false; });
+ }
+
+ // trigger click or hover event (they send the same parameters
+ // so we share their code)
+ function triggerClickHoverEvent(eventname, event, seriesFilter) {
+ var offset = eventHolder.offset(),
+ canvasX = event.pageX - offset.left - plotOffset.left,
+ canvasY = event.pageY - offset.top - plotOffset.top,
+ pos = canvasToAxisCoords({ left: canvasX, top: canvasY });
+
+ pos.pageX = event.pageX;
+ pos.pageY = event.pageY;
+
+ var item = findNearbyItem(canvasX, canvasY, seriesFilter);
+
+ if (item) {
+ // fill in mouse pos for any listeners out there
+ item.pageX = parseInt(item.series.xaxis.p2c(item.datapoint[0]) + offset.left + plotOffset.left, 10);
+ item.pageY = parseInt(item.series.yaxis.p2c(item.datapoint[1]) + offset.top + plotOffset.top, 10);
+ }
+
+ if (options.grid.autoHighlight) {
+ // clear auto-highlights
+ for (var i = 0; i < highlights.length; ++i) {
+ var h = highlights[i];
+ if (h.auto == eventname &&
+ !(item && h.series == item.series &&
+ h.point[0] == item.datapoint[0] &&
+ h.point[1] == item.datapoint[1]))
+ unhighlight(h.series, h.point);
+ }
+
+ if (item)
+ highlight(item.series, item.datapoint, eventname);
+ }
+
+ placeholder.trigger(eventname, [ pos, item ]);
+ }
+
+ function triggerRedrawOverlay() {
+ var t = options.interaction.redrawOverlayInterval;
+ if (t == -1) { // skip event queue
+ drawOverlay();
+ return;
+ }
+
+ if (!redrawTimeout)
+ redrawTimeout = setTimeout(drawOverlay, t);
+ }
+
+ function drawOverlay() {
+ redrawTimeout = null;
+
+ // draw highlights
+ octx.save();
+ overlay.clear();
+ octx.translate(plotOffset.left, plotOffset.top);
+
+ var i, hi;
+ for (i = 0; i < highlights.length; ++i) {
+ hi = highlights[i];
+
+ if (hi.series.bars.show)
+ drawBarHighlight(hi.series, hi.point);
+ else
+ drawPointHighlight(hi.series, hi.point);
+ }
+ octx.restore();
+
+ executeHooks(hooks.drawOverlay, [octx]);
+ }
+
+ function highlight(s, point, auto) {
+ if (typeof s == "number")
+ s = series[s];
+
+ if (typeof point == "number") {
+ var ps = s.datapoints.pointsize;
+ point = s.datapoints.points.slice(ps * point, ps * (point + 1));
+ }
+
+ var i = indexOfHighlight(s, point);
+ if (i == -1) {
+ highlights.push({ series: s, point: point, auto: auto });
+
+ triggerRedrawOverlay();
+ }
+ else if (!auto)
+ highlights[i].auto = false;
+ }
+
+ function unhighlight(s, point) {
+ if (s == null && point == null) {
+ highlights = [];
+ triggerRedrawOverlay();
+ return;
+ }
+
+ if (typeof s == "number")
+ s = series[s];
+
+ if (typeof point == "number") {
+ var ps = s.datapoints.pointsize;
+ point = s.datapoints.points.slice(ps * point, ps * (point + 1));
+ }
+
+ var i = indexOfHighlight(s, point);
+ if (i != -1) {
+ highlights.splice(i, 1);
+
+ triggerRedrawOverlay();
+ }
+ }
+
+ function indexOfHighlight(s, p) {
+ for (var i = 0; i < highlights.length; ++i) {
+ var h = highlights[i];
+ if (h.series == s && h.point[0] == p[0]
+ && h.point[1] == p[1])
+ return i;
+ }
+ return -1;
+ }
+
+ function drawPointHighlight(series, point) {
+ var x = point[0], y = point[1],
+ axisx = series.xaxis, axisy = series.yaxis,
+ highlightColor = (typeof series.highlightColor === "string") ? series.highlightColor : $.color.parse(series.color).scale('a', 0.5).toString();
+
+ if (x < axisx.min || x > axisx.max || y < axisy.min || y > axisy.max)
+ return;
+
+ var pointRadius = series.points.radius + series.points.lineWidth / 2;
+ octx.lineWidth = pointRadius;
+ octx.strokeStyle = highlightColor;
+ var radius = 1.5 * pointRadius;
+ x = axisx.p2c(x);
+ y = axisy.p2c(y);
+
+ octx.beginPath();
+ if (series.points.symbol == "circle")
+ octx.arc(x, y, radius, 0, 2 * Math.PI, false);
+ else
+ series.points.symbol(octx, x, y, radius, false);
+ octx.closePath();
+ octx.stroke();
+ }
+
+ function drawBarHighlight(series, point) {
+ var highlightColor = (typeof series.highlightColor === "string") ? series.highlightColor : $.color.parse(series.color).scale('a', 0.5).toString(),
+ fillStyle = highlightColor,
+ barLeft;
+
+ switch (series.bars.align) {
+ case "left":
+ barLeft = 0;
+ break;
+ case "right":
+ barLeft = -series.bars.barWidth;
+ break;
+ default:
+ barLeft = -series.bars.barWidth / 2;
+ }
+
+ octx.lineWidth = series.bars.lineWidth;
+ octx.strokeStyle = highlightColor;
+
+ drawBar(point[0], point[1], point[2] || 0, barLeft, barLeft + series.bars.barWidth,
+ function () { return fillStyle; }, series.xaxis, series.yaxis, octx, series.bars.horizontal, series.bars.lineWidth);
+ }
+
+ function getColorOrGradient(spec, bottom, top, defaultColor) {
+ if (typeof spec == "string")
+ return spec;
+ else {
+ // assume this is a gradient spec; IE currently only
+ // supports a simple vertical gradient properly, so that's
+ // what we support too
+ var gradient = ctx.createLinearGradient(0, top, 0, bottom);
+
+ for (var i = 0, l = spec.colors.length; i < l; ++i) {
+ var c = spec.colors[i];
+ if (typeof c != "string") {
+ var co = $.color.parse(defaultColor);
+ if (c.brightness != null)
+ co = co.scale('rgb', c.brightness);
+ if (c.opacity != null)
+ co.a *= c.opacity;
+ c = co.toString();
+ }
+ gradient.addColorStop(i / (l - 1), c);
+ }
+
+ return gradient;
+ }
+ }
+ }
+
+ // Add the plot function to the top level of the jQuery object
+
+ $.plot = function(placeholder, data, options) {
+ //var t0 = new Date();
+ var plot = new Plot($(placeholder), data, options, $.plot.plugins);
+ //(window.console ? console.log : alert)("time used (msecs): " + ((new Date()).getTime() - t0.getTime()));
+ return plot;
+ };
+
+ $.plot.version = "0.8.3";
+
+ $.plot.plugins = [];
+
+ // Also add the plot function as a chainable property
+
+ $.fn.plot = function(data, options) {
+ return this.each(function() {
+ $.plot(this, data, options);
+ });
+ };
+
+ // round to nearby lower multiple of base
+ function floorInBase(n, base) {
+ return base * Math.floor(n / base);
+ }
+
+})(jQuery);
diff --git a/djangoproject/static/js/lib/jquery-flot/jquery.flot.min.js b/djangoproject/static/js/lib/jquery-flot/jquery.flot.min.js
new file mode 100644
index 000000000..64a5e5a5b
--- /dev/null
+++ b/djangoproject/static/js/lib/jquery-flot/jquery.flot.min.js
@@ -0,0 +1 @@
+(function(b){b.color={};b.color.make=function(f,e,c,d){var h={};h.r=f||0;h.g=e||0;h.b=c||0;h.a=d!=null?d:1;h.add=function(k,j){for(var g=0;g=1){return"rgb("+[h.r,h.g,h.b].join(",")+")"}else{return"rgba("+[h.r,h.g,h.b,h.a].join(",")+")"}};h.normalize=function(){function g(j,k,i){return ki?i:k}h.r=g(0,parseInt(h.r),255);h.g=g(0,parseInt(h.g),255);h.b=g(0,parseInt(h.b),255);h.a=g(0,h.a,1);return h};h.clone=function(){return b.color.make(h.r,h.b,h.g,h.a)};return h.normalize()};b.color.extract=function(e,d){var f;do{f=e.css(d).toLowerCase();if(f!=""&&f!="transparent"){break}e=e.parent()}while(e.length&&!b.nodeName(e.get(0),"body"));if(f=="rgba(0, 0, 0, 0)"){f="transparent"}return b.color.parse(f)};b.color.parse=function(f){var e,c=b.color.make;if(e=/rgb\(\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*\)/.exec(f)){return c(parseInt(e[1],10),parseInt(e[2],10),parseInt(e[3],10))}if(e=/rgba\(\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*,\s*([0-9]+(?:\.[0-9]+)?)\s*\)/.exec(f)){return c(parseInt(e[1],10),parseInt(e[2],10),parseInt(e[3],10),parseFloat(e[4]))}if(e=/rgb\(\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\%\s*\)/.exec(f)){return c(parseFloat(e[1])*2.55,parseFloat(e[2])*2.55,parseFloat(e[3])*2.55)}if(e=/rgba\(\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\s*\)/.exec(f)){return c(parseFloat(e[1])*2.55,parseFloat(e[2])*2.55,parseFloat(e[3])*2.55,parseFloat(e[4]))}if(e=/#([a-fA-F0-9]{2})([a-fA-F0-9]{2})([a-fA-F0-9]{2})/.exec(f)){return c(parseInt(e[1],16),parseInt(e[2],16),parseInt(e[3],16))}if(e=/#([a-fA-F0-9])([a-fA-F0-9])([a-fA-F0-9])/.exec(f)){return c(parseInt(e[1]+e[1],16),parseInt(e[2]+e[2],16),parseInt(e[3]+e[3],16))}var d=b.trim(f).toLowerCase();if(d=="transparent"){return c(255,255,255,0)}else{e=a[d]||[0,0,0];return c(e[0],e[1],e[2])}};var a={aqua:[0,255,255],azure:[240,255,255],beige:[245,245,220],black:[0,0,0],blue:[0,0,255],brown:[165,42,42],cyan:[0,255,255],darkblue:[0,0,139],darkcyan:[0,139,139],darkgrey:[169,169,169],darkgreen:[0,100,0],darkkhaki:[189,183,107],darkmagenta:[139,0,139],darkolivegreen:[85,107,47],darkorange:[255,140,0],darkorchid:[153,50,204],darkred:[139,0,0],darksalmon:[233,150,122],darkviolet:[148,0,211],fuchsia:[255,0,255],gold:[255,215,0],green:[0,128,0],indigo:[75,0,130],khaki:[240,230,140],lightblue:[173,216,230],lightcyan:[224,255,255],lightgreen:[144,238,144],lightgrey:[211,211,211],lightpink:[255,182,193],lightyellow:[255,255,224],lime:[0,255,0],magenta:[255,0,255],maroon:[128,0,0],navy:[0,0,128],olive:[128,128,0],orange:[255,165,0],pink:[255,192,203],purple:[128,0,128],violet:[128,0,128],red:[255,0,0],silver:[192,192,192],white:[255,255,255],yellow:[255,255,0]}})(jQuery);(function(e){var d=Object.prototype.hasOwnProperty;if(!e.fn.detach){e.fn.detach=function(){return this.each(function(){if(this.parentNode){this.parentNode.removeChild(this)}})}}function a(h,g){var j=g.children("."+h)[0];if(j==null){j=document.createElement("canvas");j.className=h;e(j).css({direction:"ltr",position:"absolute",left:0,top:0}).appendTo(g);if(!j.getContext){if(window.G_vmlCanvasManager){j=window.G_vmlCanvasManager.initElement(j)}else{throw new Error("Canvas is not available. If you're using IE with a fall-back such as Excanvas, then there's either a mistake in your conditional include, or the page has no DOCTYPE and is rendering in Quirks Mode.")}}}this.element=j;var i=this.context=j.getContext("2d");var f=window.devicePixelRatio||1,k=i.webkitBackingStorePixelRatio||i.mozBackingStorePixelRatio||i.msBackingStorePixelRatio||i.oBackingStorePixelRatio||i.backingStorePixelRatio||1;this.pixelRatio=f/k;this.resize(g.width(),g.height());this.textContainer=null;this.text={};this._textCache={}}a.prototype.resize=function(i,f){if(i<=0||f<=0){throw new Error("Invalid dimensions for plot, width = "+i+", height = "+f)}var h=this.element,g=this.context,j=this.pixelRatio;if(this.width!=i){h.width=i*j;h.style.width=i+"px";this.width=i}if(this.height!=f){h.height=f*j;h.style.height=f+"px";this.height=f}g.restore();g.save();g.scale(j,j)};a.prototype.clear=function(){this.context.clearRect(0,0,this.width,this.height)};a.prototype.render=function(){var f=this._textCache;for(var o in f){if(d.call(f,o)){var n=this.getTextLayer(o),g=f[o];n.hide();for(var m in g){if(d.call(g,m)){var h=g[m];for(var p in h){if(d.call(h,p)){var k=h[p].positions;for(var j=0,l;l=k[j];j++){if(l.active){if(!l.rendered){n.append(l.element);l.rendered=true}}else{k.splice(j--,1);if(l.rendered){l.element.detach()}}}if(k.length==0){delete h[p]}}}}}n.show()}}};a.prototype.getTextLayer=function(g){var f=this.text[g];if(f==null){if(this.textContainer==null){this.textContainer=e("
").css({position:"absolute",top:0,left:0,bottom:0,right:0,"font-size":"smaller",color:"#545454"}).insertAfter(this.element)}f=this.text[g]=e("
").addClass(g).css({position:"absolute",top:0,left:0,bottom:0,right:0}).appendTo(this.textContainer)}return f};a.prototype.getTextInfo=function(m,o,j,k,g){var n,f,i,h;o=""+o;if(typeof j==="object"){n=j.style+" "+j.variant+" "+j.weight+" "+j.size+"px/"+j.lineHeight+"px "+j.family}else{n=j}f=this._textCache[m];if(f==null){f=this._textCache[m]={}}i=f[n];if(i==null){i=f[n]={}}h=i[o];if(h==null){var l=e("
").html(o).css({position:"absolute","max-width":g,top:-9999}).appendTo(this.getTextLayer(m));if(typeof j==="object"){l.css({font:n,color:j.color})}else{if(typeof j==="string"){l.addClass(j)}}h=i[o]={width:l.outerWidth(true),height:l.outerHeight(true),element:l,positions:[]};l.detach()}return h};a.prototype.addText=function(o,r,p,s,h,j,f,n,q){var g=this.getTextInfo(o,s,h,j,f),l=g.positions;if(n=="center"){r-=g.width/2}else{if(n=="right"){r-=g.width}}if(q=="middle"){p-=g.height/2}else{if(q=="bottom"){p-=g.height}}for(var k=0,m;m=l[k];k++){if(m.x==r&&m.y==p){m.active=true;return}}m={active:true,rendered:false,element:l.length?g.element.clone():g.element,x:r,y:p};l.push(m);m.element.css({top:Math.round(p),left:Math.round(r),"text-align":n})};a.prototype.removeText=function(o,q,p,s,h,j){if(s==null){var f=this._textCache[o];if(f!=null){for(var n in f){if(d.call(f,n)){var g=f[n];for(var r in g){if(d.call(g,r)){var l=g[r].positions;for(var k=0,m;m=l[k];k++){m.active=false}}}}}}}else{var l=this.getTextInfo(o,s,h,j).positions;for(var k=0,m;m=l[k];k++){if(m.x==q&&m.y==p){m.active=false}}}};function c(Q,A,C,g){var t=[],L={colors:["#edc240","#afd8f8","#cb4b4b","#4da74d","#9440ed"],legend:{show:true,noColumns:1,labelFormatter:null,labelBoxBorderColor:"#ccc",container:null,position:"ne",margin:5,backgroundColor:null,backgroundOpacity:0.85,sorted:null},xaxis:{show:null,position:"bottom",mode:null,font:null,color:null,tickColor:null,transform:null,inverseTransform:null,min:null,max:null,autoscaleMargin:null,ticks:null,tickFormatter:null,labelWidth:null,labelHeight:null,reserveSpace:null,tickLength:null,alignTicksWithAxis:null,tickDecimals:null,tickSize:null,minTickSize:null},yaxis:{autoscaleMargin:0.02,position:"left"},xaxes:[],yaxes:[],series:{points:{show:false,radius:3,lineWidth:2,fill:true,fillColor:"#ffffff",symbol:"circle"},lines:{lineWidth:2,fill:false,fillColor:null,steps:false},bars:{show:false,lineWidth:2,barWidth:1,fill:true,fillColor:null,align:"left",horizontal:false,zero:true},shadowSize:3,highlightColor:null},grid:{show:true,aboveData:false,color:"#545454",backgroundColor:null,borderColor:null,tickColor:null,margin:0,labelMargin:5,axisMargin:8,borderWidth:2,minBorderMargin:null,markings:null,markingsColor:"#f4f4f4",markingsLineWidth:2,clickable:false,hoverable:false,autoHighlight:true,mouseActiveRadius:10},interaction:{redrawOverlayInterval:1000/60},hooks:{}},ac=null,al=null,am=null,D=null,aw=null,ao=[],W=[],J={left:0,right:0,top:0,bottom:0},k=0,ad=0,p={processOptions:[],processRawData:[],processDatapoints:[],processOffset:[],drawBackground:[],drawSeries:[],draw:[],bindEvents:[],drawOverlay:[],shutdown:[]},h=this;h.setData=K;h.setupGrid=O;h.draw=au;h.getPlaceholder=function(){return Q};h.getCanvas=function(){return ac.element};h.getPlotOffset=function(){return J};h.width=function(){return k};h.height=function(){return ad};h.offset=function(){var ay=am.offset();ay.left+=J.left;ay.top+=J.top;return ay};h.getData=function(){return t};h.getAxes=function(){var az={},ay;e.each(ao.concat(W),function(aA,aB){if(aB){az[aB.direction+(aB.n!=1?aB.n:"")+"axis"]=aB}});return az};h.getXAxes=function(){return ao};h.getYAxes=function(){return W};h.c2p=Y;h.p2c=R;h.getOptions=function(){return L};h.highlight=an;h.unhighlight=ah;h.triggerRedrawOverlay=X;h.pointOffset=function(ay){return{left:parseInt(ao[x(ay,"x")-1].p2c(+ay.x)+J.left,10),top:parseInt(W[x(ay,"y")-1].p2c(+ay.y)+J.top,10)}};h.shutdown=o;h.destroy=function(){o();Q.removeData("plot").empty();t=[];L=null;ac=null;al=null;am=null;D=null;aw=null;ao=[];W=[];p=null;ag=[];h=null};h.resize=function(){var az=Q.width(),ay=Q.height();ac.resize(az,ay);al.resize(az,ay)};h.hooks=p;H(h);aa(C);ax();K(A);O();au();ar();function F(aA,ay){ay=[h].concat(ay);for(var az=0;azaA){aA=aG}}}if(aJ<=aA){aJ=aA+1}var aF,ay=[],aE=L.colors,aD=aE.length,az=0;for(aB=0;aB=0){if(az<0.5){az=-az-0.2}else{az=0}}else{az=-az}}ay[aB]=aF.scale("rgb",1+az)}var aC=0,aK;for(aB=0;aBa2.datamax&&a0!=ay){a2.datamax=a0}}e.each(j(),function(a0,a1){a1.datamin=aM;a1.datamax=aG;a1.used=false});for(aT=0;aT0&&aS[aQ-aN]!=null&&aS[aQ-aN]!=aS[aQ]&&aS[aQ-aN+1]!=aS[aQ+1]){for(aL=0;aLaK){aK=aZ}}if(aW.y){if(aZaU){aU=aZ}}}}if(aH.bars.show){var aX;switch(aH.bars.align){case"left":aX=0;break;case"right":aX=-aH.bars.barWidth;break;default:aX=-aH.bars.barWidth/2}if(aH.bars.horizontal){aP+=aX;aU+=aX+aH.bars.barWidth}else{aI+=aX;aK+=aX+aH.bars.barWidth}}aC(aH.xaxis,aI,aK);aC(aH.yaxis,aP,aU)}e.each(j(),function(a0,a1){if(a1.datamin==aM){a1.datamin=null}if(a1.datamax==aG){a1.datamax=null}})}function ax(){Q.css("padding",0).children().filter(function(){return !e(this).hasClass("flot-overlay")&&!e(this).hasClass("flot-base")}).remove();if(Q.css("position")=="static"){Q.css("position","relative")}ac=new a("flot-base",Q);al=new a("flot-overlay",Q);D=ac.context;aw=al.context;am=e(al.element).unbind();var ay=Q.data("plot");if(ay){ay.shutdown();al.clear()}Q.data("plot",h)}function ar(){if(L.grid.hoverable){am.mousemove(f);am.bind("mouseleave",P)}if(L.grid.clickable){am.click(I)}F(p.bindEvents,[am])}function o(){if(l){clearTimeout(l)}am.unbind("mousemove",f);am.unbind("mouseleave",P);am.unbind("click",I);F(p.shutdown,[am])}function n(aD){function az(aE){return aE}var aC,ay,aA=aD.options.transform||az,aB=aD.options.inverseTransform;if(aD.direction=="x"){aC=aD.scale=k/Math.abs(aA(aD.max)-aA(aD.min));ay=Math.min(aA(aD.max),aA(aD.min))}else{aC=aD.scale=ad/Math.abs(aA(aD.max)-aA(aD.min));aC=-aC;ay=Math.max(aA(aD.max),aA(aD.min))}if(aA==az){aD.p2c=function(aE){return(aE-ay)*aC}}else{aD.p2c=function(aE){return(aA(aE)-ay)*aC}}if(!aB){aD.c2p=function(aE){return ay+aE/aC}}else{aD.c2p=function(aE){return aB(ay+aE/aC)}}}function Z(aB){var ay=aB.options,aH=aB.ticks||[],aG=ay.labelWidth||0,aC=ay.labelHeight||0,aI=aG||(aB.direction=="x"?Math.floor(ac.width/(aH.length||1)):null),aE=aB.direction+"Axis "+aB.direction+aB.n+"Axis",aF="flot-"+aB.direction+"-axis flot-"+aB.direction+aB.n+"-axis "+aE,aA=ay.font||"flot-tick-label tickLabel";for(var aD=0;aD=0;--aA){E(ay[aA])}B();e.each(ay,function(aE,aF){ab(aF)})}k=ac.width-J.left-J.right;ad=ac.height-J.bottom-J.top;e.each(aC,function(aE,aF){n(aF)});if(aD){at()}av()}function m(aB){var aC=aB.options,aA=+(aC.min!=null?aC.min:aB.datamin),ay=+(aC.max!=null?aC.max:aB.datamax),aE=ay-aA;if(aE==0){var az=ay==0?1:0.01;if(aC.min==null){aA-=az}if(aC.max==null||aC.min!=null){ay+=az}}else{var aD=aC.autoscaleMargin;if(aD!=null){if(aC.min==null){aA-=aE*aD;if(aA<0&&aB.datamin!=null&&aB.datamin>=0){aA=0}}if(aC.max==null){ay+=aE*aD;if(ay>0&&aB.datamax!=null&&aB.datamax<=0){ay=0}}}}aB.min=aA;aB.max=ay}function aq(aD){var az=aD.options;var aC;if(typeof az.ticks=="number"&&az.ticks>0){aC=az.ticks}else{aC=0.3*Math.sqrt(aD.direction=="x"?ac.width:ac.height)}var aI=(aD.max-aD.min)/aC,aE=-Math.floor(Math.log(aI)/Math.LN10),aB=az.tickDecimals;if(aB!=null&&aE>aB){aE=aB}var ay=Math.pow(10,-aE),aA=aI/ay,aK;if(aA<1.5){aK=1}else{if(aA<3){aK=2;if(aA>2.25&&(aB==null||aE+1<=aB)){aK=2.5;++aE}}else{if(aA<7.5){aK=5}else{aK=10}}}aK*=ay;if(az.minTickSize!=null&&aK0){if(az.min==null){aD.min=Math.min(aD.min,aJ[0])}if(az.max==null&&aJ.length>1){aD.max=Math.max(aD.max,aJ[aJ.length-1])}}aD.tickGenerator=function(aN){var aO=[],aL,aM;for(aM=0;aM1&&/\..*0$/.test((aG[1]-aG[0]).toFixed(aH)))){aD.tickDecimals=aH}}}}}function V(aC){var aE=aC.options.ticks,aD=[];if(aE==null||(typeof aE=="number"&&aE>0)){aD=aC.tickGenerator(aC)}else{if(aE){if(e.isFunction(aE)){aD=aE(aC)}else{aD=aE}}}var aB,ay;aC.ticks=[];for(aB=0;aB1){az=aA[1]}}else{ay=+aA}if(az==null){az=aC.tickFormatter(ay,aC)}if(!isNaN(ay)){aC.ticks.push({v:ay,label:az})}}}function u(ay,az){if(ay.options.autoscaleMargin&&az.length>0){if(ay.options.min==null){ay.min=Math.min(ay.min,az[0].v)}if(ay.options.max==null&&az.length>1){ay.max=Math.max(ay.max,az[az.length-1].v)}}}function au(){ac.clear();F(p.drawBackground,[D]);var az=L.grid;if(az.show&&az.backgroundColor){r()}if(az.show&&!az.aboveData){w()}for(var ay=0;ayaF){var aA=aE;aE=aF;aF=aA}return{from:aE,to:aF,axis:az}}function r(){D.save();D.translate(J.left,J.top);D.fillStyle=v(L.grid.backgroundColor,ad,0,"rgba(255, 255, 255, 0)");D.fillRect(0,0,k,ad);D.restore()}function w(){var aO,aN,aR,aA;D.save();D.translate(J.left,J.top);var aB=L.grid.markings;if(aB){if(e.isFunction(aB)){aN=h.getAxes();aN.xmin=aN.xaxis.min;aN.xmax=aN.xaxis.max;aN.ymin=aN.yaxis.min;aN.ymax=aN.yaxis.max;aB=aB(aN)}for(aO=0;aOaC.axis.max||aG.toaG.axis.max){continue}aC.from=Math.max(aC.from,aC.axis.min);aC.to=Math.min(aC.to,aC.axis.max);aG.from=Math.max(aG.from,aG.axis.min);aG.to=Math.min(aG.to,aG.axis.max);var aD=aC.from===aC.to,aJ=aG.from===aG.to;if(aD&&aJ){continue}aC.from=Math.floor(aC.axis.p2c(aC.from));aC.to=Math.floor(aC.axis.p2c(aC.to));aG.from=Math.floor(aG.axis.p2c(aG.from));aG.to=Math.floor(aG.axis.p2c(aG.to));if(aD||aJ){var ay=aL.lineWidth||L.grid.markingsLineWidth,aP=ay%2?0.5:0;D.beginPath();D.strokeStyle=aL.color||L.grid.markingsColor;D.lineWidth=ay;if(aD){D.moveTo(aC.to+aP,aG.from);D.lineTo(aC.to+aP,aG.to)}else{D.moveTo(aC.from,aG.to+aP);D.lineTo(aC.to,aG.to+aP)}D.stroke()}else{D.fillStyle=aL.color||L.grid.markingsColor;D.fillRect(aC.from,aG.to,aC.to-aC.from,aG.from-aG.to)}}}aN=j();aR=L.grid.borderWidth;for(var aM=0;aMaz.max||(aK=="full"&&((typeof aR=="object"&&aR[az.position]>0)||aR>0)&&(aI==az.min||aI==az.max))){continue}if(az.direction=="x"){aF=az.p2c(aI);aS=aK=="full"?-ad:aK;if(az.position=="top"){aS=-aS}}else{aE=az.p2c(aI);aQ=aK=="full"?-k:aK;if(az.position=="left"){aQ=-aQ}}if(D.lineWidth==1){if(az.direction=="x"){aF=Math.floor(aF)+0.5}else{aE=Math.floor(aE)+0.5}}D.moveTo(aF,aE);D.lineTo(aF+aQ,aE+aS)}D.stroke()}if(aR){aA=L.grid.borderColor;if(typeof aR=="object"||typeof aA=="object"){if(typeof aR!=="object"){aR={top:aR,right:aR,bottom:aR,left:aR}}if(typeof aA!=="object"){aA={top:aA,right:aA,bottom:aA,left:aA}}if(aR.top>0){D.strokeStyle=aA.top;D.lineWidth=aR.top;D.beginPath();D.moveTo(0-aR.left,0-aR.top/2);D.lineTo(k,0-aR.top/2);D.stroke()}if(aR.right>0){D.strokeStyle=aA.right;D.lineWidth=aR.right;D.beginPath();D.moveTo(k+aR.right/2,0-aR.top);D.lineTo(k+aR.right/2,ad);D.stroke()}if(aR.bottom>0){D.strokeStyle=aA.bottom;D.lineWidth=aR.bottom;D.beginPath();D.moveTo(k+aR.right,ad+aR.bottom/2);D.lineTo(0,ad+aR.bottom/2);D.stroke()}if(aR.left>0){D.strokeStyle=aA.left;D.lineWidth=aR.left;D.beginPath();D.moveTo(0-aR.left/2,ad+aR.bottom);D.lineTo(0-aR.left/2,0);D.stroke()}}else{D.lineWidth=aR;D.strokeStyle=L.grid.borderColor;D.strokeRect(-aR/2,-aR/2,k+aR,ad+aR)}}D.restore()}function at(){e.each(j(),function(aJ,az){var aC=az.box,aB=az.direction+"Axis "+az.direction+az.n+"Axis",aF="flot-"+az.direction+"-axis flot-"+az.direction+az.n+"-axis "+aB,ay=az.options.font||"flot-tick-label tickLabel",aD,aI,aG,aE,aH;ac.removeText(aF);if(!az.show||az.ticks.length==0){return}for(var aA=0;aAaz.max){continue}if(az.direction=="x"){aE="center";aI=J.left+az.p2c(aD.v);if(az.position=="bottom"){aG=aC.top+aC.padding}else{aG=aC.top+aC.height-aC.padding;aH="bottom"}}else{aH="middle";aG=J.top+az.p2c(aD.v);if(az.position=="left"){aI=aC.left+aC.width-aC.padding;aE="right"}else{aI=aC.left+aC.padding}}ac.addText(aF,aI,aG,aD.label,ay,null,null,aE,aH)}})}function aj(ay){if(ay.lines.show){G(ay)}if(ay.bars.show){T(ay)}if(ay.points.show){U(ay)}}function G(aB){function aA(aM,aN,aF,aR,aQ){var aS=aM.points,aG=aM.pointsize,aK=null,aJ=null;D.beginPath();for(var aL=aG;aL=aO&&aP>aQ.max){if(aO>aQ.max){continue}aI=(aQ.max-aP)/(aO-aP)*(aH-aI)+aI;aP=aQ.max}else{if(aO>=aP&&aO>aQ.max){if(aP>aQ.max){continue}aH=(aQ.max-aP)/(aO-aP)*(aH-aI)+aI;aO=aQ.max}}if(aI<=aH&&aI=aH&&aI>aR.max){if(aH>aR.max){continue}aP=(aR.max-aI)/(aH-aI)*(aO-aP)+aP;aI=aR.max}else{if(aH>=aI&&aH>aR.max){if(aI>aR.max){continue}aO=(aR.max-aI)/(aH-aI)*(aO-aP)+aP;aH=aR.max}}if(aI!=aK||aP!=aJ){D.moveTo(aR.p2c(aI)+aN,aQ.p2c(aP)+aF)}aK=aH;aJ=aO;D.lineTo(aR.p2c(aH)+aN,aQ.p2c(aO)+aF)}D.stroke()}function aC(aF,aN,aM){var aT=aF.points,aS=aF.pointsize,aK=Math.min(Math.max(0,aM.min),aM.max),aU=0,aR,aQ=false,aJ=1,aI=0,aO=0;while(true){if(aS>0&&aU>aT.length+aS){break}aU+=aS;var aW=aT[aU-aS],aH=aT[aU-aS+aJ],aV=aT[aU],aG=aT[aU+aJ];if(aQ){if(aS>0&&aW!=null&&aV==null){aO=aU;aS=-aS;aJ=2;continue}if(aS<0&&aU==aI+aS){D.fill();aQ=false;aS=-aS;aJ=1;aU=aI=aO+aS;continue}}if(aW==null||aV==null){continue}if(aW<=aV&&aW=aV&&aW>aN.max){if(aV>aN.max){continue}aH=(aN.max-aW)/(aV-aW)*(aG-aH)+aH;aW=aN.max}else{if(aV>=aW&&aV>aN.max){if(aW>aN.max){continue}aG=(aN.max-aW)/(aV-aW)*(aG-aH)+aH;aV=aN.max}}if(!aQ){D.beginPath();D.moveTo(aN.p2c(aW),aM.p2c(aK));aQ=true}if(aH>=aM.max&&aG>=aM.max){D.lineTo(aN.p2c(aW),aM.p2c(aM.max));D.lineTo(aN.p2c(aV),aM.p2c(aM.max));continue}else{if(aH<=aM.min&&aG<=aM.min){D.lineTo(aN.p2c(aW),aM.p2c(aM.min));D.lineTo(aN.p2c(aV),aM.p2c(aM.min));continue}}var aL=aW,aP=aV;if(aH<=aG&&aH=aM.min){aW=(aM.min-aH)/(aG-aH)*(aV-aW)+aW;aH=aM.min}else{if(aG<=aH&&aG=aM.min){aV=(aM.min-aH)/(aG-aH)*(aV-aW)+aW;aG=aM.min}}if(aH>=aG&&aH>aM.max&&aG<=aM.max){aW=(aM.max-aH)/(aG-aH)*(aV-aW)+aW;aH=aM.max}else{if(aG>=aH&&aG>aM.max&&aH<=aM.max){aV=(aM.max-aH)/(aG-aH)*(aV-aW)+aW;aG=aM.max}}if(aW!=aL){D.lineTo(aN.p2c(aL),aM.p2c(aH))}D.lineTo(aN.p2c(aW),aM.p2c(aH));D.lineTo(aN.p2c(aV),aM.p2c(aG));if(aV!=aP){D.lineTo(aN.p2c(aV),aM.p2c(aG));D.lineTo(aN.p2c(aP),aM.p2c(aG))}}}D.save();D.translate(J.left,J.top);D.lineJoin="round";var aD=aB.lines.lineWidth,ay=aB.shadowSize;if(aD>0&&ay>0){D.lineWidth=ay;D.strokeStyle="rgba(0,0,0,0.1)";var aE=Math.PI/18;aA(aB.datapoints,Math.sin(aE)*(aD/2+ay/2),Math.cos(aE)*(aD/2+ay/2),aB.xaxis,aB.yaxis);D.lineWidth=ay/2;aA(aB.datapoints,Math.sin(aE)*(aD/2+ay/4),Math.cos(aE)*(aD/2+ay/4),aB.xaxis,aB.yaxis)}D.lineWidth=aD;D.strokeStyle=aB.color;var az=z(aB.lines,aB.color,0,ad);if(az){D.fillStyle=az;aC(aB.datapoints,aB.xaxis,aB.yaxis)}if(aD>0){aA(aB.datapoints,0,0,aB.xaxis,aB.yaxis)}D.restore()}function U(aB){function aE(aK,aJ,aR,aH,aP,aQ,aN,aG){var aO=aK.points,aF=aK.pointsize;for(var aI=0;aIaQ.max||aLaN.max){continue}D.beginPath();aM=aQ.p2c(aM);aL=aN.p2c(aL)+aH;if(aG=="circle"){D.arc(aM,aL,aJ,0,aP?Math.PI:Math.PI*2,false)}else{aG(D,aM,aL,aJ,aP)}D.closePath();if(aR){D.fillStyle=aR;D.fill()}D.stroke()}}D.save();D.translate(J.left,J.top);var aD=aB.points.lineWidth,az=aB.shadowSize,ay=aB.points.radius,aC=aB.points.symbol;if(aD==0){aD=0.0001}if(aD>0&&az>0){var aA=az/2;D.lineWidth=aA;D.strokeStyle="rgba(0,0,0,0.1)";aE(aB.datapoints,ay,null,aA+aA/2,true,aB.xaxis,aB.yaxis,aC);D.strokeStyle="rgba(0,0,0,0.2)";aE(aB.datapoints,ay,null,aA/2,true,aB.xaxis,aB.yaxis,aC)}D.lineWidth=aD;D.strokeStyle=aB.color;aE(aB.datapoints,ay,z(aB.points,aB.color),0,false,aB.xaxis,aB.yaxis,aC);D.restore()}function ak(aJ,aI,aR,aE,aM,aA,aH,aG,aQ,aN,az){var aB,aP,aF,aL,aC,ay,aK,aD,aO;if(aN){aD=ay=aK=true;aC=false;aB=aR;aP=aJ;aL=aI+aE;aF=aI+aM;if(aPaH.max||aLaG.max){return}if(aBaH.max){aP=aH.max;ay=false}if(aFaG.max){aL=aG.max;aK=false}aB=aH.p2c(aB);aF=aG.p2c(aF);aP=aH.p2c(aP);aL=aG.p2c(aL);if(aA){aQ.fillStyle=aA(aF,aL);aQ.fillRect(aB,aL,aP-aB,aF-aL)}if(az>0&&(aC||ay||aK||aD)){aQ.beginPath();aQ.moveTo(aB,aF);if(aC){aQ.lineTo(aB,aL)}else{aQ.moveTo(aB,aL)}if(aK){aQ.lineTo(aP,aL)}else{aQ.moveTo(aP,aL)}if(ay){aQ.lineTo(aP,aF)}else{aQ.moveTo(aP,aF)}if(aD){aQ.lineTo(aB,aF)}else{aQ.moveTo(aB,aF)}aQ.stroke()}}function T(aA){function az(aF,aE,aH,aG,aJ,aI){var aK=aF.points,aC=aF.pointsize;for(var aD=0;aD")}aG.push("");aE=true}aG.push(''+aK.label+" ")}if(aE){aG.push(" ")}if(aG.length==0){return}var aL='";if(L.legend.container!=null){e(L.legend.container).html(aL)}else{var aH="",az=L.legend.position,aA=L.legend.margin;if(aA[0]==null){aA=[aA,aA]}if(az.charAt(0)=="n"){aH+="top:"+(aA[1]+J.top)+"px;"}else{if(az.charAt(0)=="s"){aH+="bottom:"+(aA[1]+J.bottom)+"px;"}}if(az.charAt(1)=="e"){aH+="right:"+(aA[0]+J.right)+"px;"}else{if(az.charAt(1)=="w"){aH+="left:"+(aA[0]+J.left)+"px;"}}var aJ=e(''+aL.replace('style="','style="position:absolute;'+aH+";")+"
").appendTo(Q);if(L.legend.backgroundOpacity!=0){var aF=L.legend.backgroundColor;if(aF==null){aF=L.grid.backgroundColor;if(aF&&typeof aF=="string"){aF=e.color.parse(aF)}else{aF=e.color.extract(aJ,"background-color")}aF.a=1;aF=aF.toString()}var ay=aJ.children();e('
').prependTo(aJ).css("opacity",L.legend.backgroundOpacity)}}}var ag=[],l=null;function ap(aF,aD,aA){var aL=L.grid.mouseActiveRadius,aX=aL*aL+1,aV=null,aO=false,aT,aR,aQ;for(aT=t.length-1;aT>=0;--aT){if(!aA(t[aT])){continue}var aM=t[aT],aE=aM.xaxis,aC=aM.yaxis,aS=aM.datapoints.points,aN=aE.c2p(aF),aK=aC.c2p(aD),az=aL/aE.scale,ay=aL/aC.scale;aQ=aM.datapoints.pointsize;if(aE.options.inverseTransform){az=Number.MAX_VALUE}if(aC.options.inverseTransform){ay=Number.MAX_VALUE}if(aM.lines.show||aM.points.show){for(aR=0;aRaz||aH-aN<-az||aG-aK>ay||aG-aK<-ay){continue}var aJ=Math.abs(aE.p2c(aH)-aF),aI=Math.abs(aC.p2c(aG)-aD),aP=aJ*aJ+aI*aI;if(aP=Math.min(aW,aH)&&aK>=aG+aB&&aK<=aG+aU):(aN>=aH+aB&&aN<=aH+aU&&aK>=Math.min(aW,aG)&&aK<=Math.max(aW,aG))){aV=[aT,aR/aQ]}}}}if(aV){aT=aV[0];aR=aV[1];aQ=t[aT].datapoints.pointsize;return{datapoint:t[aT].datapoints.points.slice(aR*aQ,(aR+1)*aQ),dataIndex:aR,series:t[aT],seriesIndex:aT}}return null}function f(ay){if(L.grid.hoverable){i("plothover",ay,function(az){return az.hoverable!=false})}}function P(ay){if(L.grid.hoverable){i("plothover",ay,function(az){return false})}}function I(ay){i("plotclick",ay,function(az){return az.clickable!=false})}function i(az,ay,aA){var aB=am.offset(),aE=ay.pageX-aB.left-J.left,aC=ay.pageY-aB.top-J.top,aG=Y({left:aE,top:aC});aG.pageX=ay.pageX;aG.pageY=ay.pageY;var aH=ap(aE,aC,aA);if(aH){aH.pageX=parseInt(aH.series.xaxis.p2c(aH.datapoint[0])+aB.left+J.left,10);aH.pageY=parseInt(aH.series.yaxis.p2c(aH.datapoint[1])+aB.top+J.top,10)}if(L.grid.autoHighlight){for(var aD=0;aDaF.max||aAaD.max){return}var aB=ay.points.radius+ay.points.lineWidth/2;aw.lineWidth=aB;aw.strokeStyle=aG;var az=1.5*aB;aC=aF.p2c(aC);aA=aD.p2c(aA);aw.beginPath();if(ay.points.symbol=="circle"){aw.arc(aC,aA,az,0,2*Math.PI,false)}else{ay.points.symbol(aw,aC,aA,az,false)}aw.closePath();aw.stroke()}function ai(aB,ay){var aC=(typeof aB.highlightColor==="string")?aB.highlightColor:e.color.parse(aB.color).scale("a",0.5).toString(),aA=aC,az;switch(aB.bars.align){case"left":az=0;break;case"right":az=-aB.bars.barWidth;break;default:az=-aB.bars.barWidth/2}aw.lineWidth=aB.bars.lineWidth;aw.strokeStyle=aC;ak(ay[0],ay[1],ay[2]||0,az,az+aB.bars.barWidth,function(){return aA},aB.xaxis,aB.yaxis,aw,aB.bars.horizontal,aB.bars.lineWidth)}function v(aG,ay,aE,az){if(typeof aG=="string"){return aG}else{var aF=D.createLinearGradient(0,aE,0,ay);for(var aB=0,aA=aG.colors.length;aB12){o=v-12}else{if(v==0){o=12}else{o=v}}for(var p=0;p=u){break}}var I=G[B][0];var E=G[B][1];if(E=="year"){if(p.minTickSize!=null&&p.minTickSize[1]=="year"){I=Math.floor(p.minTickSize[0])}else{var s=Math.pow(10,Math.floor(Math.log(x.delta/c.year)/Math.LN10));var r=(x.delta/c.year)/s;if(r<1.5){I=1}else{if(r<3){I=2}else{if(r<7.5){I=5}else{I=10}}}I*=s}if(I<1){I=1}}x.tickSize=p.tickSize||[I,E];var A=x.tickSize[0];E=x.tickSize[1];var w=A*c[E];if(E=="second"){C.setSeconds(g(C.getSeconds(),A))}else{if(E=="minute"){C.setMinutes(g(C.getMinutes(),A))}else{if(E=="hour"){C.setHours(g(C.getHours(),A))}else{if(E=="month"){C.setMonth(g(C.getMonth(),A))}else{if(E=="quarter"){C.setMonth(3*g(C.getMonth()/3,A))}else{if(E=="year"){C.setFullYear(g(C.getFullYear(),A))}}}}}}C.setMilliseconds(0);if(w>=c.minute){C.setSeconds(0)}if(w>=c.hour){C.setMinutes(0)}if(w>=c.day){C.setHours(0)}if(w>=c.day*4){C.setDate(1)}if(w>=c.month*2){C.setMonth(g(C.getMonth(),3))}if(w>=c.quarter*2){C.setMonth(g(C.getMonth(),6))}if(w>=c.year){C.setMonth(0)}var H=0;var F=Number.NaN;var y;do{y=F;F=C.getTime();D.push(F);if(E=="month"||E=="quarter"){if(A<1){C.setDate(1);var t=C.getTime();C.setMonth(C.getMonth()+(E=="quarter"?3:1));var z=C.getTime();C.setTime(F+H*c.hour+(z-t)*A);H=C.getHours();C.setHours(0)}else{C.setMonth(C.getMonth()+A*(E=="quarter"?3:1))}}else{if(E=="year"){C.setFullYear(C.getFullYear()+A)}else{C.setTime(F+w)}}}while(F0&&h.which!=l.which||a(h.target).is(l.not))return;switch(h.type){case"mousedown":return a.extend(l,a(j).offset(),{elem:j,target:h.target,pageX:h.pageX,pageY:h.pageY}),b.add(document,"mousemove mouseup",e,l),i(j,!1),d.dragging=null,!1;case!d.dragging&&"mousemove":if(g(h.pageX-l.pageX)+g(h.pageY-l.pageY) max) {
+ // make sure min < max
+ var tmp = min;
+ min = max;
+ max = tmp;
+ }
+
+ //Check that we are in panRange
+ if (pr) {
+ if (pr[0] != null && min < pr[0]) {
+ min = pr[0];
+ }
+ if (pr[1] != null && max > pr[1]) {
+ max = pr[1];
+ }
+ }
+
+ var range = max - min;
+ if (zr &&
+ ((zr[0] != null && range < zr[0] && amount >1) ||
+ (zr[1] != null && range > zr[1] && amount <1)))
+ return;
+
+ opts.min = min;
+ opts.max = max;
+ });
+
+ plot.setupGrid();
+ plot.draw();
+
+ if (!args.preventEvent)
+ plot.getPlaceholder().trigger("plotzoom", [ plot, args ]);
+ };
+
+ plot.pan = function (args) {
+ var delta = {
+ x: +args.left,
+ y: +args.top
+ };
+
+ if (isNaN(delta.x))
+ delta.x = 0;
+ if (isNaN(delta.y))
+ delta.y = 0;
+
+ $.each(plot.getAxes(), function (_, axis) {
+ var opts = axis.options,
+ min, max, d = delta[axis.direction];
+
+ min = axis.c2p(axis.p2c(axis.min) + d),
+ max = axis.c2p(axis.p2c(axis.max) + d);
+
+ var pr = opts.panRange;
+ if (pr === false) // no panning on this axis
+ return;
+
+ if (pr) {
+ // check whether we hit the wall
+ if (pr[0] != null && pr[0] > min) {
+ d = pr[0] - min;
+ min += d;
+ max += d;
+ }
+
+ if (pr[1] != null && pr[1] < max) {
+ d = pr[1] - max;
+ min += d;
+ max += d;
+ }
+ }
+
+ opts.min = min;
+ opts.max = max;
+ });
+
+ plot.setupGrid();
+ plot.draw();
+
+ if (!args.preventEvent)
+ plot.getPlaceholder().trigger("plotpan", [ plot, args ]);
+ };
+
+ function shutdown(plot, eventHolder) {
+ eventHolder.unbind(plot.getOptions().zoom.trigger, onZoomClick);
+ eventHolder.unbind("mousewheel", onMouseWheel);
+ eventHolder.unbind("dragstart", onDragStart);
+ eventHolder.unbind("drag", onDrag);
+ eventHolder.unbind("dragend", onDragEnd);
+ if (panTimeout)
+ clearTimeout(panTimeout);
+ }
+
+ plot.hooks.bindEvents.push(bindEvents);
+ plot.hooks.shutdown.push(shutdown);
+ }
+
+ $.plot.plugins.push({
+ init: init,
+ options: options,
+ name: 'navigate',
+ version: '1.3'
+ });
+})(jQuery);
diff --git a/djangoproject/static/js/lib/jquery-flot/jquery.flot.pie.js b/djangoproject/static/js/lib/jquery-flot/jquery.flot.pie.js
new file mode 100644
index 000000000..9c19db998
--- /dev/null
+++ b/djangoproject/static/js/lib/jquery-flot/jquery.flot.pie.js
@@ -0,0 +1,820 @@
+/* Flot plugin for rendering pie charts.
+
+Copyright (c) 2007-2014 IOLA and Ole Laursen.
+Licensed under the MIT license.
+
+The plugin assumes that each series has a single data value, and that each
+value is a positive integer or zero. Negative numbers don't make sense for a
+pie chart, and have unpredictable results. The values do NOT need to be
+passed in as percentages; the plugin will calculate the total and per-slice
+percentages internally.
+
+* Created by Brian Medendorp
+
+* Updated with contributions from btburnett3, Anthony Aragues and Xavi Ivars
+
+The plugin supports these options:
+
+ series: {
+ pie: {
+ show: true/false
+ radius: 0-1 for percentage of fullsize, or a specified pixel length, or 'auto'
+ innerRadius: 0-1 for percentage of fullsize or a specified pixel length, for creating a donut effect
+ startAngle: 0-2 factor of PI used for starting angle (in radians) i.e 3/2 starts at the top, 0 and 2 have the same result
+ tilt: 0-1 for percentage to tilt the pie, where 1 is no tilt, and 0 is completely flat (nothing will show)
+ offset: {
+ top: integer value to move the pie up or down
+ left: integer value to move the pie left or right, or 'auto'
+ },
+ stroke: {
+ color: any hexidecimal color value (other formats may or may not work, so best to stick with something like '#FFF')
+ width: integer pixel width of the stroke
+ },
+ label: {
+ show: true/false, or 'auto'
+ formatter: a user-defined function that modifies the text/style of the label text
+ radius: 0-1 for percentage of fullsize, or a specified pixel length
+ background: {
+ color: any hexidecimal color value (other formats may or may not work, so best to stick with something like '#000')
+ opacity: 0-1
+ },
+ threshold: 0-1 for the percentage value at which to hide labels (if they're too small)
+ },
+ combine: {
+ threshold: 0-1 for the percentage value at which to combine slices (if they're too small)
+ color: any hexidecimal color value (other formats may or may not work, so best to stick with something like '#CCC'), if null, the plugin will automatically use the color of the first slice to be combined
+ label: any text value of what the combined slice should be labeled
+ }
+ highlight: {
+ opacity: 0-1
+ }
+ }
+ }
+
+More detail and specific examples can be found in the included HTML file.
+
+*/
+
+(function($) {
+
+ // Maximum redraw attempts when fitting labels within the plot
+
+ var REDRAW_ATTEMPTS = 10;
+
+ // Factor by which to shrink the pie when fitting labels within the plot
+
+ var REDRAW_SHRINK = 0.95;
+
+ function init(plot) {
+
+ var canvas = null,
+ target = null,
+ options = null,
+ maxRadius = null,
+ centerLeft = null,
+ centerTop = null,
+ processed = false,
+ ctx = null;
+
+ // interactive variables
+
+ var highlights = [];
+
+ // add hook to determine if pie plugin in enabled, and then perform necessary operations
+
+ plot.hooks.processOptions.push(function(plot, options) {
+ if (options.series.pie.show) {
+
+ options.grid.show = false;
+
+ // set labels.show
+
+ if (options.series.pie.label.show == "auto") {
+ if (options.legend.show) {
+ options.series.pie.label.show = false;
+ } else {
+ options.series.pie.label.show = true;
+ }
+ }
+
+ // set radius
+
+ if (options.series.pie.radius == "auto") {
+ if (options.series.pie.label.show) {
+ options.series.pie.radius = 3/4;
+ } else {
+ options.series.pie.radius = 1;
+ }
+ }
+
+ // ensure sane tilt
+
+ if (options.series.pie.tilt > 1) {
+ options.series.pie.tilt = 1;
+ } else if (options.series.pie.tilt < 0) {
+ options.series.pie.tilt = 0;
+ }
+ }
+ });
+
+ plot.hooks.bindEvents.push(function(plot, eventHolder) {
+ var options = plot.getOptions();
+ if (options.series.pie.show) {
+ if (options.grid.hoverable) {
+ eventHolder.unbind("mousemove").mousemove(onMouseMove);
+ }
+ if (options.grid.clickable) {
+ eventHolder.unbind("click").click(onClick);
+ }
+ }
+ });
+
+ plot.hooks.processDatapoints.push(function(plot, series, data, datapoints) {
+ var options = plot.getOptions();
+ if (options.series.pie.show) {
+ processDatapoints(plot, series, data, datapoints);
+ }
+ });
+
+ plot.hooks.drawOverlay.push(function(plot, octx) {
+ var options = plot.getOptions();
+ if (options.series.pie.show) {
+ drawOverlay(plot, octx);
+ }
+ });
+
+ plot.hooks.draw.push(function(plot, newCtx) {
+ var options = plot.getOptions();
+ if (options.series.pie.show) {
+ draw(plot, newCtx);
+ }
+ });
+
+ function processDatapoints(plot, series, datapoints) {
+ if (!processed) {
+ processed = true;
+ canvas = plot.getCanvas();
+ target = $(canvas).parent();
+ options = plot.getOptions();
+ plot.setData(combine(plot.getData()));
+ }
+ }
+
+ function combine(data) {
+
+ var total = 0,
+ combined = 0,
+ numCombined = 0,
+ color = options.series.pie.combine.color,
+ newdata = [];
+
+ // Fix up the raw data from Flot, ensuring the data is numeric
+
+ for (var i = 0; i < data.length; ++i) {
+
+ var value = data[i].data;
+
+ // If the data is an array, we'll assume that it's a standard
+ // Flot x-y pair, and are concerned only with the second value.
+
+ // Note how we use the original array, rather than creating a
+ // new one; this is more efficient and preserves any extra data
+ // that the user may have stored in higher indexes.
+
+ if ($.isArray(value) && value.length == 1) {
+ value = value[0];
+ }
+
+ if ($.isArray(value)) {
+ // Equivalent to $.isNumeric() but compatible with jQuery < 1.7
+ if (!isNaN(parseFloat(value[1])) && isFinite(value[1])) {
+ value[1] = +value[1];
+ } else {
+ value[1] = 0;
+ }
+ } else if (!isNaN(parseFloat(value)) && isFinite(value)) {
+ value = [1, +value];
+ } else {
+ value = [1, 0];
+ }
+
+ data[i].data = [value];
+ }
+
+ // Sum up all the slices, so we can calculate percentages for each
+
+ for (var i = 0; i < data.length; ++i) {
+ total += data[i].data[0][1];
+ }
+
+ // Count the number of slices with percentages below the combine
+ // threshold; if it turns out to be just one, we won't combine.
+
+ for (var i = 0; i < data.length; ++i) {
+ var value = data[i].data[0][1];
+ if (value / total <= options.series.pie.combine.threshold) {
+ combined += value;
+ numCombined++;
+ if (!color) {
+ color = data[i].color;
+ }
+ }
+ }
+
+ for (var i = 0; i < data.length; ++i) {
+ var value = data[i].data[0][1];
+ if (numCombined < 2 || value / total > options.series.pie.combine.threshold) {
+ newdata.push(
+ $.extend(data[i], { /* extend to allow keeping all other original data values
+ and using them e.g. in labelFormatter. */
+ data: [[1, value]],
+ color: data[i].color,
+ label: data[i].label,
+ angle: value * Math.PI * 2 / total,
+ percent: value / (total / 100)
+ })
+ );
+ }
+ }
+
+ if (numCombined > 1) {
+ newdata.push({
+ data: [[1, combined]],
+ color: color,
+ label: options.series.pie.combine.label,
+ angle: combined * Math.PI * 2 / total,
+ percent: combined / (total / 100)
+ });
+ }
+
+ return newdata;
+ }
+
+ function draw(plot, newCtx) {
+
+ if (!target) {
+ return; // if no series were passed
+ }
+
+ var canvasWidth = plot.getPlaceholder().width(),
+ canvasHeight = plot.getPlaceholder().height(),
+ legendWidth = target.children().filter(".legend").children().width() || 0;
+
+ ctx = newCtx;
+
+ // WARNING: HACK! REWRITE THIS CODE AS SOON AS POSSIBLE!
+
+ // When combining smaller slices into an 'other' slice, we need to
+ // add a new series. Since Flot gives plugins no way to modify the
+ // list of series, the pie plugin uses a hack where the first call
+ // to processDatapoints results in a call to setData with the new
+ // list of series, then subsequent processDatapoints do nothing.
+
+ // The plugin-global 'processed' flag is used to control this hack;
+ // it starts out false, and is set to true after the first call to
+ // processDatapoints.
+
+ // Unfortunately this turns future setData calls into no-ops; they
+ // call processDatapoints, the flag is true, and nothing happens.
+
+ // To fix this we'll set the flag back to false here in draw, when
+ // all series have been processed, so the next sequence of calls to
+ // processDatapoints once again starts out with a slice-combine.
+ // This is really a hack; in 0.9 we need to give plugins a proper
+ // way to modify series before any processing begins.
+
+ processed = false;
+
+ // calculate maximum radius and center point
+
+ maxRadius = Math.min(canvasWidth, canvasHeight / options.series.pie.tilt) / 2;
+ centerTop = canvasHeight / 2 + options.series.pie.offset.top;
+ centerLeft = canvasWidth / 2;
+
+ if (options.series.pie.offset.left == "auto") {
+ if (options.legend.position.match("w")) {
+ centerLeft += legendWidth / 2;
+ } else {
+ centerLeft -= legendWidth / 2;
+ }
+ if (centerLeft < maxRadius) {
+ centerLeft = maxRadius;
+ } else if (centerLeft > canvasWidth - maxRadius) {
+ centerLeft = canvasWidth - maxRadius;
+ }
+ } else {
+ centerLeft += options.series.pie.offset.left;
+ }
+
+ var slices = plot.getData(),
+ attempts = 0;
+
+ // Keep shrinking the pie's radius until drawPie returns true,
+ // indicating that all the labels fit, or we try too many times.
+
+ do {
+ if (attempts > 0) {
+ maxRadius *= REDRAW_SHRINK;
+ }
+ attempts += 1;
+ clear();
+ if (options.series.pie.tilt <= 0.8) {
+ drawShadow();
+ }
+ } while (!drawPie() && attempts < REDRAW_ATTEMPTS)
+
+ if (attempts >= REDRAW_ATTEMPTS) {
+ clear();
+ target.prepend("Could not draw pie with labels contained inside canvas
");
+ }
+
+ if (plot.setSeries && plot.insertLegend) {
+ plot.setSeries(slices);
+ plot.insertLegend();
+ }
+
+ // we're actually done at this point, just defining internal functions at this point
+
+ function clear() {
+ ctx.clearRect(0, 0, canvasWidth, canvasHeight);
+ target.children().filter(".pieLabel, .pieLabelBackground").remove();
+ }
+
+ function drawShadow() {
+
+ var shadowLeft = options.series.pie.shadow.left;
+ var shadowTop = options.series.pie.shadow.top;
+ var edge = 10;
+ var alpha = options.series.pie.shadow.alpha;
+ var radius = options.series.pie.radius > 1 ? options.series.pie.radius : maxRadius * options.series.pie.radius;
+
+ if (radius >= canvasWidth / 2 - shadowLeft || radius * options.series.pie.tilt >= canvasHeight / 2 - shadowTop || radius <= edge) {
+ return; // shadow would be outside canvas, so don't draw it
+ }
+
+ ctx.save();
+ ctx.translate(shadowLeft,shadowTop);
+ ctx.globalAlpha = alpha;
+ ctx.fillStyle = "#000";
+
+ // center and rotate to starting position
+
+ ctx.translate(centerLeft,centerTop);
+ ctx.scale(1, options.series.pie.tilt);
+
+ //radius -= edge;
+
+ for (var i = 1; i <= edge; i++) {
+ ctx.beginPath();
+ ctx.arc(0, 0, radius, 0, Math.PI * 2, false);
+ ctx.fill();
+ radius -= i;
+ }
+
+ ctx.restore();
+ }
+
+ function drawPie() {
+
+ var startAngle = Math.PI * options.series.pie.startAngle;
+ var radius = options.series.pie.radius > 1 ? options.series.pie.radius : maxRadius * options.series.pie.radius;
+
+ // center and rotate to starting position
+
+ ctx.save();
+ ctx.translate(centerLeft,centerTop);
+ ctx.scale(1, options.series.pie.tilt);
+ //ctx.rotate(startAngle); // start at top; -- This doesn't work properly in Opera
+
+ // draw slices
+
+ ctx.save();
+ var currentAngle = startAngle;
+ for (var i = 0; i < slices.length; ++i) {
+ slices[i].startAngle = currentAngle;
+ drawSlice(slices[i].angle, slices[i].color, true);
+ }
+ ctx.restore();
+
+ // draw slice outlines
+
+ if (options.series.pie.stroke.width > 0) {
+ ctx.save();
+ ctx.lineWidth = options.series.pie.stroke.width;
+ currentAngle = startAngle;
+ for (var i = 0; i < slices.length; ++i) {
+ drawSlice(slices[i].angle, options.series.pie.stroke.color, false);
+ }
+ ctx.restore();
+ }
+
+ // draw donut hole
+
+ drawDonutHole(ctx);
+
+ ctx.restore();
+
+ // Draw the labels, returning true if they fit within the plot
+
+ if (options.series.pie.label.show) {
+ return drawLabels();
+ } else return true;
+
+ function drawSlice(angle, color, fill) {
+
+ if (angle <= 0 || isNaN(angle)) {
+ return;
+ }
+
+ if (fill) {
+ ctx.fillStyle = color;
+ } else {
+ ctx.strokeStyle = color;
+ ctx.lineJoin = "round";
+ }
+
+ ctx.beginPath();
+ if (Math.abs(angle - Math.PI * 2) > 0.000000001) {
+ ctx.moveTo(0, 0); // Center of the pie
+ }
+
+ //ctx.arc(0, 0, radius, 0, angle, false); // This doesn't work properly in Opera
+ ctx.arc(0, 0, radius,currentAngle, currentAngle + angle / 2, false);
+ ctx.arc(0, 0, radius,currentAngle + angle / 2, currentAngle + angle, false);
+ ctx.closePath();
+ //ctx.rotate(angle); // This doesn't work properly in Opera
+ currentAngle += angle;
+
+ if (fill) {
+ ctx.fill();
+ } else {
+ ctx.stroke();
+ }
+ }
+
+ function drawLabels() {
+
+ var currentAngle = startAngle;
+ var radius = options.series.pie.label.radius > 1 ? options.series.pie.label.radius : maxRadius * options.series.pie.label.radius;
+
+ for (var i = 0; i < slices.length; ++i) {
+ if (slices[i].percent >= options.series.pie.label.threshold * 100) {
+ if (!drawLabel(slices[i], currentAngle, i)) {
+ return false;
+ }
+ }
+ currentAngle += slices[i].angle;
+ }
+
+ return true;
+
+ function drawLabel(slice, startAngle, index) {
+
+ if (slice.data[0][1] == 0) {
+ return true;
+ }
+
+ // format label text
+
+ var lf = options.legend.labelFormatter, text, plf = options.series.pie.label.formatter;
+
+ if (lf) {
+ text = lf(slice.label, slice);
+ } else {
+ text = slice.label;
+ }
+
+ if (plf) {
+ text = plf(text, slice);
+ }
+
+ var halfAngle = ((startAngle + slice.angle) + startAngle) / 2;
+ var x = centerLeft + Math.round(Math.cos(halfAngle) * radius);
+ var y = centerTop + Math.round(Math.sin(halfAngle) * radius) * options.series.pie.tilt;
+
+ var html = "" + text + " ";
+ target.append(html);
+
+ var label = target.children("#pieLabel" + index);
+ var labelTop = (y - label.height() / 2);
+ var labelLeft = (x - label.width() / 2);
+
+ label.css("top", labelTop);
+ label.css("left", labelLeft);
+
+ // check to make sure that the label is not outside the canvas
+
+ if (0 - labelTop > 0 || 0 - labelLeft > 0 || canvasHeight - (labelTop + label.height()) < 0 || canvasWidth - (labelLeft + label.width()) < 0) {
+ return false;
+ }
+
+ if (options.series.pie.label.background.opacity != 0) {
+
+ // put in the transparent background separately to avoid blended labels and label boxes
+
+ var c = options.series.pie.label.background.color;
+
+ if (c == null) {
+ c = slice.color;
+ }
+
+ var pos = "top:" + labelTop + "px;left:" + labelLeft + "px;";
+ $("
")
+ .css("opacity", options.series.pie.label.background.opacity)
+ .insertBefore(label);
+ }
+
+ return true;
+ } // end individual label function
+ } // end drawLabels function
+ } // end drawPie function
+ } // end draw function
+
+ // Placed here because it needs to be accessed from multiple locations
+
+ function drawDonutHole(layer) {
+ if (options.series.pie.innerRadius > 0) {
+
+ // subtract the center
+
+ layer.save();
+ var innerRadius = options.series.pie.innerRadius > 1 ? options.series.pie.innerRadius : maxRadius * options.series.pie.innerRadius;
+ layer.globalCompositeOperation = "destination-out"; // this does not work with excanvas, but it will fall back to using the stroke color
+ layer.beginPath();
+ layer.fillStyle = options.series.pie.stroke.color;
+ layer.arc(0, 0, innerRadius, 0, Math.PI * 2, false);
+ layer.fill();
+ layer.closePath();
+ layer.restore();
+
+ // add inner stroke
+
+ layer.save();
+ layer.beginPath();
+ layer.strokeStyle = options.series.pie.stroke.color;
+ layer.arc(0, 0, innerRadius, 0, Math.PI * 2, false);
+ layer.stroke();
+ layer.closePath();
+ layer.restore();
+
+ // TODO: add extra shadow inside hole (with a mask) if the pie is tilted.
+ }
+ }
+
+ //-- Additional Interactive related functions --
+
+ function isPointInPoly(poly, pt) {
+ for(var c = false, i = -1, l = poly.length, j = l - 1; ++i < l; j = i)
+ ((poly[i][1] <= pt[1] && pt[1] < poly[j][1]) || (poly[j][1] <= pt[1] && pt[1]< poly[i][1]))
+ && (pt[0] < (poly[j][0] - poly[i][0]) * (pt[1] - poly[i][1]) / (poly[j][1] - poly[i][1]) + poly[i][0])
+ && (c = !c);
+ return c;
+ }
+
+ function findNearbySlice(mouseX, mouseY) {
+
+ var slices = plot.getData(),
+ options = plot.getOptions(),
+ radius = options.series.pie.radius > 1 ? options.series.pie.radius : maxRadius * options.series.pie.radius,
+ x, y;
+
+ for (var i = 0; i < slices.length; ++i) {
+
+ var s = slices[i];
+
+ if (s.pie.show) {
+
+ ctx.save();
+ ctx.beginPath();
+ ctx.moveTo(0, 0); // Center of the pie
+ //ctx.scale(1, options.series.pie.tilt); // this actually seems to break everything when here.
+ ctx.arc(0, 0, radius, s.startAngle, s.startAngle + s.angle / 2, false);
+ ctx.arc(0, 0, radius, s.startAngle + s.angle / 2, s.startAngle + s.angle, false);
+ ctx.closePath();
+ x = mouseX - centerLeft;
+ y = mouseY - centerTop;
+
+ if (ctx.isPointInPath) {
+ if (ctx.isPointInPath(mouseX - centerLeft, mouseY - centerTop)) {
+ ctx.restore();
+ return {
+ datapoint: [s.percent, s.data],
+ dataIndex: 0,
+ series: s,
+ seriesIndex: i
+ };
+ }
+ } else {
+
+ // excanvas for IE doesn;t support isPointInPath, this is a workaround.
+
+ var p1X = radius * Math.cos(s.startAngle),
+ p1Y = radius * Math.sin(s.startAngle),
+ p2X = radius * Math.cos(s.startAngle + s.angle / 4),
+ p2Y = radius * Math.sin(s.startAngle + s.angle / 4),
+ p3X = radius * Math.cos(s.startAngle + s.angle / 2),
+ p3Y = radius * Math.sin(s.startAngle + s.angle / 2),
+ p4X = radius * Math.cos(s.startAngle + s.angle / 1.5),
+ p4Y = radius * Math.sin(s.startAngle + s.angle / 1.5),
+ p5X = radius * Math.cos(s.startAngle + s.angle),
+ p5Y = radius * Math.sin(s.startAngle + s.angle),
+ arrPoly = [[0, 0], [p1X, p1Y], [p2X, p2Y], [p3X, p3Y], [p4X, p4Y], [p5X, p5Y]],
+ arrPoint = [x, y];
+
+ // TODO: perhaps do some mathmatical trickery here with the Y-coordinate to compensate for pie tilt?
+
+ if (isPointInPoly(arrPoly, arrPoint)) {
+ ctx.restore();
+ return {
+ datapoint: [s.percent, s.data],
+ dataIndex: 0,
+ series: s,
+ seriesIndex: i
+ };
+ }
+ }
+
+ ctx.restore();
+ }
+ }
+
+ return null;
+ }
+
+ function onMouseMove(e) {
+ triggerClickHoverEvent("plothover", e);
+ }
+
+ function onClick(e) {
+ triggerClickHoverEvent("plotclick", e);
+ }
+
+ // trigger click or hover event (they send the same parameters so we share their code)
+
+ function triggerClickHoverEvent(eventname, e) {
+
+ var offset = plot.offset();
+ var canvasX = parseInt(e.pageX - offset.left);
+ var canvasY = parseInt(e.pageY - offset.top);
+ var item = findNearbySlice(canvasX, canvasY);
+
+ if (options.grid.autoHighlight) {
+
+ // clear auto-highlights
+
+ for (var i = 0; i < highlights.length; ++i) {
+ var h = highlights[i];
+ if (h.auto == eventname && !(item && h.series == item.series)) {
+ unhighlight(h.series);
+ }
+ }
+ }
+
+ // highlight the slice
+
+ if (item) {
+ highlight(item.series, eventname);
+ }
+
+ // trigger any hover bind events
+
+ var pos = { pageX: e.pageX, pageY: e.pageY };
+ target.trigger(eventname, [pos, item]);
+ }
+
+ function highlight(s, auto) {
+ //if (typeof s == "number") {
+ // s = series[s];
+ //}
+
+ var i = indexOfHighlight(s);
+
+ if (i == -1) {
+ highlights.push({ series: s, auto: auto });
+ plot.triggerRedrawOverlay();
+ } else if (!auto) {
+ highlights[i].auto = false;
+ }
+ }
+
+ function unhighlight(s) {
+ if (s == null) {
+ highlights = [];
+ plot.triggerRedrawOverlay();
+ }
+
+ //if (typeof s == "number") {
+ // s = series[s];
+ //}
+
+ var i = indexOfHighlight(s);
+
+ if (i != -1) {
+ highlights.splice(i, 1);
+ plot.triggerRedrawOverlay();
+ }
+ }
+
+ function indexOfHighlight(s) {
+ for (var i = 0; i < highlights.length; ++i) {
+ var h = highlights[i];
+ if (h.series == s)
+ return i;
+ }
+ return -1;
+ }
+
+ function drawOverlay(plot, octx) {
+
+ var options = plot.getOptions();
+
+ var radius = options.series.pie.radius > 1 ? options.series.pie.radius : maxRadius * options.series.pie.radius;
+
+ octx.save();
+ octx.translate(centerLeft, centerTop);
+ octx.scale(1, options.series.pie.tilt);
+
+ for (var i = 0; i < highlights.length; ++i) {
+ drawHighlight(highlights[i].series);
+ }
+
+ drawDonutHole(octx);
+
+ octx.restore();
+
+ function drawHighlight(series) {
+
+ if (series.angle <= 0 || isNaN(series.angle)) {
+ return;
+ }
+
+ //octx.fillStyle = parseColor(options.series.pie.highlight.color).scale(null, null, null, options.series.pie.highlight.opacity).toString();
+ octx.fillStyle = "rgba(255, 255, 255, " + options.series.pie.highlight.opacity + ")"; // this is temporary until we have access to parseColor
+ octx.beginPath();
+ if (Math.abs(series.angle - Math.PI * 2) > 0.000000001) {
+ octx.moveTo(0, 0); // Center of the pie
+ }
+ octx.arc(0, 0, radius, series.startAngle, series.startAngle + series.angle / 2, false);
+ octx.arc(0, 0, radius, series.startAngle + series.angle / 2, series.startAngle + series.angle, false);
+ octx.closePath();
+ octx.fill();
+ }
+ }
+ } // end init (plugin body)
+
+ // define pie specific options and their default values
+
+ var options = {
+ series: {
+ pie: {
+ show: false,
+ radius: "auto", // actual radius of the visible pie (based on full calculated radius if <=1, or hard pixel value)
+ innerRadius: 0, /* for donut */
+ startAngle: 3/2,
+ tilt: 1,
+ shadow: {
+ left: 5, // shadow left offset
+ top: 15, // shadow top offset
+ alpha: 0.02 // shadow alpha
+ },
+ offset: {
+ top: 0,
+ left: "auto"
+ },
+ stroke: {
+ color: "#fff",
+ width: 1
+ },
+ label: {
+ show: "auto",
+ formatter: function(label, slice) {
+ return "" + label + " " + Math.round(slice.percent) + "%
";
+ }, // formatter function
+ radius: 1, // radius at which to place the labels (based on full calculated radius if <=1, or hard pixel value)
+ background: {
+ color: null,
+ opacity: 0
+ },
+ threshold: 0 // percentage at which to hide the label (i.e. the slice is too narrow)
+ },
+ combine: {
+ threshold: -1, // percentage at which to combine little slices into one larger slice
+ color: null, // color to give the new slice (auto-generated if null)
+ label: "Other" // label to give the new slice
+ },
+ highlight: {
+ //color: "#fff", // will add this functionality once parseColor is available
+ opacity: 0.5
+ }
+ }
+ }
+ };
+
+ $.plot.plugins.push({
+ init: init,
+ options: options,
+ name: "pie",
+ version: "1.1"
+ });
+
+})(jQuery);
diff --git a/djangoproject/static/js/lib/jquery-flot/jquery.flot.resize.js b/djangoproject/static/js/lib/jquery-flot/jquery.flot.resize.js
new file mode 100644
index 000000000..8a626dda0
--- /dev/null
+++ b/djangoproject/static/js/lib/jquery-flot/jquery.flot.resize.js
@@ -0,0 +1,59 @@
+/* Flot plugin for automatically redrawing plots as the placeholder resizes.
+
+Copyright (c) 2007-2014 IOLA and Ole Laursen.
+Licensed under the MIT license.
+
+It works by listening for changes on the placeholder div (through the jQuery
+resize event plugin) - if the size changes, it will redraw the plot.
+
+There are no options. If you need to disable the plugin for some plots, you
+can just fix the size of their placeholders.
+
+*/
+
+/* Inline dependency:
+ * jQuery resize event - v1.1 - 3/14/2010
+ * http://benalman.com/projects/jquery-resize-plugin/
+ *
+ * Copyright (c) 2010 "Cowboy" Ben Alman
+ * Dual licensed under the MIT and GPL licenses.
+ * http://benalman.com/about/license/
+ */
+(function($,e,t){"$:nomunge";var i=[],n=$.resize=$.extend($.resize,{}),a,r=false,s="setTimeout",u="resize",m=u+"-special-event",o="pendingDelay",l="activeDelay",f="throttleWindow";n[o]=200;n[l]=20;n[f]=true;$.event.special[u]={setup:function(){if(!n[f]&&this[s]){return false}var e=$(this);i.push(this);e.data(m,{w:e.width(),h:e.height()});if(i.length===1){a=t;h()}},teardown:function(){if(!n[f]&&this[s]){return false}var e=$(this);for(var t=i.length-1;t>=0;t--){if(i[t]==this){i.splice(t,1);break}}e.removeData(m);if(!i.length){if(r){cancelAnimationFrame(a)}else{clearTimeout(a)}a=null}},add:function(e){if(!n[f]&&this[s]){return false}var i;function a(e,n,a){var r=$(this),s=r.data(m)||{};s.w=n!==t?n:r.width();s.h=a!==t?a:r.height();i.apply(this,arguments)}if($.isFunction(e)){i=e;return a}else{i=e.handler;e.handler=a}}};function h(t){if(r===true){r=t||1}for(var s=i.length-1;s>=0;s--){var l=$(i[s]);if(l[0]==e||l.is(":visible")){var f=l.width(),c=l.height(),d=l.data(m);if(d&&(f!==d.w||c!==d.h)){l.trigger(u,[d.w=f,d.h=c]);r=t||true}}else{d=l.data(m);d.w=0;d.h=0}}if(a!==null){if(r&&(t==null||t-r<1e3)){a=e.requestAnimationFrame(h)}else{a=setTimeout(h,n[o]);r=false}}}if(!e.requestAnimationFrame){e.requestAnimationFrame=function(){return e.webkitRequestAnimationFrame||e.mozRequestAnimationFrame||e.oRequestAnimationFrame||e.msRequestAnimationFrame||function(t,i){return e.setTimeout(function(){t((new Date).getTime())},n[l])}}()}if(!e.cancelAnimationFrame){e.cancelAnimationFrame=function(){return e.webkitCancelRequestAnimationFrame||e.mozCancelRequestAnimationFrame||e.oCancelRequestAnimationFrame||e.msCancelRequestAnimationFrame||clearTimeout}()}})(jQuery,this);
+
+(function ($) {
+ var options = { }; // no options
+
+ function init(plot) {
+ function onResize() {
+ var placeholder = plot.getPlaceholder();
+
+ // somebody might have hidden us and we can't plot
+ // when we don't have the dimensions
+ if (placeholder.width() == 0 || placeholder.height() == 0)
+ return;
+
+ plot.resize();
+ plot.setupGrid();
+ plot.draw();
+ }
+
+ function bindEvents(plot, eventHolder) {
+ plot.getPlaceholder().resize(onResize);
+ }
+
+ function shutdown(plot, eventHolder) {
+ plot.getPlaceholder().unbind("resize", onResize);
+ }
+
+ plot.hooks.bindEvents.push(bindEvents);
+ plot.hooks.shutdown.push(shutdown);
+ }
+
+ $.plot.plugins.push({
+ init: init,
+ options: options,
+ name: 'resize',
+ version: '1.0'
+ });
+})(jQuery);
diff --git a/djangoproject/static/js/lib/jquery-flot/jquery.flot.selection.js b/djangoproject/static/js/lib/jquery-flot/jquery.flot.selection.js
new file mode 100644
index 000000000..d3c20fa4e
--- /dev/null
+++ b/djangoproject/static/js/lib/jquery-flot/jquery.flot.selection.js
@@ -0,0 +1,360 @@
+/* Flot plugin for selecting regions of a plot.
+
+Copyright (c) 2007-2014 IOLA and Ole Laursen.
+Licensed under the MIT license.
+
+The plugin supports these options:
+
+selection: {
+ mode: null or "x" or "y" or "xy",
+ color: color,
+ shape: "round" or "miter" or "bevel",
+ minSize: number of pixels
+}
+
+Selection support is enabled by setting the mode to one of "x", "y" or "xy".
+In "x" mode, the user will only be able to specify the x range, similarly for
+"y" mode. For "xy", the selection becomes a rectangle where both ranges can be
+specified. "color" is color of the selection (if you need to change the color
+later on, you can get to it with plot.getOptions().selection.color). "shape"
+is the shape of the corners of the selection.
+
+"minSize" is the minimum size a selection can be in pixels. This value can
+be customized to determine the smallest size a selection can be and still
+have the selection rectangle be displayed. When customizing this value, the
+fact that it refers to pixels, not axis units must be taken into account.
+Thus, for example, if there is a bar graph in time mode with BarWidth set to 1
+minute, setting "minSize" to 1 will not make the minimum selection size 1
+minute, but rather 1 pixel. Note also that setting "minSize" to 0 will prevent
+"plotunselected" events from being fired when the user clicks the mouse without
+dragging.
+
+When selection support is enabled, a "plotselected" event will be emitted on
+the DOM element you passed into the plot function. The event handler gets a
+parameter with the ranges selected on the axes, like this:
+
+ placeholder.bind( "plotselected", function( event, ranges ) {
+ alert("You selected " + ranges.xaxis.from + " to " + ranges.xaxis.to)
+ // similar for yaxis - with multiple axes, the extra ones are in
+ // x2axis, x3axis, ...
+ });
+
+The "plotselected" event is only fired when the user has finished making the
+selection. A "plotselecting" event is fired during the process with the same
+parameters as the "plotselected" event, in case you want to know what's
+happening while it's happening,
+
+A "plotunselected" event with no arguments is emitted when the user clicks the
+mouse to remove the selection. As stated above, setting "minSize" to 0 will
+destroy this behavior.
+
+The plugin allso adds the following methods to the plot object:
+
+- setSelection( ranges, preventEvent )
+
+ Set the selection rectangle. The passed in ranges is on the same form as
+ returned in the "plotselected" event. If the selection mode is "x", you
+ should put in either an xaxis range, if the mode is "y" you need to put in
+ an yaxis range and both xaxis and yaxis if the selection mode is "xy", like
+ this:
+
+ setSelection({ xaxis: { from: 0, to: 10 }, yaxis: { from: 40, to: 60 } });
+
+ setSelection will trigger the "plotselected" event when called. If you don't
+ want that to happen, e.g. if you're inside a "plotselected" handler, pass
+ true as the second parameter. If you are using multiple axes, you can
+ specify the ranges on any of those, e.g. as x2axis/x3axis/... instead of
+ xaxis, the plugin picks the first one it sees.
+
+- clearSelection( preventEvent )
+
+ Clear the selection rectangle. Pass in true to avoid getting a
+ "plotunselected" event.
+
+- getSelection()
+
+ Returns the current selection in the same format as the "plotselected"
+ event. If there's currently no selection, the function returns null.
+
+*/
+
+(function ($) {
+ function init(plot) {
+ var selection = {
+ first: { x: -1, y: -1}, second: { x: -1, y: -1},
+ show: false,
+ active: false
+ };
+
+ // FIXME: The drag handling implemented here should be
+ // abstracted out, there's some similar code from a library in
+ // the navigation plugin, this should be massaged a bit to fit
+ // the Flot cases here better and reused. Doing this would
+ // make this plugin much slimmer.
+ var savedhandlers = {};
+
+ var mouseUpHandler = null;
+
+ function onMouseMove(e) {
+ if (selection.active) {
+ updateSelection(e);
+
+ plot.getPlaceholder().trigger("plotselecting", [ getSelection() ]);
+ }
+ }
+
+ function onMouseDown(e) {
+ if (e.which != 1) // only accept left-click
+ return;
+
+ // cancel out any text selections
+ document.body.focus();
+
+ // prevent text selection and drag in old-school browsers
+ if (document.onselectstart !== undefined && savedhandlers.onselectstart == null) {
+ savedhandlers.onselectstart = document.onselectstart;
+ document.onselectstart = function () { return false; };
+ }
+ if (document.ondrag !== undefined && savedhandlers.ondrag == null) {
+ savedhandlers.ondrag = document.ondrag;
+ document.ondrag = function () { return false; };
+ }
+
+ setSelectionPos(selection.first, e);
+
+ selection.active = true;
+
+ // this is a bit silly, but we have to use a closure to be
+ // able to whack the same handler again
+ mouseUpHandler = function (e) { onMouseUp(e); };
+
+ $(document).one("mouseup", mouseUpHandler);
+ }
+
+ function onMouseUp(e) {
+ mouseUpHandler = null;
+
+ // revert drag stuff for old-school browsers
+ if (document.onselectstart !== undefined)
+ document.onselectstart = savedhandlers.onselectstart;
+ if (document.ondrag !== undefined)
+ document.ondrag = savedhandlers.ondrag;
+
+ // no more dragging
+ selection.active = false;
+ updateSelection(e);
+
+ if (selectionIsSane())
+ triggerSelectedEvent();
+ else {
+ // this counts as a clear
+ plot.getPlaceholder().trigger("plotunselected", [ ]);
+ plot.getPlaceholder().trigger("plotselecting", [ null ]);
+ }
+
+ return false;
+ }
+
+ function getSelection() {
+ if (!selectionIsSane())
+ return null;
+
+ if (!selection.show) return null;
+
+ var r = {}, c1 = selection.first, c2 = selection.second;
+ $.each(plot.getAxes(), function (name, axis) {
+ if (axis.used) {
+ var p1 = axis.c2p(c1[axis.direction]), p2 = axis.c2p(c2[axis.direction]);
+ r[name] = { from: Math.min(p1, p2), to: Math.max(p1, p2) };
+ }
+ });
+ return r;
+ }
+
+ function triggerSelectedEvent() {
+ var r = getSelection();
+
+ plot.getPlaceholder().trigger("plotselected", [ r ]);
+
+ // backwards-compat stuff, to be removed in future
+ if (r.xaxis && r.yaxis)
+ plot.getPlaceholder().trigger("selected", [ { x1: r.xaxis.from, y1: r.yaxis.from, x2: r.xaxis.to, y2: r.yaxis.to } ]);
+ }
+
+ function clamp(min, value, max) {
+ return value < min ? min: (value > max ? max: value);
+ }
+
+ function setSelectionPos(pos, e) {
+ var o = plot.getOptions();
+ var offset = plot.getPlaceholder().offset();
+ var plotOffset = plot.getPlotOffset();
+ pos.x = clamp(0, e.pageX - offset.left - plotOffset.left, plot.width());
+ pos.y = clamp(0, e.pageY - offset.top - plotOffset.top, plot.height());
+
+ if (o.selection.mode == "y")
+ pos.x = pos == selection.first ? 0 : plot.width();
+
+ if (o.selection.mode == "x")
+ pos.y = pos == selection.first ? 0 : plot.height();
+ }
+
+ function updateSelection(pos) {
+ if (pos.pageX == null)
+ return;
+
+ setSelectionPos(selection.second, pos);
+ if (selectionIsSane()) {
+ selection.show = true;
+ plot.triggerRedrawOverlay();
+ }
+ else
+ clearSelection(true);
+ }
+
+ function clearSelection(preventEvent) {
+ if (selection.show) {
+ selection.show = false;
+ plot.triggerRedrawOverlay();
+ if (!preventEvent)
+ plot.getPlaceholder().trigger("plotunselected", [ ]);
+ }
+ }
+
+ // function taken from markings support in Flot
+ function extractRange(ranges, coord) {
+ var axis, from, to, key, axes = plot.getAxes();
+
+ for (var k in axes) {
+ axis = axes[k];
+ if (axis.direction == coord) {
+ key = coord + axis.n + "axis";
+ if (!ranges[key] && axis.n == 1)
+ key = coord + "axis"; // support x1axis as xaxis
+ if (ranges[key]) {
+ from = ranges[key].from;
+ to = ranges[key].to;
+ break;
+ }
+ }
+ }
+
+ // backwards-compat stuff - to be removed in future
+ if (!ranges[key]) {
+ axis = coord == "x" ? plot.getXAxes()[0] : plot.getYAxes()[0];
+ from = ranges[coord + "1"];
+ to = ranges[coord + "2"];
+ }
+
+ // auto-reverse as an added bonus
+ if (from != null && to != null && from > to) {
+ var tmp = from;
+ from = to;
+ to = tmp;
+ }
+
+ return { from: from, to: to, axis: axis };
+ }
+
+ function setSelection(ranges, preventEvent) {
+ var axis, range, o = plot.getOptions();
+
+ if (o.selection.mode == "y") {
+ selection.first.x = 0;
+ selection.second.x = plot.width();
+ }
+ else {
+ range = extractRange(ranges, "x");
+
+ selection.first.x = range.axis.p2c(range.from);
+ selection.second.x = range.axis.p2c(range.to);
+ }
+
+ if (o.selection.mode == "x") {
+ selection.first.y = 0;
+ selection.second.y = plot.height();
+ }
+ else {
+ range = extractRange(ranges, "y");
+
+ selection.first.y = range.axis.p2c(range.from);
+ selection.second.y = range.axis.p2c(range.to);
+ }
+
+ selection.show = true;
+ plot.triggerRedrawOverlay();
+ if (!preventEvent && selectionIsSane())
+ triggerSelectedEvent();
+ }
+
+ function selectionIsSane() {
+ var minSize = plot.getOptions().selection.minSize;
+ return Math.abs(selection.second.x - selection.first.x) >= minSize &&
+ Math.abs(selection.second.y - selection.first.y) >= minSize;
+ }
+
+ plot.clearSelection = clearSelection;
+ plot.setSelection = setSelection;
+ plot.getSelection = getSelection;
+
+ plot.hooks.bindEvents.push(function(plot, eventHolder) {
+ var o = plot.getOptions();
+ if (o.selection.mode != null) {
+ eventHolder.mousemove(onMouseMove);
+ eventHolder.mousedown(onMouseDown);
+ }
+ });
+
+
+ plot.hooks.drawOverlay.push(function (plot, ctx) {
+ // draw selection
+ if (selection.show && selectionIsSane()) {
+ var plotOffset = plot.getPlotOffset();
+ var o = plot.getOptions();
+
+ ctx.save();
+ ctx.translate(plotOffset.left, plotOffset.top);
+
+ var c = $.color.parse(o.selection.color);
+
+ ctx.strokeStyle = c.scale('a', 0.8).toString();
+ ctx.lineWidth = 1;
+ ctx.lineJoin = o.selection.shape;
+ ctx.fillStyle = c.scale('a', 0.4).toString();
+
+ var x = Math.min(selection.first.x, selection.second.x) + 0.5,
+ y = Math.min(selection.first.y, selection.second.y) + 0.5,
+ w = Math.abs(selection.second.x - selection.first.x) - 1,
+ h = Math.abs(selection.second.y - selection.first.y) - 1;
+
+ ctx.fillRect(x, y, w, h);
+ ctx.strokeRect(x, y, w, h);
+
+ ctx.restore();
+ }
+ });
+
+ plot.hooks.shutdown.push(function (plot, eventHolder) {
+ eventHolder.unbind("mousemove", onMouseMove);
+ eventHolder.unbind("mousedown", onMouseDown);
+
+ if (mouseUpHandler)
+ $(document).unbind("mouseup", mouseUpHandler);
+ });
+
+ }
+
+ $.plot.plugins.push({
+ init: init,
+ options: {
+ selection: {
+ mode: null, // one of null, "x", "y" or "xy"
+ color: "#e8cfac",
+ shape: "round", // one of "round", "miter", or "bevel"
+ minSize: 5 // minimum number of pixels
+ }
+ },
+ name: 'selection',
+ version: '1.1'
+ });
+})(jQuery);
diff --git a/djangoproject/static/js/lib/jquery-flot/jquery.flot.stack.js b/djangoproject/static/js/lib/jquery-flot/jquery.flot.stack.js
new file mode 100644
index 000000000..e75a7dfc0
--- /dev/null
+++ b/djangoproject/static/js/lib/jquery-flot/jquery.flot.stack.js
@@ -0,0 +1,188 @@
+/* Flot plugin for stacking data sets rather than overlyaing them.
+
+Copyright (c) 2007-2014 IOLA and Ole Laursen.
+Licensed under the MIT license.
+
+The plugin assumes the data is sorted on x (or y if stacking horizontally).
+For line charts, it is assumed that if a line has an undefined gap (from a
+null point), then the line above it should have the same gap - insert zeros
+instead of "null" if you want another behaviour. This also holds for the start
+and end of the chart. Note that stacking a mix of positive and negative values
+in most instances doesn't make sense (so it looks weird).
+
+Two or more series are stacked when their "stack" attribute is set to the same
+key (which can be any number or string or just "true"). To specify the default
+stack, you can set the stack option like this:
+
+ series: {
+ stack: null/false, true, or a key (number/string)
+ }
+
+You can also specify it for a single series, like this:
+
+ $.plot( $("#placeholder"), [{
+ data: [ ... ],
+ stack: true
+ }])
+
+The stacking order is determined by the order of the data series in the array
+(later series end up on top of the previous).
+
+Internally, the plugin modifies the datapoints in each series, adding an
+offset to the y value. For line series, extra data points are inserted through
+interpolation. If there's a second y value, it's also adjusted (e.g for bar
+charts or filled areas).
+
+*/
+
+(function ($) {
+ var options = {
+ series: { stack: null } // or number/string
+ };
+
+ function init(plot) {
+ function findMatchingSeries(s, allseries) {
+ var res = null;
+ for (var i = 0; i < allseries.length; ++i) {
+ if (s == allseries[i])
+ break;
+
+ if (allseries[i].stack == s.stack)
+ res = allseries[i];
+ }
+
+ return res;
+ }
+
+ function stackData(plot, s, datapoints) {
+ if (s.stack == null || s.stack === false)
+ return;
+
+ var other = findMatchingSeries(s, plot.getData());
+ if (!other)
+ return;
+
+ var ps = datapoints.pointsize,
+ points = datapoints.points,
+ otherps = other.datapoints.pointsize,
+ otherpoints = other.datapoints.points,
+ newpoints = [],
+ px, py, intery, qx, qy, bottom,
+ withlines = s.lines.show,
+ horizontal = s.bars.horizontal,
+ withbottom = ps > 2 && (horizontal ? datapoints.format[2].x : datapoints.format[2].y),
+ withsteps = withlines && s.lines.steps,
+ fromgap = true,
+ keyOffset = horizontal ? 1 : 0,
+ accumulateOffset = horizontal ? 0 : 1,
+ i = 0, j = 0, l, m;
+
+ while (true) {
+ if (i >= points.length)
+ break;
+
+ l = newpoints.length;
+
+ if (points[i] == null) {
+ // copy gaps
+ for (m = 0; m < ps; ++m)
+ newpoints.push(points[i + m]);
+ i += ps;
+ }
+ else if (j >= otherpoints.length) {
+ // for lines, we can't use the rest of the points
+ if (!withlines) {
+ for (m = 0; m < ps; ++m)
+ newpoints.push(points[i + m]);
+ }
+ i += ps;
+ }
+ else if (otherpoints[j] == null) {
+ // oops, got a gap
+ for (m = 0; m < ps; ++m)
+ newpoints.push(null);
+ fromgap = true;
+ j += otherps;
+ }
+ else {
+ // cases where we actually got two points
+ px = points[i + keyOffset];
+ py = points[i + accumulateOffset];
+ qx = otherpoints[j + keyOffset];
+ qy = otherpoints[j + accumulateOffset];
+ bottom = 0;
+
+ if (px == qx) {
+ for (m = 0; m < ps; ++m)
+ newpoints.push(points[i + m]);
+
+ newpoints[l + accumulateOffset] += qy;
+ bottom = qy;
+
+ i += ps;
+ j += otherps;
+ }
+ else if (px > qx) {
+ // we got past point below, might need to
+ // insert interpolated extra point
+ if (withlines && i > 0 && points[i - ps] != null) {
+ intery = py + (points[i - ps + accumulateOffset] - py) * (qx - px) / (points[i - ps + keyOffset] - px);
+ newpoints.push(qx);
+ newpoints.push(intery + qy);
+ for (m = 2; m < ps; ++m)
+ newpoints.push(points[i + m]);
+ bottom = qy;
+ }
+
+ j += otherps;
+ }
+ else { // px < qx
+ if (fromgap && withlines) {
+ // if we come from a gap, we just skip this point
+ i += ps;
+ continue;
+ }
+
+ for (m = 0; m < ps; ++m)
+ newpoints.push(points[i + m]);
+
+ // we might be able to interpolate a point below,
+ // this can give us a better y
+ if (withlines && j > 0 && otherpoints[j - otherps] != null)
+ bottom = qy + (otherpoints[j - otherps + accumulateOffset] - qy) * (px - qx) / (otherpoints[j - otherps + keyOffset] - qx);
+
+ newpoints[l + accumulateOffset] += bottom;
+
+ i += ps;
+ }
+
+ fromgap = false;
+
+ if (l != newpoints.length && withbottom)
+ newpoints[l + 2] += bottom;
+ }
+
+ // maintain the line steps invariant
+ if (withsteps && l != newpoints.length && l > 0
+ && newpoints[l] != null
+ && newpoints[l] != newpoints[l - ps]
+ && newpoints[l + 1] != newpoints[l - ps + 1]) {
+ for (m = 0; m < ps; ++m)
+ newpoints[l + ps + m] = newpoints[l + m];
+ newpoints[l + 1] = newpoints[l - ps + 1];
+ }
+ }
+
+ datapoints.points = newpoints;
+ }
+
+ plot.hooks.processDatapoints.push(stackData);
+ }
+
+ $.plot.plugins.push({
+ init: init,
+ options: options,
+ name: 'stack',
+ version: '1.2'
+ });
+})(jQuery);
diff --git a/djangoproject/static/js/lib/jquery-flot/jquery.flot.symbol.js b/djangoproject/static/js/lib/jquery-flot/jquery.flot.symbol.js
new file mode 100644
index 000000000..79f634971
--- /dev/null
+++ b/djangoproject/static/js/lib/jquery-flot/jquery.flot.symbol.js
@@ -0,0 +1,71 @@
+/* Flot plugin that adds some extra symbols for plotting points.
+
+Copyright (c) 2007-2014 IOLA and Ole Laursen.
+Licensed under the MIT license.
+
+The symbols are accessed as strings through the standard symbol options:
+
+ series: {
+ points: {
+ symbol: "square" // or "diamond", "triangle", "cross"
+ }
+ }
+
+*/
+
+(function ($) {
+ function processRawData(plot, series, datapoints) {
+ // we normalize the area of each symbol so it is approximately the
+ // same as a circle of the given radius
+
+ var handlers = {
+ square: function (ctx, x, y, radius, shadow) {
+ // pi * r^2 = (2s)^2 => s = r * sqrt(pi)/2
+ var size = radius * Math.sqrt(Math.PI) / 2;
+ ctx.rect(x - size, y - size, size + size, size + size);
+ },
+ diamond: function (ctx, x, y, radius, shadow) {
+ // pi * r^2 = 2s^2 => s = r * sqrt(pi/2)
+ var size = radius * Math.sqrt(Math.PI / 2);
+ ctx.moveTo(x - size, y);
+ ctx.lineTo(x, y - size);
+ ctx.lineTo(x + size, y);
+ ctx.lineTo(x, y + size);
+ ctx.lineTo(x - size, y);
+ },
+ triangle: function (ctx, x, y, radius, shadow) {
+ // pi * r^2 = 1/2 * s^2 * sin (pi / 3) => s = r * sqrt(2 * pi / sin(pi / 3))
+ var size = radius * Math.sqrt(2 * Math.PI / Math.sin(Math.PI / 3));
+ var height = size * Math.sin(Math.PI / 3);
+ ctx.moveTo(x - size/2, y + height/2);
+ ctx.lineTo(x + size/2, y + height/2);
+ if (!shadow) {
+ ctx.lineTo(x, y - height/2);
+ ctx.lineTo(x - size/2, y + height/2);
+ }
+ },
+ cross: function (ctx, x, y, radius, shadow) {
+ // pi * r^2 = (2s)^2 => s = r * sqrt(pi)/2
+ var size = radius * Math.sqrt(Math.PI) / 2;
+ ctx.moveTo(x - size, y - size);
+ ctx.lineTo(x + size, y + size);
+ ctx.moveTo(x - size, y + size);
+ ctx.lineTo(x + size, y - size);
+ }
+ };
+
+ var s = series.points.symbol;
+ if (handlers[s])
+ series.points.symbol = handlers[s];
+ }
+
+ function init(plot) {
+ plot.hooks.processDatapoints.push(processRawData);
+ }
+
+ $.plot.plugins.push({
+ init: init,
+ name: 'symbols',
+ version: '1.0'
+ });
+})(jQuery);
diff --git a/djangoproject/static/js/lib/jquery-flot/jquery.flot.threshold.js b/djangoproject/static/js/lib/jquery-flot/jquery.flot.threshold.js
new file mode 100644
index 000000000..8c99c401d
--- /dev/null
+++ b/djangoproject/static/js/lib/jquery-flot/jquery.flot.threshold.js
@@ -0,0 +1,142 @@
+/* Flot plugin for thresholding data.
+
+Copyright (c) 2007-2014 IOLA and Ole Laursen.
+Licensed under the MIT license.
+
+The plugin supports these options:
+
+ series: {
+ threshold: {
+ below: number
+ color: colorspec
+ }
+ }
+
+It can also be applied to a single series, like this:
+
+ $.plot( $("#placeholder"), [{
+ data: [ ... ],
+ threshold: { ... }
+ }])
+
+An array can be passed for multiple thresholding, like this:
+
+ threshold: [{
+ below: number1
+ color: color1
+ },{
+ below: number2
+ color: color2
+ }]
+
+These multiple threshold objects can be passed in any order since they are
+sorted by the processing function.
+
+The data points below "below" are drawn with the specified color. This makes
+it easy to mark points below 0, e.g. for budget data.
+
+Internally, the plugin works by splitting the data into two series, above and
+below the threshold. The extra series below the threshold will have its label
+cleared and the special "originSeries" attribute set to the original series.
+You may need to check for this in hover events.
+
+*/
+
+(function ($) {
+ var options = {
+ series: { threshold: null } // or { below: number, color: color spec}
+ };
+
+ function init(plot) {
+ function thresholdData(plot, s, datapoints, below, color) {
+ var ps = datapoints.pointsize, i, x, y, p, prevp,
+ thresholded = $.extend({}, s); // note: shallow copy
+
+ thresholded.datapoints = { points: [], pointsize: ps, format: datapoints.format };
+ thresholded.label = null;
+ thresholded.color = color;
+ thresholded.threshold = null;
+ thresholded.originSeries = s;
+ thresholded.data = [];
+
+ var origpoints = datapoints.points,
+ addCrossingPoints = s.lines.show;
+
+ var threspoints = [];
+ var newpoints = [];
+ var m;
+
+ for (i = 0; i < origpoints.length; i += ps) {
+ x = origpoints[i];
+ y = origpoints[i + 1];
+
+ prevp = p;
+ if (y < below)
+ p = threspoints;
+ else
+ p = newpoints;
+
+ if (addCrossingPoints && prevp != p && x != null
+ && i > 0 && origpoints[i - ps] != null) {
+ var interx = x + (below - y) * (x - origpoints[i - ps]) / (y - origpoints[i - ps + 1]);
+ prevp.push(interx);
+ prevp.push(below);
+ for (m = 2; m < ps; ++m)
+ prevp.push(origpoints[i + m]);
+
+ p.push(null); // start new segment
+ p.push(null);
+ for (m = 2; m < ps; ++m)
+ p.push(origpoints[i + m]);
+ p.push(interx);
+ p.push(below);
+ for (m = 2; m < ps; ++m)
+ p.push(origpoints[i + m]);
+ }
+
+ p.push(x);
+ p.push(y);
+ for (m = 2; m < ps; ++m)
+ p.push(origpoints[i + m]);
+ }
+
+ datapoints.points = newpoints;
+ thresholded.datapoints.points = threspoints;
+
+ if (thresholded.datapoints.points.length > 0) {
+ var origIndex = $.inArray(s, plot.getData());
+ // Insert newly-generated series right after original one (to prevent it from becoming top-most)
+ plot.getData().splice(origIndex + 1, 0, thresholded);
+ }
+
+ // FIXME: there are probably some edge cases left in bars
+ }
+
+ function processThresholds(plot, s, datapoints) {
+ if (!s.threshold)
+ return;
+
+ if (s.threshold instanceof Array) {
+ s.threshold.sort(function(a, b) {
+ return a.below - b.below;
+ });
+
+ $(s.threshold).each(function(i, th) {
+ thresholdData(plot, s, datapoints, th.below, th.color);
+ });
+ }
+ else {
+ thresholdData(plot, s, datapoints, s.threshold.below, s.threshold.color);
+ }
+ }
+
+ plot.hooks.processDatapoints.push(processThresholds);
+ }
+
+ $.plot.plugins.push({
+ init: init,
+ options: options,
+ name: 'threshold',
+ version: '1.2'
+ });
+})(jQuery);
diff --git a/djangoproject/static/js/lib/jquery-flot/jquery.flot.time.js b/djangoproject/static/js/lib/jquery-flot/jquery.flot.time.js
new file mode 100644
index 000000000..34c1d1212
--- /dev/null
+++ b/djangoproject/static/js/lib/jquery-flot/jquery.flot.time.js
@@ -0,0 +1,432 @@
+/* Pretty handling of time axes.
+
+Copyright (c) 2007-2014 IOLA and Ole Laursen.
+Licensed under the MIT license.
+
+Set axis.mode to "time" to enable. See the section "Time series data" in
+API.txt for details.
+
+*/
+
+(function($) {
+
+ var options = {
+ xaxis: {
+ timezone: null, // "browser" for local to the client or timezone for timezone-js
+ timeformat: null, // format string to use
+ twelveHourClock: false, // 12 or 24 time in time mode
+ monthNames: null // list of names of months
+ }
+ };
+
+ // round to nearby lower multiple of base
+
+ function floorInBase(n, base) {
+ return base * Math.floor(n / base);
+ }
+
+ // Returns a string with the date d formatted according to fmt.
+ // A subset of the Open Group's strftime format is supported.
+
+ function formatDate(d, fmt, monthNames, dayNames) {
+
+ if (typeof d.strftime == "function") {
+ return d.strftime(fmt);
+ }
+
+ var leftPad = function(n, pad) {
+ n = "" + n;
+ pad = "" + (pad == null ? "0" : pad);
+ return n.length == 1 ? pad + n : n;
+ };
+
+ var r = [];
+ var escape = false;
+ var hours = d.getHours();
+ var isAM = hours < 12;
+
+ if (monthNames == null) {
+ monthNames = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"];
+ }
+
+ if (dayNames == null) {
+ dayNames = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"];
+ }
+
+ var hours12;
+
+ if (hours > 12) {
+ hours12 = hours - 12;
+ } else if (hours == 0) {
+ hours12 = 12;
+ } else {
+ hours12 = hours;
+ }
+
+ for (var i = 0; i < fmt.length; ++i) {
+
+ var c = fmt.charAt(i);
+
+ if (escape) {
+ switch (c) {
+ case 'a': c = "" + dayNames[d.getDay()]; break;
+ case 'b': c = "" + monthNames[d.getMonth()]; break;
+ case 'd': c = leftPad(d.getDate()); break;
+ case 'e': c = leftPad(d.getDate(), " "); break;
+ case 'h': // For back-compat with 0.7; remove in 1.0
+ case 'H': c = leftPad(hours); break;
+ case 'I': c = leftPad(hours12); break;
+ case 'l': c = leftPad(hours12, " "); break;
+ case 'm': c = leftPad(d.getMonth() + 1); break;
+ case 'M': c = leftPad(d.getMinutes()); break;
+ // quarters not in Open Group's strftime specification
+ case 'q':
+ c = "" + (Math.floor(d.getMonth() / 3) + 1); break;
+ case 'S': c = leftPad(d.getSeconds()); break;
+ case 'y': c = leftPad(d.getFullYear() % 100); break;
+ case 'Y': c = "" + d.getFullYear(); break;
+ case 'p': c = (isAM) ? ("" + "am") : ("" + "pm"); break;
+ case 'P': c = (isAM) ? ("" + "AM") : ("" + "PM"); break;
+ case 'w': c = "" + d.getDay(); break;
+ }
+ r.push(c);
+ escape = false;
+ } else {
+ if (c == "%") {
+ escape = true;
+ } else {
+ r.push(c);
+ }
+ }
+ }
+
+ return r.join("");
+ }
+
+ // To have a consistent view of time-based data independent of which time
+ // zone the client happens to be in we need a date-like object independent
+ // of time zones. This is done through a wrapper that only calls the UTC
+ // versions of the accessor methods.
+
+ function makeUtcWrapper(d) {
+
+ function addProxyMethod(sourceObj, sourceMethod, targetObj, targetMethod) {
+ sourceObj[sourceMethod] = function() {
+ return targetObj[targetMethod].apply(targetObj, arguments);
+ };
+ };
+
+ var utc = {
+ date: d
+ };
+
+ // support strftime, if found
+
+ if (d.strftime != undefined) {
+ addProxyMethod(utc, "strftime", d, "strftime");
+ }
+
+ addProxyMethod(utc, "getTime", d, "getTime");
+ addProxyMethod(utc, "setTime", d, "setTime");
+
+ var props = ["Date", "Day", "FullYear", "Hours", "Milliseconds", "Minutes", "Month", "Seconds"];
+
+ for (var p = 0; p < props.length; p++) {
+ addProxyMethod(utc, "get" + props[p], d, "getUTC" + props[p]);
+ addProxyMethod(utc, "set" + props[p], d, "setUTC" + props[p]);
+ }
+
+ return utc;
+ };
+
+ // select time zone strategy. This returns a date-like object tied to the
+ // desired timezone
+
+ function dateGenerator(ts, opts) {
+ if (opts.timezone == "browser") {
+ return new Date(ts);
+ } else if (!opts.timezone || opts.timezone == "utc") {
+ return makeUtcWrapper(new Date(ts));
+ } else if (typeof timezoneJS != "undefined" && typeof timezoneJS.Date != "undefined") {
+ var d = new timezoneJS.Date();
+ // timezone-js is fickle, so be sure to set the time zone before
+ // setting the time.
+ d.setTimezone(opts.timezone);
+ d.setTime(ts);
+ return d;
+ } else {
+ return makeUtcWrapper(new Date(ts));
+ }
+ }
+
+ // map of app. size of time units in milliseconds
+
+ var timeUnitSize = {
+ "second": 1000,
+ "minute": 60 * 1000,
+ "hour": 60 * 60 * 1000,
+ "day": 24 * 60 * 60 * 1000,
+ "month": 30 * 24 * 60 * 60 * 1000,
+ "quarter": 3 * 30 * 24 * 60 * 60 * 1000,
+ "year": 365.2425 * 24 * 60 * 60 * 1000
+ };
+
+ // the allowed tick sizes, after 1 year we use
+ // an integer algorithm
+
+ var baseSpec = [
+ [1, "second"], [2, "second"], [5, "second"], [10, "second"],
+ [30, "second"],
+ [1, "minute"], [2, "minute"], [5, "minute"], [10, "minute"],
+ [30, "minute"],
+ [1, "hour"], [2, "hour"], [4, "hour"],
+ [8, "hour"], [12, "hour"],
+ [1, "day"], [2, "day"], [3, "day"],
+ [0.25, "month"], [0.5, "month"], [1, "month"],
+ [2, "month"]
+ ];
+
+ // we don't know which variant(s) we'll need yet, but generating both is
+ // cheap
+
+ var specMonths = baseSpec.concat([[3, "month"], [6, "month"],
+ [1, "year"]]);
+ var specQuarters = baseSpec.concat([[1, "quarter"], [2, "quarter"],
+ [1, "year"]]);
+
+ function init(plot) {
+ plot.hooks.processOptions.push(function (plot, options) {
+ $.each(plot.getAxes(), function(axisName, axis) {
+
+ var opts = axis.options;
+
+ if (opts.mode == "time") {
+ axis.tickGenerator = function(axis) {
+
+ var ticks = [];
+ var d = dateGenerator(axis.min, opts);
+ var minSize = 0;
+
+ // make quarter use a possibility if quarters are
+ // mentioned in either of these options
+
+ var spec = (opts.tickSize && opts.tickSize[1] ===
+ "quarter") ||
+ (opts.minTickSize && opts.minTickSize[1] ===
+ "quarter") ? specQuarters : specMonths;
+
+ if (opts.minTickSize != null) {
+ if (typeof opts.tickSize == "number") {
+ minSize = opts.tickSize;
+ } else {
+ minSize = opts.minTickSize[0] * timeUnitSize[opts.minTickSize[1]];
+ }
+ }
+
+ for (var i = 0; i < spec.length - 1; ++i) {
+ if (axis.delta < (spec[i][0] * timeUnitSize[spec[i][1]]
+ + spec[i + 1][0] * timeUnitSize[spec[i + 1][1]]) / 2
+ && spec[i][0] * timeUnitSize[spec[i][1]] >= minSize) {
+ break;
+ }
+ }
+
+ var size = spec[i][0];
+ var unit = spec[i][1];
+
+ // special-case the possibility of several years
+
+ if (unit == "year") {
+
+ // if given a minTickSize in years, just use it,
+ // ensuring that it's an integer
+
+ if (opts.minTickSize != null && opts.minTickSize[1] == "year") {
+ size = Math.floor(opts.minTickSize[0]);
+ } else {
+
+ var magn = Math.pow(10, Math.floor(Math.log(axis.delta / timeUnitSize.year) / Math.LN10));
+ var norm = (axis.delta / timeUnitSize.year) / magn;
+
+ if (norm < 1.5) {
+ size = 1;
+ } else if (norm < 3) {
+ size = 2;
+ } else if (norm < 7.5) {
+ size = 5;
+ } else {
+ size = 10;
+ }
+
+ size *= magn;
+ }
+
+ // minimum size for years is 1
+
+ if (size < 1) {
+ size = 1;
+ }
+ }
+
+ axis.tickSize = opts.tickSize || [size, unit];
+ var tickSize = axis.tickSize[0];
+ unit = axis.tickSize[1];
+
+ var step = tickSize * timeUnitSize[unit];
+
+ if (unit == "second") {
+ d.setSeconds(floorInBase(d.getSeconds(), tickSize));
+ } else if (unit == "minute") {
+ d.setMinutes(floorInBase(d.getMinutes(), tickSize));
+ } else if (unit == "hour") {
+ d.setHours(floorInBase(d.getHours(), tickSize));
+ } else if (unit == "month") {
+ d.setMonth(floorInBase(d.getMonth(), tickSize));
+ } else if (unit == "quarter") {
+ d.setMonth(3 * floorInBase(d.getMonth() / 3,
+ tickSize));
+ } else if (unit == "year") {
+ d.setFullYear(floorInBase(d.getFullYear(), tickSize));
+ }
+
+ // reset smaller components
+
+ d.setMilliseconds(0);
+
+ if (step >= timeUnitSize.minute) {
+ d.setSeconds(0);
+ }
+ if (step >= timeUnitSize.hour) {
+ d.setMinutes(0);
+ }
+ if (step >= timeUnitSize.day) {
+ d.setHours(0);
+ }
+ if (step >= timeUnitSize.day * 4) {
+ d.setDate(1);
+ }
+ if (step >= timeUnitSize.month * 2) {
+ d.setMonth(floorInBase(d.getMonth(), 3));
+ }
+ if (step >= timeUnitSize.quarter * 2) {
+ d.setMonth(floorInBase(d.getMonth(), 6));
+ }
+ if (step >= timeUnitSize.year) {
+ d.setMonth(0);
+ }
+
+ var carry = 0;
+ var v = Number.NaN;
+ var prev;
+
+ do {
+
+ prev = v;
+ v = d.getTime();
+ ticks.push(v);
+
+ if (unit == "month" || unit == "quarter") {
+ if (tickSize < 1) {
+
+ // a bit complicated - we'll divide the
+ // month/quarter up but we need to take
+ // care of fractions so we don't end up in
+ // the middle of a day
+
+ d.setDate(1);
+ var start = d.getTime();
+ d.setMonth(d.getMonth() +
+ (unit == "quarter" ? 3 : 1));
+ var end = d.getTime();
+ d.setTime(v + carry * timeUnitSize.hour + (end - start) * tickSize);
+ carry = d.getHours();
+ d.setHours(0);
+ } else {
+ d.setMonth(d.getMonth() +
+ tickSize * (unit == "quarter" ? 3 : 1));
+ }
+ } else if (unit == "year") {
+ d.setFullYear(d.getFullYear() + tickSize);
+ } else {
+ d.setTime(v + step);
+ }
+ } while (v < axis.max && v != prev);
+
+ return ticks;
+ };
+
+ axis.tickFormatter = function (v, axis) {
+
+ var d = dateGenerator(v, axis.options);
+
+ // first check global format
+
+ if (opts.timeformat != null) {
+ return formatDate(d, opts.timeformat, opts.monthNames, opts.dayNames);
+ }
+
+ // possibly use quarters if quarters are mentioned in
+ // any of these places
+
+ var useQuarters = (axis.options.tickSize &&
+ axis.options.tickSize[1] == "quarter") ||
+ (axis.options.minTickSize &&
+ axis.options.minTickSize[1] == "quarter");
+
+ var t = axis.tickSize[0] * timeUnitSize[axis.tickSize[1]];
+ var span = axis.max - axis.min;
+ var suffix = (opts.twelveHourClock) ? " %p" : "";
+ var hourCode = (opts.twelveHourClock) ? "%I" : "%H";
+ var fmt;
+
+ if (t < timeUnitSize.minute) {
+ fmt = hourCode + ":%M:%S" + suffix;
+ } else if (t < timeUnitSize.day) {
+ if (span < 2 * timeUnitSize.day) {
+ fmt = hourCode + ":%M" + suffix;
+ } else {
+ fmt = "%b %d " + hourCode + ":%M" + suffix;
+ }
+ } else if (t < timeUnitSize.month) {
+ fmt = "%b %d";
+ } else if ((useQuarters && t < timeUnitSize.quarter) ||
+ (!useQuarters && t < timeUnitSize.year)) {
+ if (span < timeUnitSize.year) {
+ fmt = "%b";
+ } else {
+ fmt = "%b %Y";
+ }
+ } else if (useQuarters && t < timeUnitSize.year) {
+ if (span < timeUnitSize.year) {
+ fmt = "Q%q";
+ } else {
+ fmt = "Q%q %Y";
+ }
+ } else {
+ fmt = "%Y";
+ }
+
+ var rt = formatDate(d, fmt, opts.monthNames, opts.dayNames);
+
+ return rt;
+ };
+ }
+ });
+ });
+ }
+
+ $.plot.plugins.push({
+ init: init,
+ options: options,
+ name: 'time',
+ version: '1.0'
+ });
+
+ // Time-axis support used to be in Flot core, which exposed the
+ // formatDate function on the plot object. Various plugins depend
+ // on the function, so we need to re-expose it here.
+
+ $.plot.formatDate = formatDate;
+ $.plot.dateGenerator = dateGenerator;
+
+})(jQuery);
diff --git a/djangoproject/static/js/lib/jquery-flot/jquery.js b/djangoproject/static/js/lib/jquery-flot/jquery.js
new file mode 100644
index 000000000..8c24ffc61
--- /dev/null
+++ b/djangoproject/static/js/lib/jquery-flot/jquery.js
@@ -0,0 +1,9472 @@
+/*!
+ * jQuery JavaScript Library v1.8.3
+ * http://jquery.com/
+ *
+ * Includes Sizzle.js
+ * http://sizzlejs.com/
+ *
+ * Copyright 2012 jQuery Foundation and other contributors
+ * Released under the MIT license
+ * http://jquery.org/license
+ *
+ * Date: Tue Nov 13 2012 08:20:33 GMT-0500 (Eastern Standard Time)
+ */
+(function( window, undefined ) {
+var
+ // A central reference to the root jQuery(document)
+ rootjQuery,
+
+ // The deferred used on DOM ready
+ readyList,
+
+ // Use the correct document accordingly with window argument (sandbox)
+ document = window.document,
+ location = window.location,
+ navigator = window.navigator,
+
+ // Map over jQuery in case of overwrite
+ _jQuery = window.jQuery,
+
+ // Map over the $ in case of overwrite
+ _$ = window.$,
+
+ // Save a reference to some core methods
+ core_push = Array.prototype.push,
+ core_slice = Array.prototype.slice,
+ core_indexOf = Array.prototype.indexOf,
+ core_toString = Object.prototype.toString,
+ core_hasOwn = Object.prototype.hasOwnProperty,
+ core_trim = String.prototype.trim,
+
+ // Define a local copy of jQuery
+ jQuery = function( selector, context ) {
+ // The jQuery object is actually just the init constructor 'enhanced'
+ return new jQuery.fn.init( selector, context, rootjQuery );
+ },
+
+ // Used for matching numbers
+ core_pnum = /[\-+]?(?:\d*\.|)\d+(?:[eE][\-+]?\d+|)/.source,
+
+ // Used for detecting and trimming whitespace
+ core_rnotwhite = /\S/,
+ core_rspace = /\s+/,
+
+ // Make sure we trim BOM and NBSP (here's looking at you, Safari 5.0 and IE)
+ rtrim = /^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g,
+
+ // A simple way to check for HTML strings
+ // Prioritize #id over to avoid XSS via location.hash (#9521)
+ rquickExpr = /^(?:[^#<]*(<[\w\W]+>)[^>]*$|#([\w\-]*)$)/,
+
+ // Match a standalone tag
+ rsingleTag = /^<(\w+)\s*\/?>(?:<\/\1>|)$/,
+
+ // JSON RegExp
+ rvalidchars = /^[\],:{}\s]*$/,
+ rvalidbraces = /(?:^|:|,)(?:\s*\[)+/g,
+ rvalidescape = /\\(?:["\\\/bfnrt]|u[\da-fA-F]{4})/g,
+ rvalidtokens = /"[^"\\\r\n]*"|true|false|null|-?(?:\d\d*\.|)\d+(?:[eE][\-+]?\d+|)/g,
+
+ // Matches dashed string for camelizing
+ rmsPrefix = /^-ms-/,
+ rdashAlpha = /-([\da-z])/gi,
+
+ // Used by jQuery.camelCase as callback to replace()
+ fcamelCase = function( all, letter ) {
+ return ( letter + "" ).toUpperCase();
+ },
+
+ // The ready event handler and self cleanup method
+ DOMContentLoaded = function() {
+ if ( document.addEventListener ) {
+ document.removeEventListener( "DOMContentLoaded", DOMContentLoaded, false );
+ jQuery.ready();
+ } else if ( document.readyState === "complete" ) {
+ // we're here because readyState === "complete" in oldIE
+ // which is good enough for us to call the dom ready!
+ document.detachEvent( "onreadystatechange", DOMContentLoaded );
+ jQuery.ready();
+ }
+ },
+
+ // [[Class]] -> type pairs
+ class2type = {};
+
+jQuery.fn = jQuery.prototype = {
+ constructor: jQuery,
+ init: function( selector, context, rootjQuery ) {
+ var match, elem, ret, doc;
+
+ // Handle $(""), $(null), $(undefined), $(false)
+ if ( !selector ) {
+ return this;
+ }
+
+ // Handle $(DOMElement)
+ if ( selector.nodeType ) {
+ this.context = this[0] = selector;
+ this.length = 1;
+ return this;
+ }
+
+ // Handle HTML strings
+ if ( typeof selector === "string" ) {
+ if ( selector.charAt(0) === "<" && selector.charAt( selector.length - 1 ) === ">" && selector.length >= 3 ) {
+ // Assume that strings that start and end with <> are HTML and skip the regex check
+ match = [ null, selector, null ];
+
+ } else {
+ match = rquickExpr.exec( selector );
+ }
+
+ // Match html or make sure no context is specified for #id
+ if ( match && (match[1] || !context) ) {
+
+ // HANDLE: $(html) -> $(array)
+ if ( match[1] ) {
+ context = context instanceof jQuery ? context[0] : context;
+ doc = ( context && context.nodeType ? context.ownerDocument || context : document );
+
+ // scripts is true for back-compat
+ selector = jQuery.parseHTML( match[1], doc, true );
+ if ( rsingleTag.test( match[1] ) && jQuery.isPlainObject( context ) ) {
+ this.attr.call( selector, context, true );
+ }
+
+ return jQuery.merge( this, selector );
+
+ // HANDLE: $(#id)
+ } else {
+ elem = document.getElementById( match[2] );
+
+ // Check parentNode to catch when Blackberry 4.6 returns
+ // nodes that are no longer in the document #6963
+ if ( elem && elem.parentNode ) {
+ // Handle the case where IE and Opera return items
+ // by name instead of ID
+ if ( elem.id !== match[2] ) {
+ return rootjQuery.find( selector );
+ }
+
+ // Otherwise, we inject the element directly into the jQuery object
+ this.length = 1;
+ this[0] = elem;
+ }
+
+ this.context = document;
+ this.selector = selector;
+ return this;
+ }
+
+ // HANDLE: $(expr, $(...))
+ } else if ( !context || context.jquery ) {
+ return ( context || rootjQuery ).find( selector );
+
+ // HANDLE: $(expr, context)
+ // (which is just equivalent to: $(context).find(expr)
+ } else {
+ return this.constructor( context ).find( selector );
+ }
+
+ // HANDLE: $(function)
+ // Shortcut for document ready
+ } else if ( jQuery.isFunction( selector ) ) {
+ return rootjQuery.ready( selector );
+ }
+
+ if ( selector.selector !== undefined ) {
+ this.selector = selector.selector;
+ this.context = selector.context;
+ }
+
+ return jQuery.makeArray( selector, this );
+ },
+
+ // Start with an empty selector
+ selector: "",
+
+ // The current version of jQuery being used
+ jquery: "1.8.3",
+
+ // The default length of a jQuery object is 0
+ length: 0,
+
+ // The number of elements contained in the matched element set
+ size: function() {
+ return this.length;
+ },
+
+ toArray: function() {
+ return core_slice.call( this );
+ },
+
+ // Get the Nth element in the matched element set OR
+ // Get the whole matched element set as a clean array
+ get: function( num ) {
+ return num == null ?
+
+ // Return a 'clean' array
+ this.toArray() :
+
+ // Return just the object
+ ( num < 0 ? this[ this.length + num ] : this[ num ] );
+ },
+
+ // Take an array of elements and push it onto the stack
+ // (returning the new matched element set)
+ pushStack: function( elems, name, selector ) {
+
+ // Build a new jQuery matched element set
+ var ret = jQuery.merge( this.constructor(), elems );
+
+ // Add the old object onto the stack (as a reference)
+ ret.prevObject = this;
+
+ ret.context = this.context;
+
+ if ( name === "find" ) {
+ ret.selector = this.selector + ( this.selector ? " " : "" ) + selector;
+ } else if ( name ) {
+ ret.selector = this.selector + "." + name + "(" + selector + ")";
+ }
+
+ // Return the newly-formed element set
+ return ret;
+ },
+
+ // Execute a callback for every element in the matched set.
+ // (You can seed the arguments with an array of args, but this is
+ // only used internally.)
+ each: function( callback, args ) {
+ return jQuery.each( this, callback, args );
+ },
+
+ ready: function( fn ) {
+ // Add the callback
+ jQuery.ready.promise().done( fn );
+
+ return this;
+ },
+
+ eq: function( i ) {
+ i = +i;
+ return i === -1 ?
+ this.slice( i ) :
+ this.slice( i, i + 1 );
+ },
+
+ first: function() {
+ return this.eq( 0 );
+ },
+
+ last: function() {
+ return this.eq( -1 );
+ },
+
+ slice: function() {
+ return this.pushStack( core_slice.apply( this, arguments ),
+ "slice", core_slice.call(arguments).join(",") );
+ },
+
+ map: function( callback ) {
+ return this.pushStack( jQuery.map(this, function( elem, i ) {
+ return callback.call( elem, i, elem );
+ }));
+ },
+
+ end: function() {
+ return this.prevObject || this.constructor(null);
+ },
+
+ // For internal use only.
+ // Behaves like an Array's method, not like a jQuery method.
+ push: core_push,
+ sort: [].sort,
+ splice: [].splice
+};
+
+// Give the init function the jQuery prototype for later instantiation
+jQuery.fn.init.prototype = jQuery.fn;
+
+jQuery.extend = jQuery.fn.extend = function() {
+ var options, name, src, copy, copyIsArray, clone,
+ target = arguments[0] || {},
+ i = 1,
+ length = arguments.length,
+ deep = false;
+
+ // Handle a deep copy situation
+ if ( typeof target === "boolean" ) {
+ deep = target;
+ target = arguments[1] || {};
+ // skip the boolean and the target
+ i = 2;
+ }
+
+ // Handle case when target is a string or something (possible in deep copy)
+ if ( typeof target !== "object" && !jQuery.isFunction(target) ) {
+ target = {};
+ }
+
+ // extend jQuery itself if only one argument is passed
+ if ( length === i ) {
+ target = this;
+ --i;
+ }
+
+ for ( ; i < length; i++ ) {
+ // Only deal with non-null/undefined values
+ if ( (options = arguments[ i ]) != null ) {
+ // Extend the base object
+ for ( name in options ) {
+ src = target[ name ];
+ copy = options[ name ];
+
+ // Prevent never-ending loop
+ if ( target === copy ) {
+ continue;
+ }
+
+ // Recurse if we're merging plain objects or arrays
+ if ( deep && copy && ( jQuery.isPlainObject(copy) || (copyIsArray = jQuery.isArray(copy)) ) ) {
+ if ( copyIsArray ) {
+ copyIsArray = false;
+ clone = src && jQuery.isArray(src) ? src : [];
+
+ } else {
+ clone = src && jQuery.isPlainObject(src) ? src : {};
+ }
+
+ // Never move original objects, clone them
+ target[ name ] = jQuery.extend( deep, clone, copy );
+
+ // Don't bring in undefined values
+ } else if ( copy !== undefined ) {
+ target[ name ] = copy;
+ }
+ }
+ }
+ }
+
+ // Return the modified object
+ return target;
+};
+
+jQuery.extend({
+ noConflict: function( deep ) {
+ if ( window.$ === jQuery ) {
+ window.$ = _$;
+ }
+
+ if ( deep && window.jQuery === jQuery ) {
+ window.jQuery = _jQuery;
+ }
+
+ return jQuery;
+ },
+
+ // Is the DOM ready to be used? Set to true once it occurs.
+ isReady: false,
+
+ // A counter to track how many items to wait for before
+ // the ready event fires. See #6781
+ readyWait: 1,
+
+ // Hold (or release) the ready event
+ holdReady: function( hold ) {
+ if ( hold ) {
+ jQuery.readyWait++;
+ } else {
+ jQuery.ready( true );
+ }
+ },
+
+ // Handle when the DOM is ready
+ ready: function( wait ) {
+
+ // Abort if there are pending holds or we're already ready
+ if ( wait === true ? --jQuery.readyWait : jQuery.isReady ) {
+ return;
+ }
+
+ // Make sure body exists, at least, in case IE gets a little overzealous (ticket #5443).
+ if ( !document.body ) {
+ return setTimeout( jQuery.ready, 1 );
+ }
+
+ // Remember that the DOM is ready
+ jQuery.isReady = true;
+
+ // If a normal DOM Ready event fired, decrement, and wait if need be
+ if ( wait !== true && --jQuery.readyWait > 0 ) {
+ return;
+ }
+
+ // If there are functions bound, to execute
+ readyList.resolveWith( document, [ jQuery ] );
+
+ // Trigger any bound ready events
+ if ( jQuery.fn.trigger ) {
+ jQuery( document ).trigger("ready").off("ready");
+ }
+ },
+
+ // See test/unit/core.js for details concerning isFunction.
+ // Since version 1.3, DOM methods and functions like alert
+ // aren't supported. They return false on IE (#2968).
+ isFunction: function( obj ) {
+ return jQuery.type(obj) === "function";
+ },
+
+ isArray: Array.isArray || function( obj ) {
+ return jQuery.type(obj) === "array";
+ },
+
+ isWindow: function( obj ) {
+ return obj != null && obj == obj.window;
+ },
+
+ isNumeric: function( obj ) {
+ return !isNaN( parseFloat(obj) ) && isFinite( obj );
+ },
+
+ type: function( obj ) {
+ return obj == null ?
+ String( obj ) :
+ class2type[ core_toString.call(obj) ] || "object";
+ },
+
+ isPlainObject: function( obj ) {
+ // Must be an Object.
+ // Because of IE, we also have to check the presence of the constructor property.
+ // Make sure that DOM nodes and window objects don't pass through, as well
+ if ( !obj || jQuery.type(obj) !== "object" || obj.nodeType || jQuery.isWindow( obj ) ) {
+ return false;
+ }
+
+ try {
+ // Not own constructor property must be Object
+ if ( obj.constructor &&
+ !core_hasOwn.call(obj, "constructor") &&
+ !core_hasOwn.call(obj.constructor.prototype, "isPrototypeOf") ) {
+ return false;
+ }
+ } catch ( e ) {
+ // IE8,9 Will throw exceptions on certain host objects #9897
+ return false;
+ }
+
+ // Own properties are enumerated firstly, so to speed up,
+ // if last one is own, then all properties are own.
+
+ var key;
+ for ( key in obj ) {}
+
+ return key === undefined || core_hasOwn.call( obj, key );
+ },
+
+ isEmptyObject: function( obj ) {
+ var name;
+ for ( name in obj ) {
+ return false;
+ }
+ return true;
+ },
+
+ error: function( msg ) {
+ throw new Error( msg );
+ },
+
+ // data: string of html
+ // context (optional): If specified, the fragment will be created in this context, defaults to document
+ // scripts (optional): If true, will include scripts passed in the html string
+ parseHTML: function( data, context, scripts ) {
+ var parsed;
+ if ( !data || typeof data !== "string" ) {
+ return null;
+ }
+ if ( typeof context === "boolean" ) {
+ scripts = context;
+ context = 0;
+ }
+ context = context || document;
+
+ // Single tag
+ if ( (parsed = rsingleTag.exec( data )) ) {
+ return [ context.createElement( parsed[1] ) ];
+ }
+
+ parsed = jQuery.buildFragment( [ data ], context, scripts ? null : [] );
+ return jQuery.merge( [],
+ (parsed.cacheable ? jQuery.clone( parsed.fragment ) : parsed.fragment).childNodes );
+ },
+
+ parseJSON: function( data ) {
+ if ( !data || typeof data !== "string") {
+ return null;
+ }
+
+ // Make sure leading/trailing whitespace is removed (IE can't handle it)
+ data = jQuery.trim( data );
+
+ // Attempt to parse using the native JSON parser first
+ if ( window.JSON && window.JSON.parse ) {
+ return window.JSON.parse( data );
+ }
+
+ // Make sure the incoming data is actual JSON
+ // Logic borrowed from http://json.org/json2.js
+ if ( rvalidchars.test( data.replace( rvalidescape, "@" )
+ .replace( rvalidtokens, "]" )
+ .replace( rvalidbraces, "")) ) {
+
+ return ( new Function( "return " + data ) )();
+
+ }
+ jQuery.error( "Invalid JSON: " + data );
+ },
+
+ // Cross-browser xml parsing
+ parseXML: function( data ) {
+ var xml, tmp;
+ if ( !data || typeof data !== "string" ) {
+ return null;
+ }
+ try {
+ if ( window.DOMParser ) { // Standard
+ tmp = new DOMParser();
+ xml = tmp.parseFromString( data , "text/xml" );
+ } else { // IE
+ xml = new ActiveXObject( "Microsoft.XMLDOM" );
+ xml.async = "false";
+ xml.loadXML( data );
+ }
+ } catch( e ) {
+ xml = undefined;
+ }
+ if ( !xml || !xml.documentElement || xml.getElementsByTagName( "parsererror" ).length ) {
+ jQuery.error( "Invalid XML: " + data );
+ }
+ return xml;
+ },
+
+ noop: function() {},
+
+ // Evaluates a script in a global context
+ // Workarounds based on findings by Jim Driscoll
+ // http://weblogs.java.net/blog/driscoll/archive/2009/09/08/eval-javascript-global-context
+ globalEval: function( data ) {
+ if ( data && core_rnotwhite.test( data ) ) {
+ // We use execScript on Internet Explorer
+ // We use an anonymous function so that context is window
+ // rather than jQuery in Firefox
+ ( window.execScript || function( data ) {
+ window[ "eval" ].call( window, data );
+ } )( data );
+ }
+ },
+
+ // Convert dashed to camelCase; used by the css and data modules
+ // Microsoft forgot to hump their vendor prefix (#9572)
+ camelCase: function( string ) {
+ return string.replace( rmsPrefix, "ms-" ).replace( rdashAlpha, fcamelCase );
+ },
+
+ nodeName: function( elem, name ) {
+ return elem.nodeName && elem.nodeName.toLowerCase() === name.toLowerCase();
+ },
+
+ // args is for internal usage only
+ each: function( obj, callback, args ) {
+ var name,
+ i = 0,
+ length = obj.length,
+ isObj = length === undefined || jQuery.isFunction( obj );
+
+ if ( args ) {
+ if ( isObj ) {
+ for ( name in obj ) {
+ if ( callback.apply( obj[ name ], args ) === false ) {
+ break;
+ }
+ }
+ } else {
+ for ( ; i < length; ) {
+ if ( callback.apply( obj[ i++ ], args ) === false ) {
+ break;
+ }
+ }
+ }
+
+ // A special, fast, case for the most common use of each
+ } else {
+ if ( isObj ) {
+ for ( name in obj ) {
+ if ( callback.call( obj[ name ], name, obj[ name ] ) === false ) {
+ break;
+ }
+ }
+ } else {
+ for ( ; i < length; ) {
+ if ( callback.call( obj[ i ], i, obj[ i++ ] ) === false ) {
+ break;
+ }
+ }
+ }
+ }
+
+ return obj;
+ },
+
+ // Use native String.trim function wherever possible
+ trim: core_trim && !core_trim.call("\uFEFF\xA0") ?
+ function( text ) {
+ return text == null ?
+ "" :
+ core_trim.call( text );
+ } :
+
+ // Otherwise use our own trimming functionality
+ function( text ) {
+ return text == null ?
+ "" :
+ ( text + "" ).replace( rtrim, "" );
+ },
+
+ // results is for internal usage only
+ makeArray: function( arr, results ) {
+ var type,
+ ret = results || [];
+
+ if ( arr != null ) {
+ // The window, strings (and functions) also have 'length'
+ // Tweaked logic slightly to handle Blackberry 4.7 RegExp issues #6930
+ type = jQuery.type( arr );
+
+ if ( arr.length == null || type === "string" || type === "function" || type === "regexp" || jQuery.isWindow( arr ) ) {
+ core_push.call( ret, arr );
+ } else {
+ jQuery.merge( ret, arr );
+ }
+ }
+
+ return ret;
+ },
+
+ inArray: function( elem, arr, i ) {
+ var len;
+
+ if ( arr ) {
+ if ( core_indexOf ) {
+ return core_indexOf.call( arr, elem, i );
+ }
+
+ len = arr.length;
+ i = i ? i < 0 ? Math.max( 0, len + i ) : i : 0;
+
+ for ( ; i < len; i++ ) {
+ // Skip accessing in sparse arrays
+ if ( i in arr && arr[ i ] === elem ) {
+ return i;
+ }
+ }
+ }
+
+ return -1;
+ },
+
+ merge: function( first, second ) {
+ var l = second.length,
+ i = first.length,
+ j = 0;
+
+ if ( typeof l === "number" ) {
+ for ( ; j < l; j++ ) {
+ first[ i++ ] = second[ j ];
+ }
+
+ } else {
+ while ( second[j] !== undefined ) {
+ first[ i++ ] = second[ j++ ];
+ }
+ }
+
+ first.length = i;
+
+ return first;
+ },
+
+ grep: function( elems, callback, inv ) {
+ var retVal,
+ ret = [],
+ i = 0,
+ length = elems.length;
+ inv = !!inv;
+
+ // Go through the array, only saving the items
+ // that pass the validator function
+ for ( ; i < length; i++ ) {
+ retVal = !!callback( elems[ i ], i );
+ if ( inv !== retVal ) {
+ ret.push( elems[ i ] );
+ }
+ }
+
+ return ret;
+ },
+
+ // arg is for internal usage only
+ map: function( elems, callback, arg ) {
+ var value, key,
+ ret = [],
+ i = 0,
+ length = elems.length,
+ // jquery objects are treated as arrays
+ isArray = elems instanceof jQuery || length !== undefined && typeof length === "number" && ( ( length > 0 && elems[ 0 ] && elems[ length -1 ] ) || length === 0 || jQuery.isArray( elems ) ) ;
+
+ // Go through the array, translating each of the items to their
+ if ( isArray ) {
+ for ( ; i < length; i++ ) {
+ value = callback( elems[ i ], i, arg );
+
+ if ( value != null ) {
+ ret[ ret.length ] = value;
+ }
+ }
+
+ // Go through every key on the object,
+ } else {
+ for ( key in elems ) {
+ value = callback( elems[ key ], key, arg );
+
+ if ( value != null ) {
+ ret[ ret.length ] = value;
+ }
+ }
+ }
+
+ // Flatten any nested arrays
+ return ret.concat.apply( [], ret );
+ },
+
+ // A global GUID counter for objects
+ guid: 1,
+
+ // Bind a function to a context, optionally partially applying any
+ // arguments.
+ proxy: function( fn, context ) {
+ var tmp, args, proxy;
+
+ if ( typeof context === "string" ) {
+ tmp = fn[ context ];
+ context = fn;
+ fn = tmp;
+ }
+
+ // Quick check to determine if target is callable, in the spec
+ // this throws a TypeError, but we will just return undefined.
+ if ( !jQuery.isFunction( fn ) ) {
+ return undefined;
+ }
+
+ // Simulated bind
+ args = core_slice.call( arguments, 2 );
+ proxy = function() {
+ return fn.apply( context, args.concat( core_slice.call( arguments ) ) );
+ };
+
+ // Set the guid of unique handler to the same of original handler, so it can be removed
+ proxy.guid = fn.guid = fn.guid || jQuery.guid++;
+
+ return proxy;
+ },
+
+ // Multifunctional method to get and set values of a collection
+ // The value/s can optionally be executed if it's a function
+ access: function( elems, fn, key, value, chainable, emptyGet, pass ) {
+ var exec,
+ bulk = key == null,
+ i = 0,
+ length = elems.length;
+
+ // Sets many values
+ if ( key && typeof key === "object" ) {
+ for ( i in key ) {
+ jQuery.access( elems, fn, i, key[i], 1, emptyGet, value );
+ }
+ chainable = 1;
+
+ // Sets one value
+ } else if ( value !== undefined ) {
+ // Optionally, function values get executed if exec is true
+ exec = pass === undefined && jQuery.isFunction( value );
+
+ if ( bulk ) {
+ // Bulk operations only iterate when executing function values
+ if ( exec ) {
+ exec = fn;
+ fn = function( elem, key, value ) {
+ return exec.call( jQuery( elem ), value );
+ };
+
+ // Otherwise they run against the entire set
+ } else {
+ fn.call( elems, value );
+ fn = null;
+ }
+ }
+
+ if ( fn ) {
+ for (; i < length; i++ ) {
+ fn( elems[i], key, exec ? value.call( elems[i], i, fn( elems[i], key ) ) : value, pass );
+ }
+ }
+
+ chainable = 1;
+ }
+
+ return chainable ?
+ elems :
+
+ // Gets
+ bulk ?
+ fn.call( elems ) :
+ length ? fn( elems[0], key ) : emptyGet;
+ },
+
+ now: function() {
+ return ( new Date() ).getTime();
+ }
+});
+
+jQuery.ready.promise = function( obj ) {
+ if ( !readyList ) {
+
+ readyList = jQuery.Deferred();
+
+ // Catch cases where $(document).ready() is called after the browser event has already occurred.
+ // we once tried to use readyState "interactive" here, but it caused issues like the one
+ // discovered by ChrisS here: http://bugs.jquery.com/ticket/12282#comment:15
+ if ( document.readyState === "complete" ) {
+ // Handle it asynchronously to allow scripts the opportunity to delay ready
+ setTimeout( jQuery.ready, 1 );
+
+ // Standards-based browsers support DOMContentLoaded
+ } else if ( document.addEventListener ) {
+ // Use the handy event callback
+ document.addEventListener( "DOMContentLoaded", DOMContentLoaded, false );
+
+ // A fallback to window.onload, that will always work
+ window.addEventListener( "load", jQuery.ready, false );
+
+ // If IE event model is used
+ } else {
+ // Ensure firing before onload, maybe late but safe also for iframes
+ document.attachEvent( "onreadystatechange", DOMContentLoaded );
+
+ // A fallback to window.onload, that will always work
+ window.attachEvent( "onload", jQuery.ready );
+
+ // If IE and not a frame
+ // continually check to see if the document is ready
+ var top = false;
+
+ try {
+ top = window.frameElement == null && document.documentElement;
+ } catch(e) {}
+
+ if ( top && top.doScroll ) {
+ (function doScrollCheck() {
+ if ( !jQuery.isReady ) {
+
+ try {
+ // Use the trick by Diego Perini
+ // http://javascript.nwbox.com/IEContentLoaded/
+ top.doScroll("left");
+ } catch(e) {
+ return setTimeout( doScrollCheck, 50 );
+ }
+
+ // and execute any waiting functions
+ jQuery.ready();
+ }
+ })();
+ }
+ }
+ }
+ return readyList.promise( obj );
+};
+
+// Populate the class2type map
+jQuery.each("Boolean Number String Function Array Date RegExp Object".split(" "), function(i, name) {
+ class2type[ "[object " + name + "]" ] = name.toLowerCase();
+});
+
+// All jQuery objects should point back to these
+rootjQuery = jQuery(document);
+// String to Object options format cache
+var optionsCache = {};
+
+// Convert String-formatted options into Object-formatted ones and store in cache
+function createOptions( options ) {
+ var object = optionsCache[ options ] = {};
+ jQuery.each( options.split( core_rspace ), function( _, flag ) {
+ object[ flag ] = true;
+ });
+ return object;
+}
+
+/*
+ * Create a callback list using the following parameters:
+ *
+ * options: an optional list of space-separated options that will change how
+ * the callback list behaves or a more traditional option object
+ *
+ * By default a callback list will act like an event callback list and can be
+ * "fired" multiple times.
+ *
+ * Possible options:
+ *
+ * once: will ensure the callback list can only be fired once (like a Deferred)
+ *
+ * memory: will keep track of previous values and will call any callback added
+ * after the list has been fired right away with the latest "memorized"
+ * values (like a Deferred)
+ *
+ * unique: will ensure a callback can only be added once (no duplicate in the list)
+ *
+ * stopOnFalse: interrupt callings when a callback returns false
+ *
+ */
+jQuery.Callbacks = function( options ) {
+
+ // Convert options from String-formatted to Object-formatted if needed
+ // (we check in cache first)
+ options = typeof options === "string" ?
+ ( optionsCache[ options ] || createOptions( options ) ) :
+ jQuery.extend( {}, options );
+
+ var // Last fire value (for non-forgettable lists)
+ memory,
+ // Flag to know if list was already fired
+ fired,
+ // Flag to know if list is currently firing
+ firing,
+ // First callback to fire (used internally by add and fireWith)
+ firingStart,
+ // End of the loop when firing
+ firingLength,
+ // Index of currently firing callback (modified by remove if needed)
+ firingIndex,
+ // Actual callback list
+ list = [],
+ // Stack of fire calls for repeatable lists
+ stack = !options.once && [],
+ // Fire callbacks
+ fire = function( data ) {
+ memory = options.memory && data;
+ fired = true;
+ firingIndex = firingStart || 0;
+ firingStart = 0;
+ firingLength = list.length;
+ firing = true;
+ for ( ; list && firingIndex < firingLength; firingIndex++ ) {
+ if ( list[ firingIndex ].apply( data[ 0 ], data[ 1 ] ) === false && options.stopOnFalse ) {
+ memory = false; // To prevent further calls using add
+ break;
+ }
+ }
+ firing = false;
+ if ( list ) {
+ if ( stack ) {
+ if ( stack.length ) {
+ fire( stack.shift() );
+ }
+ } else if ( memory ) {
+ list = [];
+ } else {
+ self.disable();
+ }
+ }
+ },
+ // Actual Callbacks object
+ self = {
+ // Add a callback or a collection of callbacks to the list
+ add: function() {
+ if ( list ) {
+ // First, we save the current length
+ var start = list.length;
+ (function add( args ) {
+ jQuery.each( args, function( _, arg ) {
+ var type = jQuery.type( arg );
+ if ( type === "function" ) {
+ if ( !options.unique || !self.has( arg ) ) {
+ list.push( arg );
+ }
+ } else if ( arg && arg.length && type !== "string" ) {
+ // Inspect recursively
+ add( arg );
+ }
+ });
+ })( arguments );
+ // Do we need to add the callbacks to the
+ // current firing batch?
+ if ( firing ) {
+ firingLength = list.length;
+ // With memory, if we're not firing then
+ // we should call right away
+ } else if ( memory ) {
+ firingStart = start;
+ fire( memory );
+ }
+ }
+ return this;
+ },
+ // Remove a callback from the list
+ remove: function() {
+ if ( list ) {
+ jQuery.each( arguments, function( _, arg ) {
+ var index;
+ while( ( index = jQuery.inArray( arg, list, index ) ) > -1 ) {
+ list.splice( index, 1 );
+ // Handle firing indexes
+ if ( firing ) {
+ if ( index <= firingLength ) {
+ firingLength--;
+ }
+ if ( index <= firingIndex ) {
+ firingIndex--;
+ }
+ }
+ }
+ });
+ }
+ return this;
+ },
+ // Control if a given callback is in the list
+ has: function( fn ) {
+ return jQuery.inArray( fn, list ) > -1;
+ },
+ // Remove all callbacks from the list
+ empty: function() {
+ list = [];
+ return this;
+ },
+ // Have the list do nothing anymore
+ disable: function() {
+ list = stack = memory = undefined;
+ return this;
+ },
+ // Is it disabled?
+ disabled: function() {
+ return !list;
+ },
+ // Lock the list in its current state
+ lock: function() {
+ stack = undefined;
+ if ( !memory ) {
+ self.disable();
+ }
+ return this;
+ },
+ // Is it locked?
+ locked: function() {
+ return !stack;
+ },
+ // Call all callbacks with the given context and arguments
+ fireWith: function( context, args ) {
+ args = args || [];
+ args = [ context, args.slice ? args.slice() : args ];
+ if ( list && ( !fired || stack ) ) {
+ if ( firing ) {
+ stack.push( args );
+ } else {
+ fire( args );
+ }
+ }
+ return this;
+ },
+ // Call all the callbacks with the given arguments
+ fire: function() {
+ self.fireWith( this, arguments );
+ return this;
+ },
+ // To know if the callbacks have already been called at least once
+ fired: function() {
+ return !!fired;
+ }
+ };
+
+ return self;
+};
+jQuery.extend({
+
+ Deferred: function( func ) {
+ var tuples = [
+ // action, add listener, listener list, final state
+ [ "resolve", "done", jQuery.Callbacks("once memory"), "resolved" ],
+ [ "reject", "fail", jQuery.Callbacks("once memory"), "rejected" ],
+ [ "notify", "progress", jQuery.Callbacks("memory") ]
+ ],
+ state = "pending",
+ promise = {
+ state: function() {
+ return state;
+ },
+ always: function() {
+ deferred.done( arguments ).fail( arguments );
+ return this;
+ },
+ then: function( /* fnDone, fnFail, fnProgress */ ) {
+ var fns = arguments;
+ return jQuery.Deferred(function( newDefer ) {
+ jQuery.each( tuples, function( i, tuple ) {
+ var action = tuple[ 0 ],
+ fn = fns[ i ];
+ // deferred[ done | fail | progress ] for forwarding actions to newDefer
+ deferred[ tuple[1] ]( jQuery.isFunction( fn ) ?
+ function() {
+ var returned = fn.apply( this, arguments );
+ if ( returned && jQuery.isFunction( returned.promise ) ) {
+ returned.promise()
+ .done( newDefer.resolve )
+ .fail( newDefer.reject )
+ .progress( newDefer.notify );
+ } else {
+ newDefer[ action + "With" ]( this === deferred ? newDefer : this, [ returned ] );
+ }
+ } :
+ newDefer[ action ]
+ );
+ });
+ fns = null;
+ }).promise();
+ },
+ // Get a promise for this deferred
+ // If obj is provided, the promise aspect is added to the object
+ promise: function( obj ) {
+ return obj != null ? jQuery.extend( obj, promise ) : promise;
+ }
+ },
+ deferred = {};
+
+ // Keep pipe for back-compat
+ promise.pipe = promise.then;
+
+ // Add list-specific methods
+ jQuery.each( tuples, function( i, tuple ) {
+ var list = tuple[ 2 ],
+ stateString = tuple[ 3 ];
+
+ // promise[ done | fail | progress ] = list.add
+ promise[ tuple[1] ] = list.add;
+
+ // Handle state
+ if ( stateString ) {
+ list.add(function() {
+ // state = [ resolved | rejected ]
+ state = stateString;
+
+ // [ reject_list | resolve_list ].disable; progress_list.lock
+ }, tuples[ i ^ 1 ][ 2 ].disable, tuples[ 2 ][ 2 ].lock );
+ }
+
+ // deferred[ resolve | reject | notify ] = list.fire
+ deferred[ tuple[0] ] = list.fire;
+ deferred[ tuple[0] + "With" ] = list.fireWith;
+ });
+
+ // Make the deferred a promise
+ promise.promise( deferred );
+
+ // Call given func if any
+ if ( func ) {
+ func.call( deferred, deferred );
+ }
+
+ // All done!
+ return deferred;
+ },
+
+ // Deferred helper
+ when: function( subordinate /* , ..., subordinateN */ ) {
+ var i = 0,
+ resolveValues = core_slice.call( arguments ),
+ length = resolveValues.length,
+
+ // the count of uncompleted subordinates
+ remaining = length !== 1 || ( subordinate && jQuery.isFunction( subordinate.promise ) ) ? length : 0,
+
+ // the master Deferred. If resolveValues consist of only a single Deferred, just use that.
+ deferred = remaining === 1 ? subordinate : jQuery.Deferred(),
+
+ // Update function for both resolve and progress values
+ updateFunc = function( i, contexts, values ) {
+ return function( value ) {
+ contexts[ i ] = this;
+ values[ i ] = arguments.length > 1 ? core_slice.call( arguments ) : value;
+ if( values === progressValues ) {
+ deferred.notifyWith( contexts, values );
+ } else if ( !( --remaining ) ) {
+ deferred.resolveWith( contexts, values );
+ }
+ };
+ },
+
+ progressValues, progressContexts, resolveContexts;
+
+ // add listeners to Deferred subordinates; treat others as resolved
+ if ( length > 1 ) {
+ progressValues = new Array( length );
+ progressContexts = new Array( length );
+ resolveContexts = new Array( length );
+ for ( ; i < length; i++ ) {
+ if ( resolveValues[ i ] && jQuery.isFunction( resolveValues[ i ].promise ) ) {
+ resolveValues[ i ].promise()
+ .done( updateFunc( i, resolveContexts, resolveValues ) )
+ .fail( deferred.reject )
+ .progress( updateFunc( i, progressContexts, progressValues ) );
+ } else {
+ --remaining;
+ }
+ }
+ }
+
+ // if we're not waiting on anything, resolve the master
+ if ( !remaining ) {
+ deferred.resolveWith( resolveContexts, resolveValues );
+ }
+
+ return deferred.promise();
+ }
+});
+jQuery.support = (function() {
+
+ var support,
+ all,
+ a,
+ select,
+ opt,
+ input,
+ fragment,
+ eventName,
+ i,
+ isSupported,
+ clickFn,
+ div = document.createElement("div");
+
+ // Setup
+ div.setAttribute( "className", "t" );
+ div.innerHTML = " a ";
+
+ // Support tests won't run in some limited or non-browser environments
+ all = div.getElementsByTagName("*");
+ a = div.getElementsByTagName("a")[ 0 ];
+ if ( !all || !a || !all.length ) {
+ return {};
+ }
+
+ // First batch of tests
+ select = document.createElement("select");
+ opt = select.appendChild( document.createElement("option") );
+ input = div.getElementsByTagName("input")[ 0 ];
+
+ a.style.cssText = "top:1px;float:left;opacity:.5";
+ support = {
+ // IE strips leading whitespace when .innerHTML is used
+ leadingWhitespace: ( div.firstChild.nodeType === 3 ),
+
+ // Make sure that tbody elements aren't automatically inserted
+ // IE will insert them into empty tables
+ tbody: !div.getElementsByTagName("tbody").length,
+
+ // Make sure that link elements get serialized correctly by innerHTML
+ // This requires a wrapper element in IE
+ htmlSerialize: !!div.getElementsByTagName("link").length,
+
+ // Get the style information from getAttribute
+ // (IE uses .cssText instead)
+ style: /top/.test( a.getAttribute("style") ),
+
+ // Make sure that URLs aren't manipulated
+ // (IE normalizes it by default)
+ hrefNormalized: ( a.getAttribute("href") === "/a" ),
+
+ // Make sure that element opacity exists
+ // (IE uses filter instead)
+ // Use a regex to work around a WebKit issue. See #5145
+ opacity: /^0.5/.test( a.style.opacity ),
+
+ // Verify style float existence
+ // (IE uses styleFloat instead of cssFloat)
+ cssFloat: !!a.style.cssFloat,
+
+ // Make sure that if no value is specified for a checkbox
+ // that it defaults to "on".
+ // (WebKit defaults to "" instead)
+ checkOn: ( input.value === "on" ),
+
+ // Make sure that a selected-by-default option has a working selected property.
+ // (WebKit defaults to false instead of true, IE too, if it's in an optgroup)
+ optSelected: opt.selected,
+
+ // Test setAttribute on camelCase class. If it works, we need attrFixes when doing get/setAttribute (ie6/7)
+ getSetAttribute: div.className !== "t",
+
+ // Tests for enctype support on a form (#6743)
+ enctype: !!document.createElement("form").enctype,
+
+ // Makes sure cloning an html5 element does not cause problems
+ // Where outerHTML is undefined, this still works
+ html5Clone: document.createElement("nav").cloneNode( true ).outerHTML !== "<:nav>",
+
+ // jQuery.support.boxModel DEPRECATED in 1.8 since we don't support Quirks Mode
+ boxModel: ( document.compatMode === "CSS1Compat" ),
+
+ // Will be defined later
+ submitBubbles: true,
+ changeBubbles: true,
+ focusinBubbles: false,
+ deleteExpando: true,
+ noCloneEvent: true,
+ inlineBlockNeedsLayout: false,
+ shrinkWrapBlocks: false,
+ reliableMarginRight: true,
+ boxSizingReliable: true,
+ pixelPosition: false
+ };
+
+ // Make sure checked status is properly cloned
+ input.checked = true;
+ support.noCloneChecked = input.cloneNode( true ).checked;
+
+ // Make sure that the options inside disabled selects aren't marked as disabled
+ // (WebKit marks them as disabled)
+ select.disabled = true;
+ support.optDisabled = !opt.disabled;
+
+ // Test to see if it's possible to delete an expando from an element
+ // Fails in Internet Explorer
+ try {
+ delete div.test;
+ } catch( e ) {
+ support.deleteExpando = false;
+ }
+
+ if ( !div.addEventListener && div.attachEvent && div.fireEvent ) {
+ div.attachEvent( "onclick", clickFn = function() {
+ // Cloning a node shouldn't copy over any
+ // bound event handlers (IE does this)
+ support.noCloneEvent = false;
+ });
+ div.cloneNode( true ).fireEvent("onclick");
+ div.detachEvent( "onclick", clickFn );
+ }
+
+ // Check if a radio maintains its value
+ // after being appended to the DOM
+ input = document.createElement("input");
+ input.value = "t";
+ input.setAttribute( "type", "radio" );
+ support.radioValue = input.value === "t";
+
+ input.setAttribute( "checked", "checked" );
+
+ // #11217 - WebKit loses check when the name is after the checked attribute
+ input.setAttribute( "name", "t" );
+
+ div.appendChild( input );
+ fragment = document.createDocumentFragment();
+ fragment.appendChild( div.lastChild );
+
+ // WebKit doesn't clone checked state correctly in fragments
+ support.checkClone = fragment.cloneNode( true ).cloneNode( true ).lastChild.checked;
+
+ // Check if a disconnected checkbox will retain its checked
+ // value of true after appended to the DOM (IE6/7)
+ support.appendChecked = input.checked;
+
+ fragment.removeChild( input );
+ fragment.appendChild( div );
+
+ // Technique from Juriy Zaytsev
+ // http://perfectionkills.com/detecting-event-support-without-browser-sniffing/
+ // We only care about the case where non-standard event systems
+ // are used, namely in IE. Short-circuiting here helps us to
+ // avoid an eval call (in setAttribute) which can cause CSP
+ // to go haywire. See: https://developer.mozilla.org/en/Security/CSP
+ if ( div.attachEvent ) {
+ for ( i in {
+ submit: true,
+ change: true,
+ focusin: true
+ }) {
+ eventName = "on" + i;
+ isSupported = ( eventName in div );
+ if ( !isSupported ) {
+ div.setAttribute( eventName, "return;" );
+ isSupported = ( typeof div[ eventName ] === "function" );
+ }
+ support[ i + "Bubbles" ] = isSupported;
+ }
+ }
+
+ // Run tests that need a body at doc ready
+ jQuery(function() {
+ var container, div, tds, marginDiv,
+ divReset = "padding:0;margin:0;border:0;display:block;overflow:hidden;",
+ body = document.getElementsByTagName("body")[0];
+
+ if ( !body ) {
+ // Return for frameset docs that don't have a body
+ return;
+ }
+
+ container = document.createElement("div");
+ container.style.cssText = "visibility:hidden;border:0;width:0;height:0;position:static;top:0;margin-top:1px";
+ body.insertBefore( container, body.firstChild );
+
+ // Construct the test element
+ div = document.createElement("div");
+ container.appendChild( div );
+
+ // Check if table cells still have offsetWidth/Height when they are set
+ // to display:none and there are still other visible table cells in a
+ // table row; if so, offsetWidth/Height are not reliable for use when
+ // determining if an element has been hidden directly using
+ // display:none (it is still safe to use offsets if a parent element is
+ // hidden; don safety goggles and see bug #4512 for more information).
+ // (only IE 8 fails this test)
+ div.innerHTML = "";
+ tds = div.getElementsByTagName("td");
+ tds[ 0 ].style.cssText = "padding:0;margin:0;border:0;display:none";
+ isSupported = ( tds[ 0 ].offsetHeight === 0 );
+
+ tds[ 0 ].style.display = "";
+ tds[ 1 ].style.display = "none";
+
+ // Check if empty table cells still have offsetWidth/Height
+ // (IE <= 8 fail this test)
+ support.reliableHiddenOffsets = isSupported && ( tds[ 0 ].offsetHeight === 0 );
+
+ // Check box-sizing and margin behavior
+ div.innerHTML = "";
+ div.style.cssText = "box-sizing:border-box;-moz-box-sizing:border-box;-webkit-box-sizing:border-box;padding:1px;border:1px;display:block;width:4px;margin-top:1%;position:absolute;top:1%;";
+ support.boxSizing = ( div.offsetWidth === 4 );
+ support.doesNotIncludeMarginInBodyOffset = ( body.offsetTop !== 1 );
+
+ // NOTE: To any future maintainer, we've window.getComputedStyle
+ // because jsdom on node.js will break without it.
+ if ( window.getComputedStyle ) {
+ support.pixelPosition = ( window.getComputedStyle( div, null ) || {} ).top !== "1%";
+ support.boxSizingReliable = ( window.getComputedStyle( div, null ) || { width: "4px" } ).width === "4px";
+
+ // Check if div with explicit width and no margin-right incorrectly
+ // gets computed margin-right based on width of container. For more
+ // info see bug #3333
+ // Fails in WebKit before Feb 2011 nightlies
+ // WebKit Bug 13343 - getComputedStyle returns wrong value for margin-right
+ marginDiv = document.createElement("div");
+ marginDiv.style.cssText = div.style.cssText = divReset;
+ marginDiv.style.marginRight = marginDiv.style.width = "0";
+ div.style.width = "1px";
+ div.appendChild( marginDiv );
+ support.reliableMarginRight =
+ !parseFloat( ( window.getComputedStyle( marginDiv, null ) || {} ).marginRight );
+ }
+
+ if ( typeof div.style.zoom !== "undefined" ) {
+ // Check if natively block-level elements act like inline-block
+ // elements when setting their display to 'inline' and giving
+ // them layout
+ // (IE < 8 does this)
+ div.innerHTML = "";
+ div.style.cssText = divReset + "width:1px;padding:1px;display:inline;zoom:1";
+ support.inlineBlockNeedsLayout = ( div.offsetWidth === 3 );
+
+ // Check if elements with layout shrink-wrap their children
+ // (IE 6 does this)
+ div.style.display = "block";
+ div.style.overflow = "visible";
+ div.innerHTML = "
";
+ div.firstChild.style.width = "5px";
+ support.shrinkWrapBlocks = ( div.offsetWidth !== 3 );
+
+ container.style.zoom = 1;
+ }
+
+ // Null elements to avoid leaks in IE
+ body.removeChild( container );
+ container = div = tds = marginDiv = null;
+ });
+
+ // Null elements to avoid leaks in IE
+ fragment.removeChild( div );
+ all = a = select = opt = input = fragment = div = null;
+
+ return support;
+})();
+var rbrace = /(?:\{[\s\S]*\}|\[[\s\S]*\])$/,
+ rmultiDash = /([A-Z])/g;
+
+jQuery.extend({
+ cache: {},
+
+ deletedIds: [],
+
+ // Remove at next major release (1.9/2.0)
+ uuid: 0,
+
+ // Unique for each copy of jQuery on the page
+ // Non-digits removed to match rinlinejQuery
+ expando: "jQuery" + ( jQuery.fn.jquery + Math.random() ).replace( /\D/g, "" ),
+
+ // The following elements throw uncatchable exceptions if you
+ // attempt to add expando properties to them.
+ noData: {
+ "embed": true,
+ // Ban all objects except for Flash (which handle expandos)
+ "object": "clsid:D27CDB6E-AE6D-11cf-96B8-444553540000",
+ "applet": true
+ },
+
+ hasData: function( elem ) {
+ elem = elem.nodeType ? jQuery.cache[ elem[jQuery.expando] ] : elem[ jQuery.expando ];
+ return !!elem && !isEmptyDataObject( elem );
+ },
+
+ data: function( elem, name, data, pvt /* Internal Use Only */ ) {
+ if ( !jQuery.acceptData( elem ) ) {
+ return;
+ }
+
+ var thisCache, ret,
+ internalKey = jQuery.expando,
+ getByName = typeof name === "string",
+
+ // We have to handle DOM nodes and JS objects differently because IE6-7
+ // can't GC object references properly across the DOM-JS boundary
+ isNode = elem.nodeType,
+
+ // Only DOM nodes need the global jQuery cache; JS object data is
+ // attached directly to the object so GC can occur automatically
+ cache = isNode ? jQuery.cache : elem,
+
+ // Only defining an ID for JS objects if its cache already exists allows
+ // the code to shortcut on the same path as a DOM node with no cache
+ id = isNode ? elem[ internalKey ] : elem[ internalKey ] && internalKey;
+
+ // Avoid doing any more work than we need to when trying to get data on an
+ // object that has no data at all
+ if ( (!id || !cache[id] || (!pvt && !cache[id].data)) && getByName && data === undefined ) {
+ return;
+ }
+
+ if ( !id ) {
+ // Only DOM nodes need a new unique ID for each element since their data
+ // ends up in the global cache
+ if ( isNode ) {
+ elem[ internalKey ] = id = jQuery.deletedIds.pop() || jQuery.guid++;
+ } else {
+ id = internalKey;
+ }
+ }
+
+ if ( !cache[ id ] ) {
+ cache[ id ] = {};
+
+ // Avoids exposing jQuery metadata on plain JS objects when the object
+ // is serialized using JSON.stringify
+ if ( !isNode ) {
+ cache[ id ].toJSON = jQuery.noop;
+ }
+ }
+
+ // An object can be passed to jQuery.data instead of a key/value pair; this gets
+ // shallow copied over onto the existing cache
+ if ( typeof name === "object" || typeof name === "function" ) {
+ if ( pvt ) {
+ cache[ id ] = jQuery.extend( cache[ id ], name );
+ } else {
+ cache[ id ].data = jQuery.extend( cache[ id ].data, name );
+ }
+ }
+
+ thisCache = cache[ id ];
+
+ // jQuery data() is stored in a separate object inside the object's internal data
+ // cache in order to avoid key collisions between internal data and user-defined
+ // data.
+ if ( !pvt ) {
+ if ( !thisCache.data ) {
+ thisCache.data = {};
+ }
+
+ thisCache = thisCache.data;
+ }
+
+ if ( data !== undefined ) {
+ thisCache[ jQuery.camelCase( name ) ] = data;
+ }
+
+ // Check for both converted-to-camel and non-converted data property names
+ // If a data property was specified
+ if ( getByName ) {
+
+ // First Try to find as-is property data
+ ret = thisCache[ name ];
+
+ // Test for null|undefined property data
+ if ( ret == null ) {
+
+ // Try to find the camelCased property
+ ret = thisCache[ jQuery.camelCase( name ) ];
+ }
+ } else {
+ ret = thisCache;
+ }
+
+ return ret;
+ },
+
+ removeData: function( elem, name, pvt /* Internal Use Only */ ) {
+ if ( !jQuery.acceptData( elem ) ) {
+ return;
+ }
+
+ var thisCache, i, l,
+
+ isNode = elem.nodeType,
+
+ // See jQuery.data for more information
+ cache = isNode ? jQuery.cache : elem,
+ id = isNode ? elem[ jQuery.expando ] : jQuery.expando;
+
+ // If there is already no cache entry for this object, there is no
+ // purpose in continuing
+ if ( !cache[ id ] ) {
+ return;
+ }
+
+ if ( name ) {
+
+ thisCache = pvt ? cache[ id ] : cache[ id ].data;
+
+ if ( thisCache ) {
+
+ // Support array or space separated string names for data keys
+ if ( !jQuery.isArray( name ) ) {
+
+ // try the string as a key before any manipulation
+ if ( name in thisCache ) {
+ name = [ name ];
+ } else {
+
+ // split the camel cased version by spaces unless a key with the spaces exists
+ name = jQuery.camelCase( name );
+ if ( name in thisCache ) {
+ name = [ name ];
+ } else {
+ name = name.split(" ");
+ }
+ }
+ }
+
+ for ( i = 0, l = name.length; i < l; i++ ) {
+ delete thisCache[ name[i] ];
+ }
+
+ // If there is no data left in the cache, we want to continue
+ // and let the cache object itself get destroyed
+ if ( !( pvt ? isEmptyDataObject : jQuery.isEmptyObject )( thisCache ) ) {
+ return;
+ }
+ }
+ }
+
+ // See jQuery.data for more information
+ if ( !pvt ) {
+ delete cache[ id ].data;
+
+ // Don't destroy the parent cache unless the internal data object
+ // had been the only thing left in it
+ if ( !isEmptyDataObject( cache[ id ] ) ) {
+ return;
+ }
+ }
+
+ // Destroy the cache
+ if ( isNode ) {
+ jQuery.cleanData( [ elem ], true );
+
+ // Use delete when supported for expandos or `cache` is not a window per isWindow (#10080)
+ } else if ( jQuery.support.deleteExpando || cache != cache.window ) {
+ delete cache[ id ];
+
+ // When all else fails, null
+ } else {
+ cache[ id ] = null;
+ }
+ },
+
+ // For internal use only.
+ _data: function( elem, name, data ) {
+ return jQuery.data( elem, name, data, true );
+ },
+
+ // A method for determining if a DOM node can handle the data expando
+ acceptData: function( elem ) {
+ var noData = elem.nodeName && jQuery.noData[ elem.nodeName.toLowerCase() ];
+
+ // nodes accept data unless otherwise specified; rejection can be conditional
+ return !noData || noData !== true && elem.getAttribute("classid") === noData;
+ }
+});
+
+jQuery.fn.extend({
+ data: function( key, value ) {
+ var parts, part, attr, name, l,
+ elem = this[0],
+ i = 0,
+ data = null;
+
+ // Gets all values
+ if ( key === undefined ) {
+ if ( this.length ) {
+ data = jQuery.data( elem );
+
+ if ( elem.nodeType === 1 && !jQuery._data( elem, "parsedAttrs" ) ) {
+ attr = elem.attributes;
+ for ( l = attr.length; i < l; i++ ) {
+ name = attr[i].name;
+
+ if ( !name.indexOf( "data-" ) ) {
+ name = jQuery.camelCase( name.substring(5) );
+
+ dataAttr( elem, name, data[ name ] );
+ }
+ }
+ jQuery._data( elem, "parsedAttrs", true );
+ }
+ }
+
+ return data;
+ }
+
+ // Sets multiple values
+ if ( typeof key === "object" ) {
+ return this.each(function() {
+ jQuery.data( this, key );
+ });
+ }
+
+ parts = key.split( ".", 2 );
+ parts[1] = parts[1] ? "." + parts[1] : "";
+ part = parts[1] + "!";
+
+ return jQuery.access( this, function( value ) {
+
+ if ( value === undefined ) {
+ data = this.triggerHandler( "getData" + part, [ parts[0] ] );
+
+ // Try to fetch any internally stored data first
+ if ( data === undefined && elem ) {
+ data = jQuery.data( elem, key );
+ data = dataAttr( elem, key, data );
+ }
+
+ return data === undefined && parts[1] ?
+ this.data( parts[0] ) :
+ data;
+ }
+
+ parts[1] = value;
+ this.each(function() {
+ var self = jQuery( this );
+
+ self.triggerHandler( "setData" + part, parts );
+ jQuery.data( this, key, value );
+ self.triggerHandler( "changeData" + part, parts );
+ });
+ }, null, value, arguments.length > 1, null, false );
+ },
+
+ removeData: function( key ) {
+ return this.each(function() {
+ jQuery.removeData( this, key );
+ });
+ }
+});
+
+function dataAttr( elem, key, data ) {
+ // If nothing was found internally, try to fetch any
+ // data from the HTML5 data-* attribute
+ if ( data === undefined && elem.nodeType === 1 ) {
+
+ var name = "data-" + key.replace( rmultiDash, "-$1" ).toLowerCase();
+
+ data = elem.getAttribute( name );
+
+ if ( typeof data === "string" ) {
+ try {
+ data = data === "true" ? true :
+ data === "false" ? false :
+ data === "null" ? null :
+ // Only convert to a number if it doesn't change the string
+ +data + "" === data ? +data :
+ rbrace.test( data ) ? jQuery.parseJSON( data ) :
+ data;
+ } catch( e ) {}
+
+ // Make sure we set the data so it isn't changed later
+ jQuery.data( elem, key, data );
+
+ } else {
+ data = undefined;
+ }
+ }
+
+ return data;
+}
+
+// checks a cache object for emptiness
+function isEmptyDataObject( obj ) {
+ var name;
+ for ( name in obj ) {
+
+ // if the public data object is empty, the private is still empty
+ if ( name === "data" && jQuery.isEmptyObject( obj[name] ) ) {
+ continue;
+ }
+ if ( name !== "toJSON" ) {
+ return false;
+ }
+ }
+
+ return true;
+}
+jQuery.extend({
+ queue: function( elem, type, data ) {
+ var queue;
+
+ if ( elem ) {
+ type = ( type || "fx" ) + "queue";
+ queue = jQuery._data( elem, type );
+
+ // Speed up dequeue by getting out quickly if this is just a lookup
+ if ( data ) {
+ if ( !queue || jQuery.isArray(data) ) {
+ queue = jQuery._data( elem, type, jQuery.makeArray(data) );
+ } else {
+ queue.push( data );
+ }
+ }
+ return queue || [];
+ }
+ },
+
+ dequeue: function( elem, type ) {
+ type = type || "fx";
+
+ var queue = jQuery.queue( elem, type ),
+ startLength = queue.length,
+ fn = queue.shift(),
+ hooks = jQuery._queueHooks( elem, type ),
+ next = function() {
+ jQuery.dequeue( elem, type );
+ };
+
+ // If the fx queue is dequeued, always remove the progress sentinel
+ if ( fn === "inprogress" ) {
+ fn = queue.shift();
+ startLength--;
+ }
+
+ if ( fn ) {
+
+ // Add a progress sentinel to prevent the fx queue from being
+ // automatically dequeued
+ if ( type === "fx" ) {
+ queue.unshift( "inprogress" );
+ }
+
+ // clear up the last queue stop function
+ delete hooks.stop;
+ fn.call( elem, next, hooks );
+ }
+
+ if ( !startLength && hooks ) {
+ hooks.empty.fire();
+ }
+ },
+
+ // not intended for public consumption - generates a queueHooks object, or returns the current one
+ _queueHooks: function( elem, type ) {
+ var key = type + "queueHooks";
+ return jQuery._data( elem, key ) || jQuery._data( elem, key, {
+ empty: jQuery.Callbacks("once memory").add(function() {
+ jQuery.removeData( elem, type + "queue", true );
+ jQuery.removeData( elem, key, true );
+ })
+ });
+ }
+});
+
+jQuery.fn.extend({
+ queue: function( type, data ) {
+ var setter = 2;
+
+ if ( typeof type !== "string" ) {
+ data = type;
+ type = "fx";
+ setter--;
+ }
+
+ if ( arguments.length < setter ) {
+ return jQuery.queue( this[0], type );
+ }
+
+ return data === undefined ?
+ this :
+ this.each(function() {
+ var queue = jQuery.queue( this, type, data );
+
+ // ensure a hooks for this queue
+ jQuery._queueHooks( this, type );
+
+ if ( type === "fx" && queue[0] !== "inprogress" ) {
+ jQuery.dequeue( this, type );
+ }
+ });
+ },
+ dequeue: function( type ) {
+ return this.each(function() {
+ jQuery.dequeue( this, type );
+ });
+ },
+ // Based off of the plugin by Clint Helfers, with permission.
+ // http://blindsignals.com/index.php/2009/07/jquery-delay/
+ delay: function( time, type ) {
+ time = jQuery.fx ? jQuery.fx.speeds[ time ] || time : time;
+ type = type || "fx";
+
+ return this.queue( type, function( next, hooks ) {
+ var timeout = setTimeout( next, time );
+ hooks.stop = function() {
+ clearTimeout( timeout );
+ };
+ });
+ },
+ clearQueue: function( type ) {
+ return this.queue( type || "fx", [] );
+ },
+ // Get a promise resolved when queues of a certain type
+ // are emptied (fx is the type by default)
+ promise: function( type, obj ) {
+ var tmp,
+ count = 1,
+ defer = jQuery.Deferred(),
+ elements = this,
+ i = this.length,
+ resolve = function() {
+ if ( !( --count ) ) {
+ defer.resolveWith( elements, [ elements ] );
+ }
+ };
+
+ if ( typeof type !== "string" ) {
+ obj = type;
+ type = undefined;
+ }
+ type = type || "fx";
+
+ while( i-- ) {
+ tmp = jQuery._data( elements[ i ], type + "queueHooks" );
+ if ( tmp && tmp.empty ) {
+ count++;
+ tmp.empty.add( resolve );
+ }
+ }
+ resolve();
+ return defer.promise( obj );
+ }
+});
+var nodeHook, boolHook, fixSpecified,
+ rclass = /[\t\r\n]/g,
+ rreturn = /\r/g,
+ rtype = /^(?:button|input)$/i,
+ rfocusable = /^(?:button|input|object|select|textarea)$/i,
+ rclickable = /^a(?:rea|)$/i,
+ rboolean = /^(?:autofocus|autoplay|async|checked|controls|defer|disabled|hidden|loop|multiple|open|readonly|required|scoped|selected)$/i,
+ getSetAttribute = jQuery.support.getSetAttribute;
+
+jQuery.fn.extend({
+ attr: function( name, value ) {
+ return jQuery.access( this, jQuery.attr, name, value, arguments.length > 1 );
+ },
+
+ removeAttr: function( name ) {
+ return this.each(function() {
+ jQuery.removeAttr( this, name );
+ });
+ },
+
+ prop: function( name, value ) {
+ return jQuery.access( this, jQuery.prop, name, value, arguments.length > 1 );
+ },
+
+ removeProp: function( name ) {
+ name = jQuery.propFix[ name ] || name;
+ return this.each(function() {
+ // try/catch handles cases where IE balks (such as removing a property on window)
+ try {
+ this[ name ] = undefined;
+ delete this[ name ];
+ } catch( e ) {}
+ });
+ },
+
+ addClass: function( value ) {
+ var classNames, i, l, elem,
+ setClass, c, cl;
+
+ if ( jQuery.isFunction( value ) ) {
+ return this.each(function( j ) {
+ jQuery( this ).addClass( value.call(this, j, this.className) );
+ });
+ }
+
+ if ( value && typeof value === "string" ) {
+ classNames = value.split( core_rspace );
+
+ for ( i = 0, l = this.length; i < l; i++ ) {
+ elem = this[ i ];
+
+ if ( elem.nodeType === 1 ) {
+ if ( !elem.className && classNames.length === 1 ) {
+ elem.className = value;
+
+ } else {
+ setClass = " " + elem.className + " ";
+
+ for ( c = 0, cl = classNames.length; c < cl; c++ ) {
+ if ( setClass.indexOf( " " + classNames[ c ] + " " ) < 0 ) {
+ setClass += classNames[ c ] + " ";
+ }
+ }
+ elem.className = jQuery.trim( setClass );
+ }
+ }
+ }
+ }
+
+ return this;
+ },
+
+ removeClass: function( value ) {
+ var removes, className, elem, c, cl, i, l;
+
+ if ( jQuery.isFunction( value ) ) {
+ return this.each(function( j ) {
+ jQuery( this ).removeClass( value.call(this, j, this.className) );
+ });
+ }
+ if ( (value && typeof value === "string") || value === undefined ) {
+ removes = ( value || "" ).split( core_rspace );
+
+ for ( i = 0, l = this.length; i < l; i++ ) {
+ elem = this[ i ];
+ if ( elem.nodeType === 1 && elem.className ) {
+
+ className = (" " + elem.className + " ").replace( rclass, " " );
+
+ // loop over each item in the removal list
+ for ( c = 0, cl = removes.length; c < cl; c++ ) {
+ // Remove until there is nothing to remove,
+ while ( className.indexOf(" " + removes[ c ] + " ") >= 0 ) {
+ className = className.replace( " " + removes[ c ] + " " , " " );
+ }
+ }
+ elem.className = value ? jQuery.trim( className ) : "";
+ }
+ }
+ }
+
+ return this;
+ },
+
+ toggleClass: function( value, stateVal ) {
+ var type = typeof value,
+ isBool = typeof stateVal === "boolean";
+
+ if ( jQuery.isFunction( value ) ) {
+ return this.each(function( i ) {
+ jQuery( this ).toggleClass( value.call(this, i, this.className, stateVal), stateVal );
+ });
+ }
+
+ return this.each(function() {
+ if ( type === "string" ) {
+ // toggle individual class names
+ var className,
+ i = 0,
+ self = jQuery( this ),
+ state = stateVal,
+ classNames = value.split( core_rspace );
+
+ while ( (className = classNames[ i++ ]) ) {
+ // check each className given, space separated list
+ state = isBool ? state : !self.hasClass( className );
+ self[ state ? "addClass" : "removeClass" ]( className );
+ }
+
+ } else if ( type === "undefined" || type === "boolean" ) {
+ if ( this.className ) {
+ // store className if set
+ jQuery._data( this, "__className__", this.className );
+ }
+
+ // toggle whole className
+ this.className = this.className || value === false ? "" : jQuery._data( this, "__className__" ) || "";
+ }
+ });
+ },
+
+ hasClass: function( selector ) {
+ var className = " " + selector + " ",
+ i = 0,
+ l = this.length;
+ for ( ; i < l; i++ ) {
+ if ( this[i].nodeType === 1 && (" " + this[i].className + " ").replace(rclass, " ").indexOf( className ) >= 0 ) {
+ return true;
+ }
+ }
+
+ return false;
+ },
+
+ val: function( value ) {
+ var hooks, ret, isFunction,
+ elem = this[0];
+
+ if ( !arguments.length ) {
+ if ( elem ) {
+ hooks = jQuery.valHooks[ elem.type ] || jQuery.valHooks[ elem.nodeName.toLowerCase() ];
+
+ if ( hooks && "get" in hooks && (ret = hooks.get( elem, "value" )) !== undefined ) {
+ return ret;
+ }
+
+ ret = elem.value;
+
+ return typeof ret === "string" ?
+ // handle most common string cases
+ ret.replace(rreturn, "") :
+ // handle cases where value is null/undef or number
+ ret == null ? "" : ret;
+ }
+
+ return;
+ }
+
+ isFunction = jQuery.isFunction( value );
+
+ return this.each(function( i ) {
+ var val,
+ self = jQuery(this);
+
+ if ( this.nodeType !== 1 ) {
+ return;
+ }
+
+ if ( isFunction ) {
+ val = value.call( this, i, self.val() );
+ } else {
+ val = value;
+ }
+
+ // Treat null/undefined as ""; convert numbers to string
+ if ( val == null ) {
+ val = "";
+ } else if ( typeof val === "number" ) {
+ val += "";
+ } else if ( jQuery.isArray( val ) ) {
+ val = jQuery.map(val, function ( value ) {
+ return value == null ? "" : value + "";
+ });
+ }
+
+ hooks = jQuery.valHooks[ this.type ] || jQuery.valHooks[ this.nodeName.toLowerCase() ];
+
+ // If set returns undefined, fall back to normal setting
+ if ( !hooks || !("set" in hooks) || hooks.set( this, val, "value" ) === undefined ) {
+ this.value = val;
+ }
+ });
+ }
+});
+
+jQuery.extend({
+ valHooks: {
+ option: {
+ get: function( elem ) {
+ // attributes.value is undefined in Blackberry 4.7 but
+ // uses .value. See #6932
+ var val = elem.attributes.value;
+ return !val || val.specified ? elem.value : elem.text;
+ }
+ },
+ select: {
+ get: function( elem ) {
+ var value, option,
+ options = elem.options,
+ index = elem.selectedIndex,
+ one = elem.type === "select-one" || index < 0,
+ values = one ? null : [],
+ max = one ? index + 1 : options.length,
+ i = index < 0 ?
+ max :
+ one ? index : 0;
+
+ // Loop through all the selected options
+ for ( ; i < max; i++ ) {
+ option = options[ i ];
+
+ // oldIE doesn't update selected after form reset (#2551)
+ if ( ( option.selected || i === index ) &&
+ // Don't return options that are disabled or in a disabled optgroup
+ ( jQuery.support.optDisabled ? !option.disabled : option.getAttribute("disabled") === null ) &&
+ ( !option.parentNode.disabled || !jQuery.nodeName( option.parentNode, "optgroup" ) ) ) {
+
+ // Get the specific value for the option
+ value = jQuery( option ).val();
+
+ // We don't need an array for one selects
+ if ( one ) {
+ return value;
+ }
+
+ // Multi-Selects return an array
+ values.push( value );
+ }
+ }
+
+ return values;
+ },
+
+ set: function( elem, value ) {
+ var values = jQuery.makeArray( value );
+
+ jQuery(elem).find("option").each(function() {
+ this.selected = jQuery.inArray( jQuery(this).val(), values ) >= 0;
+ });
+
+ if ( !values.length ) {
+ elem.selectedIndex = -1;
+ }
+ return values;
+ }
+ }
+ },
+
+ // Unused in 1.8, left in so attrFn-stabbers won't die; remove in 1.9
+ attrFn: {},
+
+ attr: function( elem, name, value, pass ) {
+ var ret, hooks, notxml,
+ nType = elem.nodeType;
+
+ // don't get/set attributes on text, comment and attribute nodes
+ if ( !elem || nType === 3 || nType === 8 || nType === 2 ) {
+ return;
+ }
+
+ if ( pass && jQuery.isFunction( jQuery.fn[ name ] ) ) {
+ return jQuery( elem )[ name ]( value );
+ }
+
+ // Fallback to prop when attributes are not supported
+ if ( typeof elem.getAttribute === "undefined" ) {
+ return jQuery.prop( elem, name, value );
+ }
+
+ notxml = nType !== 1 || !jQuery.isXMLDoc( elem );
+
+ // All attributes are lowercase
+ // Grab necessary hook if one is defined
+ if ( notxml ) {
+ name = name.toLowerCase();
+ hooks = jQuery.attrHooks[ name ] || ( rboolean.test( name ) ? boolHook : nodeHook );
+ }
+
+ if ( value !== undefined ) {
+
+ if ( value === null ) {
+ jQuery.removeAttr( elem, name );
+ return;
+
+ } else if ( hooks && "set" in hooks && notxml && (ret = hooks.set( elem, value, name )) !== undefined ) {
+ return ret;
+
+ } else {
+ elem.setAttribute( name, value + "" );
+ return value;
+ }
+
+ } else if ( hooks && "get" in hooks && notxml && (ret = hooks.get( elem, name )) !== null ) {
+ return ret;
+
+ } else {
+
+ ret = elem.getAttribute( name );
+
+ // Non-existent attributes return null, we normalize to undefined
+ return ret === null ?
+ undefined :
+ ret;
+ }
+ },
+
+ removeAttr: function( elem, value ) {
+ var propName, attrNames, name, isBool,
+ i = 0;
+
+ if ( value && elem.nodeType === 1 ) {
+
+ attrNames = value.split( core_rspace );
+
+ for ( ; i < attrNames.length; i++ ) {
+ name = attrNames[ i ];
+
+ if ( name ) {
+ propName = jQuery.propFix[ name ] || name;
+ isBool = rboolean.test( name );
+
+ // See #9699 for explanation of this approach (setting first, then removal)
+ // Do not do this for boolean attributes (see #10870)
+ if ( !isBool ) {
+ jQuery.attr( elem, name, "" );
+ }
+ elem.removeAttribute( getSetAttribute ? name : propName );
+
+ // Set corresponding property to false for boolean attributes
+ if ( isBool && propName in elem ) {
+ elem[ propName ] = false;
+ }
+ }
+ }
+ }
+ },
+
+ attrHooks: {
+ type: {
+ set: function( elem, value ) {
+ // We can't allow the type property to be changed (since it causes problems in IE)
+ if ( rtype.test( elem.nodeName ) && elem.parentNode ) {
+ jQuery.error( "type property can't be changed" );
+ } else if ( !jQuery.support.radioValue && value === "radio" && jQuery.nodeName(elem, "input") ) {
+ // Setting the type on a radio button after the value resets the value in IE6-9
+ // Reset value to it's default in case type is set after value
+ // This is for element creation
+ var val = elem.value;
+ elem.setAttribute( "type", value );
+ if ( val ) {
+ elem.value = val;
+ }
+ return value;
+ }
+ }
+ },
+ // Use the value property for back compat
+ // Use the nodeHook for button elements in IE6/7 (#1954)
+ value: {
+ get: function( elem, name ) {
+ if ( nodeHook && jQuery.nodeName( elem, "button" ) ) {
+ return nodeHook.get( elem, name );
+ }
+ return name in elem ?
+ elem.value :
+ null;
+ },
+ set: function( elem, value, name ) {
+ if ( nodeHook && jQuery.nodeName( elem, "button" ) ) {
+ return nodeHook.set( elem, value, name );
+ }
+ // Does not return so that setAttribute is also used
+ elem.value = value;
+ }
+ }
+ },
+
+ propFix: {
+ tabindex: "tabIndex",
+ readonly: "readOnly",
+ "for": "htmlFor",
+ "class": "className",
+ maxlength: "maxLength",
+ cellspacing: "cellSpacing",
+ cellpadding: "cellPadding",
+ rowspan: "rowSpan",
+ colspan: "colSpan",
+ usemap: "useMap",
+ frameborder: "frameBorder",
+ contenteditable: "contentEditable"
+ },
+
+ prop: function( elem, name, value ) {
+ var ret, hooks, notxml,
+ nType = elem.nodeType;
+
+ // don't get/set properties on text, comment and attribute nodes
+ if ( !elem || nType === 3 || nType === 8 || nType === 2 ) {
+ return;
+ }
+
+ notxml = nType !== 1 || !jQuery.isXMLDoc( elem );
+
+ if ( notxml ) {
+ // Fix name and attach hooks
+ name = jQuery.propFix[ name ] || name;
+ hooks = jQuery.propHooks[ name ];
+ }
+
+ if ( value !== undefined ) {
+ if ( hooks && "set" in hooks && (ret = hooks.set( elem, value, name )) !== undefined ) {
+ return ret;
+
+ } else {
+ return ( elem[ name ] = value );
+ }
+
+ } else {
+ if ( hooks && "get" in hooks && (ret = hooks.get( elem, name )) !== null ) {
+ return ret;
+
+ } else {
+ return elem[ name ];
+ }
+ }
+ },
+
+ propHooks: {
+ tabIndex: {
+ get: function( elem ) {
+ // elem.tabIndex doesn't always return the correct value when it hasn't been explicitly set
+ // http://fluidproject.org/blog/2008/01/09/getting-setting-and-removing-tabindex-values-with-javascript/
+ var attributeNode = elem.getAttributeNode("tabindex");
+
+ return attributeNode && attributeNode.specified ?
+ parseInt( attributeNode.value, 10 ) :
+ rfocusable.test( elem.nodeName ) || rclickable.test( elem.nodeName ) && elem.href ?
+ 0 :
+ undefined;
+ }
+ }
+ }
+});
+
+// Hook for boolean attributes
+boolHook = {
+ get: function( elem, name ) {
+ // Align boolean attributes with corresponding properties
+ // Fall back to attribute presence where some booleans are not supported
+ var attrNode,
+ property = jQuery.prop( elem, name );
+ return property === true || typeof property !== "boolean" && ( attrNode = elem.getAttributeNode(name) ) && attrNode.nodeValue !== false ?
+ name.toLowerCase() :
+ undefined;
+ },
+ set: function( elem, value, name ) {
+ var propName;
+ if ( value === false ) {
+ // Remove boolean attributes when set to false
+ jQuery.removeAttr( elem, name );
+ } else {
+ // value is true since we know at this point it's type boolean and not false
+ // Set boolean attributes to the same name and set the DOM property
+ propName = jQuery.propFix[ name ] || name;
+ if ( propName in elem ) {
+ // Only set the IDL specifically if it already exists on the element
+ elem[ propName ] = true;
+ }
+
+ elem.setAttribute( name, name.toLowerCase() );
+ }
+ return name;
+ }
+};
+
+// IE6/7 do not support getting/setting some attributes with get/setAttribute
+if ( !getSetAttribute ) {
+
+ fixSpecified = {
+ name: true,
+ id: true,
+ coords: true
+ };
+
+ // Use this for any attribute in IE6/7
+ // This fixes almost every IE6/7 issue
+ nodeHook = jQuery.valHooks.button = {
+ get: function( elem, name ) {
+ var ret;
+ ret = elem.getAttributeNode( name );
+ return ret && ( fixSpecified[ name ] ? ret.value !== "" : ret.specified ) ?
+ ret.value :
+ undefined;
+ },
+ set: function( elem, value, name ) {
+ // Set the existing or create a new attribute node
+ var ret = elem.getAttributeNode( name );
+ if ( !ret ) {
+ ret = document.createAttribute( name );
+ elem.setAttributeNode( ret );
+ }
+ return ( ret.value = value + "" );
+ }
+ };
+
+ // Set width and height to auto instead of 0 on empty string( Bug #8150 )
+ // This is for removals
+ jQuery.each([ "width", "height" ], function( i, name ) {
+ jQuery.attrHooks[ name ] = jQuery.extend( jQuery.attrHooks[ name ], {
+ set: function( elem, value ) {
+ if ( value === "" ) {
+ elem.setAttribute( name, "auto" );
+ return value;
+ }
+ }
+ });
+ });
+
+ // Set contenteditable to false on removals(#10429)
+ // Setting to empty string throws an error as an invalid value
+ jQuery.attrHooks.contenteditable = {
+ get: nodeHook.get,
+ set: function( elem, value, name ) {
+ if ( value === "" ) {
+ value = "false";
+ }
+ nodeHook.set( elem, value, name );
+ }
+ };
+}
+
+
+// Some attributes require a special call on IE
+if ( !jQuery.support.hrefNormalized ) {
+ jQuery.each([ "href", "src", "width", "height" ], function( i, name ) {
+ jQuery.attrHooks[ name ] = jQuery.extend( jQuery.attrHooks[ name ], {
+ get: function( elem ) {
+ var ret = elem.getAttribute( name, 2 );
+ return ret === null ? undefined : ret;
+ }
+ });
+ });
+}
+
+if ( !jQuery.support.style ) {
+ jQuery.attrHooks.style = {
+ get: function( elem ) {
+ // Return undefined in the case of empty string
+ // Normalize to lowercase since IE uppercases css property names
+ return elem.style.cssText.toLowerCase() || undefined;
+ },
+ set: function( elem, value ) {
+ return ( elem.style.cssText = value + "" );
+ }
+ };
+}
+
+// Safari mis-reports the default selected property of an option
+// Accessing the parent's selectedIndex property fixes it
+if ( !jQuery.support.optSelected ) {
+ jQuery.propHooks.selected = jQuery.extend( jQuery.propHooks.selected, {
+ get: function( elem ) {
+ var parent = elem.parentNode;
+
+ if ( parent ) {
+ parent.selectedIndex;
+
+ // Make sure that it also works with optgroups, see #5701
+ if ( parent.parentNode ) {
+ parent.parentNode.selectedIndex;
+ }
+ }
+ return null;
+ }
+ });
+}
+
+// IE6/7 call enctype encoding
+if ( !jQuery.support.enctype ) {
+ jQuery.propFix.enctype = "encoding";
+}
+
+// Radios and checkboxes getter/setter
+if ( !jQuery.support.checkOn ) {
+ jQuery.each([ "radio", "checkbox" ], function() {
+ jQuery.valHooks[ this ] = {
+ get: function( elem ) {
+ // Handle the case where in Webkit "" is returned instead of "on" if a value isn't specified
+ return elem.getAttribute("value") === null ? "on" : elem.value;
+ }
+ };
+ });
+}
+jQuery.each([ "radio", "checkbox" ], function() {
+ jQuery.valHooks[ this ] = jQuery.extend( jQuery.valHooks[ this ], {
+ set: function( elem, value ) {
+ if ( jQuery.isArray( value ) ) {
+ return ( elem.checked = jQuery.inArray( jQuery(elem).val(), value ) >= 0 );
+ }
+ }
+ });
+});
+var rformElems = /^(?:textarea|input|select)$/i,
+ rtypenamespace = /^([^\.]*|)(?:\.(.+)|)$/,
+ rhoverHack = /(?:^|\s)hover(\.\S+|)\b/,
+ rkeyEvent = /^key/,
+ rmouseEvent = /^(?:mouse|contextmenu)|click/,
+ rfocusMorph = /^(?:focusinfocus|focusoutblur)$/,
+ hoverHack = function( events ) {
+ return jQuery.event.special.hover ? events : events.replace( rhoverHack, "mouseenter$1 mouseleave$1" );
+ };
+
+/*
+ * Helper functions for managing events -- not part of the public interface.
+ * Props to Dean Edwards' addEvent library for many of the ideas.
+ */
+jQuery.event = {
+
+ add: function( elem, types, handler, data, selector ) {
+
+ var elemData, eventHandle, events,
+ t, tns, type, namespaces, handleObj,
+ handleObjIn, handlers, special;
+
+ // Don't attach events to noData or text/comment nodes (allow plain objects tho)
+ if ( elem.nodeType === 3 || elem.nodeType === 8 || !types || !handler || !(elemData = jQuery._data( elem )) ) {
+ return;
+ }
+
+ // Caller can pass in an object of custom data in lieu of the handler
+ if ( handler.handler ) {
+ handleObjIn = handler;
+ handler = handleObjIn.handler;
+ selector = handleObjIn.selector;
+ }
+
+ // Make sure that the handler has a unique ID, used to find/remove it later
+ if ( !handler.guid ) {
+ handler.guid = jQuery.guid++;
+ }
+
+ // Init the element's event structure and main handler, if this is the first
+ events = elemData.events;
+ if ( !events ) {
+ elemData.events = events = {};
+ }
+ eventHandle = elemData.handle;
+ if ( !eventHandle ) {
+ elemData.handle = eventHandle = function( e ) {
+ // Discard the second event of a jQuery.event.trigger() and
+ // when an event is called after a page has unloaded
+ return typeof jQuery !== "undefined" && (!e || jQuery.event.triggered !== e.type) ?
+ jQuery.event.dispatch.apply( eventHandle.elem, arguments ) :
+ undefined;
+ };
+ // Add elem as a property of the handle fn to prevent a memory leak with IE non-native events
+ eventHandle.elem = elem;
+ }
+
+ // Handle multiple events separated by a space
+ // jQuery(...).bind("mouseover mouseout", fn);
+ types = jQuery.trim( hoverHack(types) ).split( " " );
+ for ( t = 0; t < types.length; t++ ) {
+
+ tns = rtypenamespace.exec( types[t] ) || [];
+ type = tns[1];
+ namespaces = ( tns[2] || "" ).split( "." ).sort();
+
+ // If event changes its type, use the special event handlers for the changed type
+ special = jQuery.event.special[ type ] || {};
+
+ // If selector defined, determine special event api type, otherwise given type
+ type = ( selector ? special.delegateType : special.bindType ) || type;
+
+ // Update special based on newly reset type
+ special = jQuery.event.special[ type ] || {};
+
+ // handleObj is passed to all event handlers
+ handleObj = jQuery.extend({
+ type: type,
+ origType: tns[1],
+ data: data,
+ handler: handler,
+ guid: handler.guid,
+ selector: selector,
+ needsContext: selector && jQuery.expr.match.needsContext.test( selector ),
+ namespace: namespaces.join(".")
+ }, handleObjIn );
+
+ // Init the event handler queue if we're the first
+ handlers = events[ type ];
+ if ( !handlers ) {
+ handlers = events[ type ] = [];
+ handlers.delegateCount = 0;
+
+ // Only use addEventListener/attachEvent if the special events handler returns false
+ if ( !special.setup || special.setup.call( elem, data, namespaces, eventHandle ) === false ) {
+ // Bind the global event handler to the element
+ if ( elem.addEventListener ) {
+ elem.addEventListener( type, eventHandle, false );
+
+ } else if ( elem.attachEvent ) {
+ elem.attachEvent( "on" + type, eventHandle );
+ }
+ }
+ }
+
+ if ( special.add ) {
+ special.add.call( elem, handleObj );
+
+ if ( !handleObj.handler.guid ) {
+ handleObj.handler.guid = handler.guid;
+ }
+ }
+
+ // Add to the element's handler list, delegates in front
+ if ( selector ) {
+ handlers.splice( handlers.delegateCount++, 0, handleObj );
+ } else {
+ handlers.push( handleObj );
+ }
+
+ // Keep track of which events have ever been used, for event optimization
+ jQuery.event.global[ type ] = true;
+ }
+
+ // Nullify elem to prevent memory leaks in IE
+ elem = null;
+ },
+
+ global: {},
+
+ // Detach an event or set of events from an element
+ remove: function( elem, types, handler, selector, mappedTypes ) {
+
+ var t, tns, type, origType, namespaces, origCount,
+ j, events, special, eventType, handleObj,
+ elemData = jQuery.hasData( elem ) && jQuery._data( elem );
+
+ if ( !elemData || !(events = elemData.events) ) {
+ return;
+ }
+
+ // Once for each type.namespace in types; type may be omitted
+ types = jQuery.trim( hoverHack( types || "" ) ).split(" ");
+ for ( t = 0; t < types.length; t++ ) {
+ tns = rtypenamespace.exec( types[t] ) || [];
+ type = origType = tns[1];
+ namespaces = tns[2];
+
+ // Unbind all events (on this namespace, if provided) for the element
+ if ( !type ) {
+ for ( type in events ) {
+ jQuery.event.remove( elem, type + types[ t ], handler, selector, true );
+ }
+ continue;
+ }
+
+ special = jQuery.event.special[ type ] || {};
+ type = ( selector? special.delegateType : special.bindType ) || type;
+ eventType = events[ type ] || [];
+ origCount = eventType.length;
+ namespaces = namespaces ? new RegExp("(^|\\.)" + namespaces.split(".").sort().join("\\.(?:.*\\.|)") + "(\\.|$)") : null;
+
+ // Remove matching events
+ for ( j = 0; j < eventType.length; j++ ) {
+ handleObj = eventType[ j ];
+
+ if ( ( mappedTypes || origType === handleObj.origType ) &&
+ ( !handler || handler.guid === handleObj.guid ) &&
+ ( !namespaces || namespaces.test( handleObj.namespace ) ) &&
+ ( !selector || selector === handleObj.selector || selector === "**" && handleObj.selector ) ) {
+ eventType.splice( j--, 1 );
+
+ if ( handleObj.selector ) {
+ eventType.delegateCount--;
+ }
+ if ( special.remove ) {
+ special.remove.call( elem, handleObj );
+ }
+ }
+ }
+
+ // Remove generic event handler if we removed something and no more handlers exist
+ // (avoids potential for endless recursion during removal of special event handlers)
+ if ( eventType.length === 0 && origCount !== eventType.length ) {
+ if ( !special.teardown || special.teardown.call( elem, namespaces, elemData.handle ) === false ) {
+ jQuery.removeEvent( elem, type, elemData.handle );
+ }
+
+ delete events[ type ];
+ }
+ }
+
+ // Remove the expando if it's no longer used
+ if ( jQuery.isEmptyObject( events ) ) {
+ delete elemData.handle;
+
+ // removeData also checks for emptiness and clears the expando if empty
+ // so use it instead of delete
+ jQuery.removeData( elem, "events", true );
+ }
+ },
+
+ // Events that are safe to short-circuit if no handlers are attached.
+ // Native DOM events should not be added, they may have inline handlers.
+ customEvent: {
+ "getData": true,
+ "setData": true,
+ "changeData": true
+ },
+
+ trigger: function( event, data, elem, onlyHandlers ) {
+ // Don't do events on text and comment nodes
+ if ( elem && (elem.nodeType === 3 || elem.nodeType === 8) ) {
+ return;
+ }
+
+ // Event object or event type
+ var cache, exclusive, i, cur, old, ontype, special, handle, eventPath, bubbleType,
+ type = event.type || event,
+ namespaces = [];
+
+ // focus/blur morphs to focusin/out; ensure we're not firing them right now
+ if ( rfocusMorph.test( type + jQuery.event.triggered ) ) {
+ return;
+ }
+
+ if ( type.indexOf( "!" ) >= 0 ) {
+ // Exclusive events trigger only for the exact event (no namespaces)
+ type = type.slice(0, -1);
+ exclusive = true;
+ }
+
+ if ( type.indexOf( "." ) >= 0 ) {
+ // Namespaced trigger; create a regexp to match event type in handle()
+ namespaces = type.split(".");
+ type = namespaces.shift();
+ namespaces.sort();
+ }
+
+ if ( (!elem || jQuery.event.customEvent[ type ]) && !jQuery.event.global[ type ] ) {
+ // No jQuery handlers for this event type, and it can't have inline handlers
+ return;
+ }
+
+ // Caller can pass in an Event, Object, or just an event type string
+ event = typeof event === "object" ?
+ // jQuery.Event object
+ event[ jQuery.expando ] ? event :
+ // Object literal
+ new jQuery.Event( type, event ) :
+ // Just the event type (string)
+ new jQuery.Event( type );
+
+ event.type = type;
+ event.isTrigger = true;
+ event.exclusive = exclusive;
+ event.namespace = namespaces.join( "." );
+ event.namespace_re = event.namespace? new RegExp("(^|\\.)" + namespaces.join("\\.(?:.*\\.|)") + "(\\.|$)") : null;
+ ontype = type.indexOf( ":" ) < 0 ? "on" + type : "";
+
+ // Handle a global trigger
+ if ( !elem ) {
+
+ // TODO: Stop taunting the data cache; remove global events and always attach to document
+ cache = jQuery.cache;
+ for ( i in cache ) {
+ if ( cache[ i ].events && cache[ i ].events[ type ] ) {
+ jQuery.event.trigger( event, data, cache[ i ].handle.elem, true );
+ }
+ }
+ return;
+ }
+
+ // Clean up the event in case it is being reused
+ event.result = undefined;
+ if ( !event.target ) {
+ event.target = elem;
+ }
+
+ // Clone any incoming data and prepend the event, creating the handler arg list
+ data = data != null ? jQuery.makeArray( data ) : [];
+ data.unshift( event );
+
+ // Allow special events to draw outside the lines
+ special = jQuery.event.special[ type ] || {};
+ if ( special.trigger && special.trigger.apply( elem, data ) === false ) {
+ return;
+ }
+
+ // Determine event propagation path in advance, per W3C events spec (#9951)
+ // Bubble up to document, then to window; watch for a global ownerDocument var (#9724)
+ eventPath = [[ elem, special.bindType || type ]];
+ if ( !onlyHandlers && !special.noBubble && !jQuery.isWindow( elem ) ) {
+
+ bubbleType = special.delegateType || type;
+ cur = rfocusMorph.test( bubbleType + type ) ? elem : elem.parentNode;
+ for ( old = elem; cur; cur = cur.parentNode ) {
+ eventPath.push([ cur, bubbleType ]);
+ old = cur;
+ }
+
+ // Only add window if we got to document (e.g., not plain obj or detached DOM)
+ if ( old === (elem.ownerDocument || document) ) {
+ eventPath.push([ old.defaultView || old.parentWindow || window, bubbleType ]);
+ }
+ }
+
+ // Fire handlers on the event path
+ for ( i = 0; i < eventPath.length && !event.isPropagationStopped(); i++ ) {
+
+ cur = eventPath[i][0];
+ event.type = eventPath[i][1];
+
+ handle = ( jQuery._data( cur, "events" ) || {} )[ event.type ] && jQuery._data( cur, "handle" );
+ if ( handle ) {
+ handle.apply( cur, data );
+ }
+ // Note that this is a bare JS function and not a jQuery handler
+ handle = ontype && cur[ ontype ];
+ if ( handle && jQuery.acceptData( cur ) && handle.apply && handle.apply( cur, data ) === false ) {
+ event.preventDefault();
+ }
+ }
+ event.type = type;
+
+ // If nobody prevented the default action, do it now
+ if ( !onlyHandlers && !event.isDefaultPrevented() ) {
+
+ if ( (!special._default || special._default.apply( elem.ownerDocument, data ) === false) &&
+ !(type === "click" && jQuery.nodeName( elem, "a" )) && jQuery.acceptData( elem ) ) {
+
+ // Call a native DOM method on the target with the same name name as the event.
+ // Can't use an .isFunction() check here because IE6/7 fails that test.
+ // Don't do default actions on window, that's where global variables be (#6170)
+ // IE<9 dies on focus/blur to hidden element (#1486)
+ if ( ontype && elem[ type ] && ((type !== "focus" && type !== "blur") || event.target.offsetWidth !== 0) && !jQuery.isWindow( elem ) ) {
+
+ // Don't re-trigger an onFOO event when we call its FOO() method
+ old = elem[ ontype ];
+
+ if ( old ) {
+ elem[ ontype ] = null;
+ }
+
+ // Prevent re-triggering of the same event, since we already bubbled it above
+ jQuery.event.triggered = type;
+ elem[ type ]();
+ jQuery.event.triggered = undefined;
+
+ if ( old ) {
+ elem[ ontype ] = old;
+ }
+ }
+ }
+ }
+
+ return event.result;
+ },
+
+ dispatch: function( event ) {
+
+ // Make a writable jQuery.Event from the native event object
+ event = jQuery.event.fix( event || window.event );
+
+ var i, j, cur, ret, selMatch, matched, matches, handleObj, sel, related,
+ handlers = ( (jQuery._data( this, "events" ) || {} )[ event.type ] || []),
+ delegateCount = handlers.delegateCount,
+ args = core_slice.call( arguments ),
+ run_all = !event.exclusive && !event.namespace,
+ special = jQuery.event.special[ event.type ] || {},
+ handlerQueue = [];
+
+ // Use the fix-ed jQuery.Event rather than the (read-only) native event
+ args[0] = event;
+ event.delegateTarget = this;
+
+ // Call the preDispatch hook for the mapped type, and let it bail if desired
+ if ( special.preDispatch && special.preDispatch.call( this, event ) === false ) {
+ return;
+ }
+
+ // Determine handlers that should run if there are delegated events
+ // Avoid non-left-click bubbling in Firefox (#3861)
+ if ( delegateCount && !(event.button && event.type === "click") ) {
+
+ for ( cur = event.target; cur != this; cur = cur.parentNode || this ) {
+
+ // Don't process clicks (ONLY) on disabled elements (#6911, #8165, #11382, #11764)
+ if ( cur.disabled !== true || event.type !== "click" ) {
+ selMatch = {};
+ matches = [];
+ for ( i = 0; i < delegateCount; i++ ) {
+ handleObj = handlers[ i ];
+ sel = handleObj.selector;
+
+ if ( selMatch[ sel ] === undefined ) {
+ selMatch[ sel ] = handleObj.needsContext ?
+ jQuery( sel, this ).index( cur ) >= 0 :
+ jQuery.find( sel, this, null, [ cur ] ).length;
+ }
+ if ( selMatch[ sel ] ) {
+ matches.push( handleObj );
+ }
+ }
+ if ( matches.length ) {
+ handlerQueue.push({ elem: cur, matches: matches });
+ }
+ }
+ }
+ }
+
+ // Add the remaining (directly-bound) handlers
+ if ( handlers.length > delegateCount ) {
+ handlerQueue.push({ elem: this, matches: handlers.slice( delegateCount ) });
+ }
+
+ // Run delegates first; they may want to stop propagation beneath us
+ for ( i = 0; i < handlerQueue.length && !event.isPropagationStopped(); i++ ) {
+ matched = handlerQueue[ i ];
+ event.currentTarget = matched.elem;
+
+ for ( j = 0; j < matched.matches.length && !event.isImmediatePropagationStopped(); j++ ) {
+ handleObj = matched.matches[ j ];
+
+ // Triggered event must either 1) be non-exclusive and have no namespace, or
+ // 2) have namespace(s) a subset or equal to those in the bound event (both can have no namespace).
+ if ( run_all || (!event.namespace && !handleObj.namespace) || event.namespace_re && event.namespace_re.test( handleObj.namespace ) ) {
+
+ event.data = handleObj.data;
+ event.handleObj = handleObj;
+
+ ret = ( (jQuery.event.special[ handleObj.origType ] || {}).handle || handleObj.handler )
+ .apply( matched.elem, args );
+
+ if ( ret !== undefined ) {
+ event.result = ret;
+ if ( ret === false ) {
+ event.preventDefault();
+ event.stopPropagation();
+ }
+ }
+ }
+ }
+ }
+
+ // Call the postDispatch hook for the mapped type
+ if ( special.postDispatch ) {
+ special.postDispatch.call( this, event );
+ }
+
+ return event.result;
+ },
+
+ // Includes some event props shared by KeyEvent and MouseEvent
+ // *** attrChange attrName relatedNode srcElement are not normalized, non-W3C, deprecated, will be removed in 1.8 ***
+ props: "attrChange attrName relatedNode srcElement altKey bubbles cancelable ctrlKey currentTarget eventPhase metaKey relatedTarget shiftKey target timeStamp view which".split(" "),
+
+ fixHooks: {},
+
+ keyHooks: {
+ props: "char charCode key keyCode".split(" "),
+ filter: function( event, original ) {
+
+ // Add which for key events
+ if ( event.which == null ) {
+ event.which = original.charCode != null ? original.charCode : original.keyCode;
+ }
+
+ return event;
+ }
+ },
+
+ mouseHooks: {
+ props: "button buttons clientX clientY fromElement offsetX offsetY pageX pageY screenX screenY toElement".split(" "),
+ filter: function( event, original ) {
+ var eventDoc, doc, body,
+ button = original.button,
+ fromElement = original.fromElement;
+
+ // Calculate pageX/Y if missing and clientX/Y available
+ if ( event.pageX == null && original.clientX != null ) {
+ eventDoc = event.target.ownerDocument || document;
+ doc = eventDoc.documentElement;
+ body = eventDoc.body;
+
+ event.pageX = original.clientX + ( doc && doc.scrollLeft || body && body.scrollLeft || 0 ) - ( doc && doc.clientLeft || body && body.clientLeft || 0 );
+ event.pageY = original.clientY + ( doc && doc.scrollTop || body && body.scrollTop || 0 ) - ( doc && doc.clientTop || body && body.clientTop || 0 );
+ }
+
+ // Add relatedTarget, if necessary
+ if ( !event.relatedTarget && fromElement ) {
+ event.relatedTarget = fromElement === event.target ? original.toElement : fromElement;
+ }
+
+ // Add which for click: 1 === left; 2 === middle; 3 === right
+ // Note: button is not normalized, so don't use it
+ if ( !event.which && button !== undefined ) {
+ event.which = ( button & 1 ? 1 : ( button & 2 ? 3 : ( button & 4 ? 2 : 0 ) ) );
+ }
+
+ return event;
+ }
+ },
+
+ fix: function( event ) {
+ if ( event[ jQuery.expando ] ) {
+ return event;
+ }
+
+ // Create a writable copy of the event object and normalize some properties
+ var i, prop,
+ originalEvent = event,
+ fixHook = jQuery.event.fixHooks[ event.type ] || {},
+ copy = fixHook.props ? this.props.concat( fixHook.props ) : this.props;
+
+ event = jQuery.Event( originalEvent );
+
+ for ( i = copy.length; i; ) {
+ prop = copy[ --i ];
+ event[ prop ] = originalEvent[ prop ];
+ }
+
+ // Fix target property, if necessary (#1925, IE 6/7/8 & Safari2)
+ if ( !event.target ) {
+ event.target = originalEvent.srcElement || document;
+ }
+
+ // Target should not be a text node (#504, Safari)
+ if ( event.target.nodeType === 3 ) {
+ event.target = event.target.parentNode;
+ }
+
+ // For mouse/key events, metaKey==false if it's undefined (#3368, #11328; IE6/7/8)
+ event.metaKey = !!event.metaKey;
+
+ return fixHook.filter? fixHook.filter( event, originalEvent ) : event;
+ },
+
+ special: {
+ load: {
+ // Prevent triggered image.load events from bubbling to window.load
+ noBubble: true
+ },
+
+ focus: {
+ delegateType: "focusin"
+ },
+ blur: {
+ delegateType: "focusout"
+ },
+
+ beforeunload: {
+ setup: function( data, namespaces, eventHandle ) {
+ // We only want to do this special case on windows
+ if ( jQuery.isWindow( this ) ) {
+ this.onbeforeunload = eventHandle;
+ }
+ },
+
+ teardown: function( namespaces, eventHandle ) {
+ if ( this.onbeforeunload === eventHandle ) {
+ this.onbeforeunload = null;
+ }
+ }
+ }
+ },
+
+ simulate: function( type, elem, event, bubble ) {
+ // Piggyback on a donor event to simulate a different one.
+ // Fake originalEvent to avoid donor's stopPropagation, but if the
+ // simulated event prevents default then we do the same on the donor.
+ var e = jQuery.extend(
+ new jQuery.Event(),
+ event,
+ { type: type,
+ isSimulated: true,
+ originalEvent: {}
+ }
+ );
+ if ( bubble ) {
+ jQuery.event.trigger( e, null, elem );
+ } else {
+ jQuery.event.dispatch.call( elem, e );
+ }
+ if ( e.isDefaultPrevented() ) {
+ event.preventDefault();
+ }
+ }
+};
+
+// Some plugins are using, but it's undocumented/deprecated and will be removed.
+// The 1.7 special event interface should provide all the hooks needed now.
+jQuery.event.handle = jQuery.event.dispatch;
+
+jQuery.removeEvent = document.removeEventListener ?
+ function( elem, type, handle ) {
+ if ( elem.removeEventListener ) {
+ elem.removeEventListener( type, handle, false );
+ }
+ } :
+ function( elem, type, handle ) {
+ var name = "on" + type;
+
+ if ( elem.detachEvent ) {
+
+ // #8545, #7054, preventing memory leaks for custom events in IE6-8
+ // detachEvent needed property on element, by name of that event, to properly expose it to GC
+ if ( typeof elem[ name ] === "undefined" ) {
+ elem[ name ] = null;
+ }
+
+ elem.detachEvent( name, handle );
+ }
+ };
+
+jQuery.Event = function( src, props ) {
+ // Allow instantiation without the 'new' keyword
+ if ( !(this instanceof jQuery.Event) ) {
+ return new jQuery.Event( src, props );
+ }
+
+ // Event object
+ if ( src && src.type ) {
+ this.originalEvent = src;
+ this.type = src.type;
+
+ // Events bubbling up the document may have been marked as prevented
+ // by a handler lower down the tree; reflect the correct value.
+ this.isDefaultPrevented = ( src.defaultPrevented || src.returnValue === false ||
+ src.getPreventDefault && src.getPreventDefault() ) ? returnTrue : returnFalse;
+
+ // Event type
+ } else {
+ this.type = src;
+ }
+
+ // Put explicitly provided properties onto the event object
+ if ( props ) {
+ jQuery.extend( this, props );
+ }
+
+ // Create a timestamp if incoming event doesn't have one
+ this.timeStamp = src && src.timeStamp || jQuery.now();
+
+ // Mark it as fixed
+ this[ jQuery.expando ] = true;
+};
+
+function returnFalse() {
+ return false;
+}
+function returnTrue() {
+ return true;
+}
+
+// jQuery.Event is based on DOM3 Events as specified by the ECMAScript Language Binding
+// http://www.w3.org/TR/2003/WD-DOM-Level-3-Events-20030331/ecma-script-binding.html
+jQuery.Event.prototype = {
+ preventDefault: function() {
+ this.isDefaultPrevented = returnTrue;
+
+ var e = this.originalEvent;
+ if ( !e ) {
+ return;
+ }
+
+ // if preventDefault exists run it on the original event
+ if ( e.preventDefault ) {
+ e.preventDefault();
+
+ // otherwise set the returnValue property of the original event to false (IE)
+ } else {
+ e.returnValue = false;
+ }
+ },
+ stopPropagation: function() {
+ this.isPropagationStopped = returnTrue;
+
+ var e = this.originalEvent;
+ if ( !e ) {
+ return;
+ }
+ // if stopPropagation exists run it on the original event
+ if ( e.stopPropagation ) {
+ e.stopPropagation();
+ }
+ // otherwise set the cancelBubble property of the original event to true (IE)
+ e.cancelBubble = true;
+ },
+ stopImmediatePropagation: function() {
+ this.isImmediatePropagationStopped = returnTrue;
+ this.stopPropagation();
+ },
+ isDefaultPrevented: returnFalse,
+ isPropagationStopped: returnFalse,
+ isImmediatePropagationStopped: returnFalse
+};
+
+// Create mouseenter/leave events using mouseover/out and event-time checks
+jQuery.each({
+ mouseenter: "mouseover",
+ mouseleave: "mouseout"
+}, function( orig, fix ) {
+ jQuery.event.special[ orig ] = {
+ delegateType: fix,
+ bindType: fix,
+
+ handle: function( event ) {
+ var ret,
+ target = this,
+ related = event.relatedTarget,
+ handleObj = event.handleObj,
+ selector = handleObj.selector;
+
+ // For mousenter/leave call the handler if related is outside the target.
+ // NB: No relatedTarget if the mouse left/entered the browser window
+ if ( !related || (related !== target && !jQuery.contains( target, related )) ) {
+ event.type = handleObj.origType;
+ ret = handleObj.handler.apply( this, arguments );
+ event.type = fix;
+ }
+ return ret;
+ }
+ };
+});
+
+// IE submit delegation
+if ( !jQuery.support.submitBubbles ) {
+
+ jQuery.event.special.submit = {
+ setup: function() {
+ // Only need this for delegated form submit events
+ if ( jQuery.nodeName( this, "form" ) ) {
+ return false;
+ }
+
+ // Lazy-add a submit handler when a descendant form may potentially be submitted
+ jQuery.event.add( this, "click._submit keypress._submit", function( e ) {
+ // Node name check avoids a VML-related crash in IE (#9807)
+ var elem = e.target,
+ form = jQuery.nodeName( elem, "input" ) || jQuery.nodeName( elem, "button" ) ? elem.form : undefined;
+ if ( form && !jQuery._data( form, "_submit_attached" ) ) {
+ jQuery.event.add( form, "submit._submit", function( event ) {
+ event._submit_bubble = true;
+ });
+ jQuery._data( form, "_submit_attached", true );
+ }
+ });
+ // return undefined since we don't need an event listener
+ },
+
+ postDispatch: function( event ) {
+ // If form was submitted by the user, bubble the event up the tree
+ if ( event._submit_bubble ) {
+ delete event._submit_bubble;
+ if ( this.parentNode && !event.isTrigger ) {
+ jQuery.event.simulate( "submit", this.parentNode, event, true );
+ }
+ }
+ },
+
+ teardown: function() {
+ // Only need this for delegated form submit events
+ if ( jQuery.nodeName( this, "form" ) ) {
+ return false;
+ }
+
+ // Remove delegated handlers; cleanData eventually reaps submit handlers attached above
+ jQuery.event.remove( this, "._submit" );
+ }
+ };
+}
+
+// IE change delegation and checkbox/radio fix
+if ( !jQuery.support.changeBubbles ) {
+
+ jQuery.event.special.change = {
+
+ setup: function() {
+
+ if ( rformElems.test( this.nodeName ) ) {
+ // IE doesn't fire change on a check/radio until blur; trigger it on click
+ // after a propertychange. Eat the blur-change in special.change.handle.
+ // This still fires onchange a second time for check/radio after blur.
+ if ( this.type === "checkbox" || this.type === "radio" ) {
+ jQuery.event.add( this, "propertychange._change", function( event ) {
+ if ( event.originalEvent.propertyName === "checked" ) {
+ this._just_changed = true;
+ }
+ });
+ jQuery.event.add( this, "click._change", function( event ) {
+ if ( this._just_changed && !event.isTrigger ) {
+ this._just_changed = false;
+ }
+ // Allow triggered, simulated change events (#11500)
+ jQuery.event.simulate( "change", this, event, true );
+ });
+ }
+ return false;
+ }
+ // Delegated event; lazy-add a change handler on descendant inputs
+ jQuery.event.add( this, "beforeactivate._change", function( e ) {
+ var elem = e.target;
+
+ if ( rformElems.test( elem.nodeName ) && !jQuery._data( elem, "_change_attached" ) ) {
+ jQuery.event.add( elem, "change._change", function( event ) {
+ if ( this.parentNode && !event.isSimulated && !event.isTrigger ) {
+ jQuery.event.simulate( "change", this.parentNode, event, true );
+ }
+ });
+ jQuery._data( elem, "_change_attached", true );
+ }
+ });
+ },
+
+ handle: function( event ) {
+ var elem = event.target;
+
+ // Swallow native change events from checkbox/radio, we already triggered them above
+ if ( this !== elem || event.isSimulated || event.isTrigger || (elem.type !== "radio" && elem.type !== "checkbox") ) {
+ return event.handleObj.handler.apply( this, arguments );
+ }
+ },
+
+ teardown: function() {
+ jQuery.event.remove( this, "._change" );
+
+ return !rformElems.test( this.nodeName );
+ }
+ };
+}
+
+// Create "bubbling" focus and blur events
+if ( !jQuery.support.focusinBubbles ) {
+ jQuery.each({ focus: "focusin", blur: "focusout" }, function( orig, fix ) {
+
+ // Attach a single capturing handler while someone wants focusin/focusout
+ var attaches = 0,
+ handler = function( event ) {
+ jQuery.event.simulate( fix, event.target, jQuery.event.fix( event ), true );
+ };
+
+ jQuery.event.special[ fix ] = {
+ setup: function() {
+ if ( attaches++ === 0 ) {
+ document.addEventListener( orig, handler, true );
+ }
+ },
+ teardown: function() {
+ if ( --attaches === 0 ) {
+ document.removeEventListener( orig, handler, true );
+ }
+ }
+ };
+ });
+}
+
+jQuery.fn.extend({
+
+ on: function( types, selector, data, fn, /*INTERNAL*/ one ) {
+ var origFn, type;
+
+ // Types can be a map of types/handlers
+ if ( typeof types === "object" ) {
+ // ( types-Object, selector, data )
+ if ( typeof selector !== "string" ) { // && selector != null
+ // ( types-Object, data )
+ data = data || selector;
+ selector = undefined;
+ }
+ for ( type in types ) {
+ this.on( type, selector, data, types[ type ], one );
+ }
+ return this;
+ }
+
+ if ( data == null && fn == null ) {
+ // ( types, fn )
+ fn = selector;
+ data = selector = undefined;
+ } else if ( fn == null ) {
+ if ( typeof selector === "string" ) {
+ // ( types, selector, fn )
+ fn = data;
+ data = undefined;
+ } else {
+ // ( types, data, fn )
+ fn = data;
+ data = selector;
+ selector = undefined;
+ }
+ }
+ if ( fn === false ) {
+ fn = returnFalse;
+ } else if ( !fn ) {
+ return this;
+ }
+
+ if ( one === 1 ) {
+ origFn = fn;
+ fn = function( event ) {
+ // Can use an empty set, since event contains the info
+ jQuery().off( event );
+ return origFn.apply( this, arguments );
+ };
+ // Use same guid so caller can remove using origFn
+ fn.guid = origFn.guid || ( origFn.guid = jQuery.guid++ );
+ }
+ return this.each( function() {
+ jQuery.event.add( this, types, fn, data, selector );
+ });
+ },
+ one: function( types, selector, data, fn ) {
+ return this.on( types, selector, data, fn, 1 );
+ },
+ off: function( types, selector, fn ) {
+ var handleObj, type;
+ if ( types && types.preventDefault && types.handleObj ) {
+ // ( event ) dispatched jQuery.Event
+ handleObj = types.handleObj;
+ jQuery( types.delegateTarget ).off(
+ handleObj.namespace ? handleObj.origType + "." + handleObj.namespace : handleObj.origType,
+ handleObj.selector,
+ handleObj.handler
+ );
+ return this;
+ }
+ if ( typeof types === "object" ) {
+ // ( types-object [, selector] )
+ for ( type in types ) {
+ this.off( type, selector, types[ type ] );
+ }
+ return this;
+ }
+ if ( selector === false || typeof selector === "function" ) {
+ // ( types [, fn] )
+ fn = selector;
+ selector = undefined;
+ }
+ if ( fn === false ) {
+ fn = returnFalse;
+ }
+ return this.each(function() {
+ jQuery.event.remove( this, types, fn, selector );
+ });
+ },
+
+ bind: function( types, data, fn ) {
+ return this.on( types, null, data, fn );
+ },
+ unbind: function( types, fn ) {
+ return this.off( types, null, fn );
+ },
+
+ live: function( types, data, fn ) {
+ jQuery( this.context ).on( types, this.selector, data, fn );
+ return this;
+ },
+ die: function( types, fn ) {
+ jQuery( this.context ).off( types, this.selector || "**", fn );
+ return this;
+ },
+
+ delegate: function( selector, types, data, fn ) {
+ return this.on( types, selector, data, fn );
+ },
+ undelegate: function( selector, types, fn ) {
+ // ( namespace ) or ( selector, types [, fn] )
+ return arguments.length === 1 ? this.off( selector, "**" ) : this.off( types, selector || "**", fn );
+ },
+
+ trigger: function( type, data ) {
+ return this.each(function() {
+ jQuery.event.trigger( type, data, this );
+ });
+ },
+ triggerHandler: function( type, data ) {
+ if ( this[0] ) {
+ return jQuery.event.trigger( type, data, this[0], true );
+ }
+ },
+
+ toggle: function( fn ) {
+ // Save reference to arguments for access in closure
+ var args = arguments,
+ guid = fn.guid || jQuery.guid++,
+ i = 0,
+ toggler = function( event ) {
+ // Figure out which function to execute
+ var lastToggle = ( jQuery._data( this, "lastToggle" + fn.guid ) || 0 ) % i;
+ jQuery._data( this, "lastToggle" + fn.guid, lastToggle + 1 );
+
+ // Make sure that clicks stop
+ event.preventDefault();
+
+ // and execute the function
+ return args[ lastToggle ].apply( this, arguments ) || false;
+ };
+
+ // link all the functions, so any of them can unbind this click handler
+ toggler.guid = guid;
+ while ( i < args.length ) {
+ args[ i++ ].guid = guid;
+ }
+
+ return this.click( toggler );
+ },
+
+ hover: function( fnOver, fnOut ) {
+ return this.mouseenter( fnOver ).mouseleave( fnOut || fnOver );
+ }
+});
+
+jQuery.each( ("blur focus focusin focusout load resize scroll unload click dblclick " +
+ "mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave " +
+ "change select submit keydown keypress keyup error contextmenu").split(" "), function( i, name ) {
+
+ // Handle event binding
+ jQuery.fn[ name ] = function( data, fn ) {
+ if ( fn == null ) {
+ fn = data;
+ data = null;
+ }
+
+ return arguments.length > 0 ?
+ this.on( name, null, data, fn ) :
+ this.trigger( name );
+ };
+
+ if ( rkeyEvent.test( name ) ) {
+ jQuery.event.fixHooks[ name ] = jQuery.event.keyHooks;
+ }
+
+ if ( rmouseEvent.test( name ) ) {
+ jQuery.event.fixHooks[ name ] = jQuery.event.mouseHooks;
+ }
+});
+/*!
+ * Sizzle CSS Selector Engine
+ * Copyright 2012 jQuery Foundation and other contributors
+ * Released under the MIT license
+ * http://sizzlejs.com/
+ */
+(function( window, undefined ) {
+
+var cachedruns,
+ assertGetIdNotName,
+ Expr,
+ getText,
+ isXML,
+ contains,
+ compile,
+ sortOrder,
+ hasDuplicate,
+ outermostContext,
+
+ baseHasDuplicate = true,
+ strundefined = "undefined",
+
+ expando = ( "sizcache" + Math.random() ).replace( ".", "" ),
+
+ Token = String,
+ document = window.document,
+ docElem = document.documentElement,
+ dirruns = 0,
+ done = 0,
+ pop = [].pop,
+ push = [].push,
+ slice = [].slice,
+ // Use a stripped-down indexOf if a native one is unavailable
+ indexOf = [].indexOf || function( elem ) {
+ var i = 0,
+ len = this.length;
+ for ( ; i < len; i++ ) {
+ if ( this[i] === elem ) {
+ return i;
+ }
+ }
+ return -1;
+ },
+
+ // Augment a function for special use by Sizzle
+ markFunction = function( fn, value ) {
+ fn[ expando ] = value == null || value;
+ return fn;
+ },
+
+ createCache = function() {
+ var cache = {},
+ keys = [];
+
+ return markFunction(function( key, value ) {
+ // Only keep the most recent entries
+ if ( keys.push( key ) > Expr.cacheLength ) {
+ delete cache[ keys.shift() ];
+ }
+
+ // Retrieve with (key + " ") to avoid collision with native Object.prototype properties (see Issue #157)
+ return (cache[ key + " " ] = value);
+ }, cache );
+ },
+
+ classCache = createCache(),
+ tokenCache = createCache(),
+ compilerCache = createCache(),
+
+ // Regex
+
+ // Whitespace characters http://www.w3.org/TR/css3-selectors/#whitespace
+ whitespace = "[\\x20\\t\\r\\n\\f]",
+ // http://www.w3.org/TR/css3-syntax/#characters
+ characterEncoding = "(?:\\\\.|[-\\w]|[^\\x00-\\xa0])+",
+
+ // Loosely modeled on CSS identifier characters
+ // An unquoted value should be a CSS identifier (http://www.w3.org/TR/css3-selectors/#attribute-selectors)
+ // Proper syntax: http://www.w3.org/TR/CSS21/syndata.html#value-def-identifier
+ identifier = characterEncoding.replace( "w", "w#" ),
+
+ // Acceptable operators http://www.w3.org/TR/selectors/#attribute-selectors
+ operators = "([*^$|!~]?=)",
+ attributes = "\\[" + whitespace + "*(" + characterEncoding + ")" + whitespace +
+ "*(?:" + operators + whitespace + "*(?:(['\"])((?:\\\\.|[^\\\\])*?)\\3|(" + identifier + ")|)|)" + whitespace + "*\\]",
+
+ // Prefer arguments not in parens/brackets,
+ // then attribute selectors and non-pseudos (denoted by :),
+ // then anything else
+ // These preferences are here to reduce the number of selectors
+ // needing tokenize in the PSEUDO preFilter
+ pseudos = ":(" + characterEncoding + ")(?:\\((?:(['\"])((?:\\\\.|[^\\\\])*?)\\2|([^()[\\]]*|(?:(?:" + attributes + ")|[^:]|\\\\.)*|.*))\\)|)",
+
+ // For matchExpr.POS and matchExpr.needsContext
+ pos = ":(even|odd|eq|gt|lt|nth|first|last)(?:\\(" + whitespace +
+ "*((?:-\\d)?\\d*)" + whitespace + "*\\)|)(?=[^-]|$)",
+
+ // Leading and non-escaped trailing whitespace, capturing some non-whitespace characters preceding the latter
+ rtrim = new RegExp( "^" + whitespace + "+|((?:^|[^\\\\])(?:\\\\.)*)" + whitespace + "+$", "g" ),
+
+ rcomma = new RegExp( "^" + whitespace + "*," + whitespace + "*" ),
+ rcombinators = new RegExp( "^" + whitespace + "*([\\x20\\t\\r\\n\\f>+~])" + whitespace + "*" ),
+ rpseudo = new RegExp( pseudos ),
+
+ // Easily-parseable/retrievable ID or TAG or CLASS selectors
+ rquickExpr = /^(?:#([\w\-]+)|(\w+)|\.([\w\-]+))$/,
+
+ rnot = /^:not/,
+ rsibling = /[\x20\t\r\n\f]*[+~]/,
+ rendsWithNot = /:not\($/,
+
+ rheader = /h\d/i,
+ rinputs = /input|select|textarea|button/i,
+
+ rbackslash = /\\(?!\\)/g,
+
+ matchExpr = {
+ "ID": new RegExp( "^#(" + characterEncoding + ")" ),
+ "CLASS": new RegExp( "^\\.(" + characterEncoding + ")" ),
+ "NAME": new RegExp( "^\\[name=['\"]?(" + characterEncoding + ")['\"]?\\]" ),
+ "TAG": new RegExp( "^(" + characterEncoding.replace( "w", "w*" ) + ")" ),
+ "ATTR": new RegExp( "^" + attributes ),
+ "PSEUDO": new RegExp( "^" + pseudos ),
+ "POS": new RegExp( pos, "i" ),
+ "CHILD": new RegExp( "^:(only|nth|first|last)-child(?:\\(" + whitespace +
+ "*(even|odd|(([+-]|)(\\d*)n|)" + whitespace + "*(?:([+-]|)" + whitespace +
+ "*(\\d+)|))" + whitespace + "*\\)|)", "i" ),
+ // For use in libraries implementing .is()
+ "needsContext": new RegExp( "^" + whitespace + "*[>+~]|" + pos, "i" )
+ },
+
+ // Support
+
+ // Used for testing something on an element
+ assert = function( fn ) {
+ var div = document.createElement("div");
+
+ try {
+ return fn( div );
+ } catch (e) {
+ return false;
+ } finally {
+ // release memory in IE
+ div = null;
+ }
+ },
+
+ // Check if getElementsByTagName("*") returns only elements
+ assertTagNameNoComments = assert(function( div ) {
+ div.appendChild( document.createComment("") );
+ return !div.getElementsByTagName("*").length;
+ }),
+
+ // Check if getAttribute returns normalized href attributes
+ assertHrefNotNormalized = assert(function( div ) {
+ div.innerHTML = " ";
+ return div.firstChild && typeof div.firstChild.getAttribute !== strundefined &&
+ div.firstChild.getAttribute("href") === "#";
+ }),
+
+ // Check if attributes should be retrieved by attribute nodes
+ assertAttributes = assert(function( div ) {
+ div.innerHTML = " ";
+ var type = typeof div.lastChild.getAttribute("multiple");
+ // IE8 returns a string for some attributes even when not present
+ return type !== "boolean" && type !== "string";
+ }),
+
+ // Check if getElementsByClassName can be trusted
+ assertUsableClassName = assert(function( div ) {
+ // Opera can't find a second classname (in 9.6)
+ div.innerHTML = "
";
+ if ( !div.getElementsByClassName || !div.getElementsByClassName("e").length ) {
+ return false;
+ }
+
+ // Safari 3.2 caches class attributes and doesn't catch changes
+ div.lastChild.className = "e";
+ return div.getElementsByClassName("e").length === 2;
+ }),
+
+ // Check if getElementById returns elements by name
+ // Check if getElementsByName privileges form controls or returns elements by ID
+ assertUsableName = assert(function( div ) {
+ // Inject content
+ div.id = expando + 0;
+ div.innerHTML = "
";
+ docElem.insertBefore( div, docElem.firstChild );
+
+ // Test
+ var pass = document.getElementsByName &&
+ // buggy browsers will return fewer than the correct 2
+ document.getElementsByName( expando ).length === 2 +
+ // buggy browsers will return more than the correct 0
+ document.getElementsByName( expando + 0 ).length;
+ assertGetIdNotName = !document.getElementById( expando );
+
+ // Cleanup
+ docElem.removeChild( div );
+
+ return pass;
+ });
+
+// If slice is not available, provide a backup
+try {
+ slice.call( docElem.childNodes, 0 )[0].nodeType;
+} catch ( e ) {
+ slice = function( i ) {
+ var elem,
+ results = [];
+ for ( ; (elem = this[i]); i++ ) {
+ results.push( elem );
+ }
+ return results;
+ };
+}
+
+function Sizzle( selector, context, results, seed ) {
+ results = results || [];
+ context = context || document;
+ var match, elem, xml, m,
+ nodeType = context.nodeType;
+
+ if ( !selector || typeof selector !== "string" ) {
+ return results;
+ }
+
+ if ( nodeType !== 1 && nodeType !== 9 ) {
+ return [];
+ }
+
+ xml = isXML( context );
+
+ if ( !xml && !seed ) {
+ if ( (match = rquickExpr.exec( selector )) ) {
+ // Speed-up: Sizzle("#ID")
+ if ( (m = match[1]) ) {
+ if ( nodeType === 9 ) {
+ elem = context.getElementById( m );
+ // Check parentNode to catch when Blackberry 4.6 returns
+ // nodes that are no longer in the document #6963
+ if ( elem && elem.parentNode ) {
+ // Handle the case where IE, Opera, and Webkit return items
+ // by name instead of ID
+ if ( elem.id === m ) {
+ results.push( elem );
+ return results;
+ }
+ } else {
+ return results;
+ }
+ } else {
+ // Context is not a document
+ if ( context.ownerDocument && (elem = context.ownerDocument.getElementById( m )) &&
+ contains( context, elem ) && elem.id === m ) {
+ results.push( elem );
+ return results;
+ }
+ }
+
+ // Speed-up: Sizzle("TAG")
+ } else if ( match[2] ) {
+ push.apply( results, slice.call(context.getElementsByTagName( selector ), 0) );
+ return results;
+
+ // Speed-up: Sizzle(".CLASS")
+ } else if ( (m = match[3]) && assertUsableClassName && context.getElementsByClassName ) {
+ push.apply( results, slice.call(context.getElementsByClassName( m ), 0) );
+ return results;
+ }
+ }
+ }
+
+ // All others
+ return select( selector.replace( rtrim, "$1" ), context, results, seed, xml );
+}
+
+Sizzle.matches = function( expr, elements ) {
+ return Sizzle( expr, null, null, elements );
+};
+
+Sizzle.matchesSelector = function( elem, expr ) {
+ return Sizzle( expr, null, null, [ elem ] ).length > 0;
+};
+
+// Returns a function to use in pseudos for input types
+function createInputPseudo( type ) {
+ return function( elem ) {
+ var name = elem.nodeName.toLowerCase();
+ return name === "input" && elem.type === type;
+ };
+}
+
+// Returns a function to use in pseudos for buttons
+function createButtonPseudo( type ) {
+ return function( elem ) {
+ var name = elem.nodeName.toLowerCase();
+ return (name === "input" || name === "button") && elem.type === type;
+ };
+}
+
+// Returns a function to use in pseudos for positionals
+function createPositionalPseudo( fn ) {
+ return markFunction(function( argument ) {
+ argument = +argument;
+ return markFunction(function( seed, matches ) {
+ var j,
+ matchIndexes = fn( [], seed.length, argument ),
+ i = matchIndexes.length;
+
+ // Match elements found at the specified indexes
+ while ( i-- ) {
+ if ( seed[ (j = matchIndexes[i]) ] ) {
+ seed[j] = !(matches[j] = seed[j]);
+ }
+ }
+ });
+ });
+}
+
+/**
+ * Utility function for retrieving the text value of an array of DOM nodes
+ * @param {Array|Element} elem
+ */
+getText = Sizzle.getText = function( elem ) {
+ var node,
+ ret = "",
+ i = 0,
+ nodeType = elem.nodeType;
+
+ if ( nodeType ) {
+ if ( nodeType === 1 || nodeType === 9 || nodeType === 11 ) {
+ // Use textContent for elements
+ // innerText usage removed for consistency of new lines (see #11153)
+ if ( typeof elem.textContent === "string" ) {
+ return elem.textContent;
+ } else {
+ // Traverse its children
+ for ( elem = elem.firstChild; elem; elem = elem.nextSibling ) {
+ ret += getText( elem );
+ }
+ }
+ } else if ( nodeType === 3 || nodeType === 4 ) {
+ return elem.nodeValue;
+ }
+ // Do not include comment or processing instruction nodes
+ } else {
+
+ // If no nodeType, this is expected to be an array
+ for ( ; (node = elem[i]); i++ ) {
+ // Do not traverse comment nodes
+ ret += getText( node );
+ }
+ }
+ return ret;
+};
+
+isXML = Sizzle.isXML = function( elem ) {
+ // documentElement is verified for cases where it doesn't yet exist
+ // (such as loading iframes in IE - #4833)
+ var documentElement = elem && (elem.ownerDocument || elem).documentElement;
+ return documentElement ? documentElement.nodeName !== "HTML" : false;
+};
+
+// Element contains another
+contains = Sizzle.contains = docElem.contains ?
+ function( a, b ) {
+ var adown = a.nodeType === 9 ? a.documentElement : a,
+ bup = b && b.parentNode;
+ return a === bup || !!( bup && bup.nodeType === 1 && adown.contains && adown.contains(bup) );
+ } :
+ docElem.compareDocumentPosition ?
+ function( a, b ) {
+ return b && !!( a.compareDocumentPosition( b ) & 16 );
+ } :
+ function( a, b ) {
+ while ( (b = b.parentNode) ) {
+ if ( b === a ) {
+ return true;
+ }
+ }
+ return false;
+ };
+
+Sizzle.attr = function( elem, name ) {
+ var val,
+ xml = isXML( elem );
+
+ if ( !xml ) {
+ name = name.toLowerCase();
+ }
+ if ( (val = Expr.attrHandle[ name ]) ) {
+ return val( elem );
+ }
+ if ( xml || assertAttributes ) {
+ return elem.getAttribute( name );
+ }
+ val = elem.getAttributeNode( name );
+ return val ?
+ typeof elem[ name ] === "boolean" ?
+ elem[ name ] ? name : null :
+ val.specified ? val.value : null :
+ null;
+};
+
+Expr = Sizzle.selectors = {
+
+ // Can be adjusted by the user
+ cacheLength: 50,
+
+ createPseudo: markFunction,
+
+ match: matchExpr,
+
+ // IE6/7 return a modified href
+ attrHandle: assertHrefNotNormalized ?
+ {} :
+ {
+ "href": function( elem ) {
+ return elem.getAttribute( "href", 2 );
+ },
+ "type": function( elem ) {
+ return elem.getAttribute("type");
+ }
+ },
+
+ find: {
+ "ID": assertGetIdNotName ?
+ function( id, context, xml ) {
+ if ( typeof context.getElementById !== strundefined && !xml ) {
+ var m = context.getElementById( id );
+ // Check parentNode to catch when Blackberry 4.6 returns
+ // nodes that are no longer in the document #6963
+ return m && m.parentNode ? [m] : [];
+ }
+ } :
+ function( id, context, xml ) {
+ if ( typeof context.getElementById !== strundefined && !xml ) {
+ var m = context.getElementById( id );
+
+ return m ?
+ m.id === id || typeof m.getAttributeNode !== strundefined && m.getAttributeNode("id").value === id ?
+ [m] :
+ undefined :
+ [];
+ }
+ },
+
+ "TAG": assertTagNameNoComments ?
+ function( tag, context ) {
+ if ( typeof context.getElementsByTagName !== strundefined ) {
+ return context.getElementsByTagName( tag );
+ }
+ } :
+ function( tag, context ) {
+ var results = context.getElementsByTagName( tag );
+
+ // Filter out possible comments
+ if ( tag === "*" ) {
+ var elem,
+ tmp = [],
+ i = 0;
+
+ for ( ; (elem = results[i]); i++ ) {
+ if ( elem.nodeType === 1 ) {
+ tmp.push( elem );
+ }
+ }
+
+ return tmp;
+ }
+ return results;
+ },
+
+ "NAME": assertUsableName && function( tag, context ) {
+ if ( typeof context.getElementsByName !== strundefined ) {
+ return context.getElementsByName( name );
+ }
+ },
+
+ "CLASS": assertUsableClassName && function( className, context, xml ) {
+ if ( typeof context.getElementsByClassName !== strundefined && !xml ) {
+ return context.getElementsByClassName( className );
+ }
+ }
+ },
+
+ relative: {
+ ">": { dir: "parentNode", first: true },
+ " ": { dir: "parentNode" },
+ "+": { dir: "previousSibling", first: true },
+ "~": { dir: "previousSibling" }
+ },
+
+ preFilter: {
+ "ATTR": function( match ) {
+ match[1] = match[1].replace( rbackslash, "" );
+
+ // Move the given value to match[3] whether quoted or unquoted
+ match[3] = ( match[4] || match[5] || "" ).replace( rbackslash, "" );
+
+ if ( match[2] === "~=" ) {
+ match[3] = " " + match[3] + " ";
+ }
+
+ return match.slice( 0, 4 );
+ },
+
+ "CHILD": function( match ) {
+ /* matches from matchExpr["CHILD"]
+ 1 type (only|nth|...)
+ 2 argument (even|odd|\d*|\d*n([+-]\d+)?|...)
+ 3 xn-component of xn+y argument ([+-]?\d*n|)
+ 4 sign of xn-component
+ 5 x of xn-component
+ 6 sign of y-component
+ 7 y of y-component
+ */
+ match[1] = match[1].toLowerCase();
+
+ if ( match[1] === "nth" ) {
+ // nth-child requires argument
+ if ( !match[2] ) {
+ Sizzle.error( match[0] );
+ }
+
+ // numeric x and y parameters for Expr.filter.CHILD
+ // remember that false/true cast respectively to 0/1
+ match[3] = +( match[3] ? match[4] + (match[5] || 1) : 2 * ( match[2] === "even" || match[2] === "odd" ) );
+ match[4] = +( ( match[6] + match[7] ) || match[2] === "odd" );
+
+ // other types prohibit arguments
+ } else if ( match[2] ) {
+ Sizzle.error( match[0] );
+ }
+
+ return match;
+ },
+
+ "PSEUDO": function( match ) {
+ var unquoted, excess;
+ if ( matchExpr["CHILD"].test( match[0] ) ) {
+ return null;
+ }
+
+ if ( match[3] ) {
+ match[2] = match[3];
+ } else if ( (unquoted = match[4]) ) {
+ // Only check arguments that contain a pseudo
+ if ( rpseudo.test(unquoted) &&
+ // Get excess from tokenize (recursively)
+ (excess = tokenize( unquoted, true )) &&
+ // advance to the next closing parenthesis
+ (excess = unquoted.indexOf( ")", unquoted.length - excess ) - unquoted.length) ) {
+
+ // excess is a negative index
+ unquoted = unquoted.slice( 0, excess );
+ match[0] = match[0].slice( 0, excess );
+ }
+ match[2] = unquoted;
+ }
+
+ // Return only captures needed by the pseudo filter method (type and argument)
+ return match.slice( 0, 3 );
+ }
+ },
+
+ filter: {
+ "ID": assertGetIdNotName ?
+ function( id ) {
+ id = id.replace( rbackslash, "" );
+ return function( elem ) {
+ return elem.getAttribute("id") === id;
+ };
+ } :
+ function( id ) {
+ id = id.replace( rbackslash, "" );
+ return function( elem ) {
+ var node = typeof elem.getAttributeNode !== strundefined && elem.getAttributeNode("id");
+ return node && node.value === id;
+ };
+ },
+
+ "TAG": function( nodeName ) {
+ if ( nodeName === "*" ) {
+ return function() { return true; };
+ }
+ nodeName = nodeName.replace( rbackslash, "" ).toLowerCase();
+
+ return function( elem ) {
+ return elem.nodeName && elem.nodeName.toLowerCase() === nodeName;
+ };
+ },
+
+ "CLASS": function( className ) {
+ var pattern = classCache[ expando ][ className + " " ];
+
+ return pattern ||
+ (pattern = new RegExp( "(^|" + whitespace + ")" + className + "(" + whitespace + "|$)" )) &&
+ classCache( className, function( elem ) {
+ return pattern.test( elem.className || (typeof elem.getAttribute !== strundefined && elem.getAttribute("class")) || "" );
+ });
+ },
+
+ "ATTR": function( name, operator, check ) {
+ return function( elem, context ) {
+ var result = Sizzle.attr( elem, name );
+
+ if ( result == null ) {
+ return operator === "!=";
+ }
+ if ( !operator ) {
+ return true;
+ }
+
+ result += "";
+
+ return operator === "=" ? result === check :
+ operator === "!=" ? result !== check :
+ operator === "^=" ? check && result.indexOf( check ) === 0 :
+ operator === "*=" ? check && result.indexOf( check ) > -1 :
+ operator === "$=" ? check && result.substr( result.length - check.length ) === check :
+ operator === "~=" ? ( " " + result + " " ).indexOf( check ) > -1 :
+ operator === "|=" ? result === check || result.substr( 0, check.length + 1 ) === check + "-" :
+ false;
+ };
+ },
+
+ "CHILD": function( type, argument, first, last ) {
+
+ if ( type === "nth" ) {
+ return function( elem ) {
+ var node, diff,
+ parent = elem.parentNode;
+
+ if ( first === 1 && last === 0 ) {
+ return true;
+ }
+
+ if ( parent ) {
+ diff = 0;
+ for ( node = parent.firstChild; node; node = node.nextSibling ) {
+ if ( node.nodeType === 1 ) {
+ diff++;
+ if ( elem === node ) {
+ break;
+ }
+ }
+ }
+ }
+
+ // Incorporate the offset (or cast to NaN), then check against cycle size
+ diff -= last;
+ return diff === first || ( diff % first === 0 && diff / first >= 0 );
+ };
+ }
+
+ return function( elem ) {
+ var node = elem;
+
+ switch ( type ) {
+ case "only":
+ case "first":
+ while ( (node = node.previousSibling) ) {
+ if ( node.nodeType === 1 ) {
+ return false;
+ }
+ }
+
+ if ( type === "first" ) {
+ return true;
+ }
+
+ node = elem;
+
+ /* falls through */
+ case "last":
+ while ( (node = node.nextSibling) ) {
+ if ( node.nodeType === 1 ) {
+ return false;
+ }
+ }
+
+ return true;
+ }
+ };
+ },
+
+ "PSEUDO": function( pseudo, argument ) {
+ // pseudo-class names are case-insensitive
+ // http://www.w3.org/TR/selectors/#pseudo-classes
+ // Prioritize by case sensitivity in case custom pseudos are added with uppercase letters
+ // Remember that setFilters inherits from pseudos
+ var args,
+ fn = Expr.pseudos[ pseudo ] || Expr.setFilters[ pseudo.toLowerCase() ] ||
+ Sizzle.error( "unsupported pseudo: " + pseudo );
+
+ // The user may use createPseudo to indicate that
+ // arguments are needed to create the filter function
+ // just as Sizzle does
+ if ( fn[ expando ] ) {
+ return fn( argument );
+ }
+
+ // But maintain support for old signatures
+ if ( fn.length > 1 ) {
+ args = [ pseudo, pseudo, "", argument ];
+ return Expr.setFilters.hasOwnProperty( pseudo.toLowerCase() ) ?
+ markFunction(function( seed, matches ) {
+ var idx,
+ matched = fn( seed, argument ),
+ i = matched.length;
+ while ( i-- ) {
+ idx = indexOf.call( seed, matched[i] );
+ seed[ idx ] = !( matches[ idx ] = matched[i] );
+ }
+ }) :
+ function( elem ) {
+ return fn( elem, 0, args );
+ };
+ }
+
+ return fn;
+ }
+ },
+
+ pseudos: {
+ "not": markFunction(function( selector ) {
+ // Trim the selector passed to compile
+ // to avoid treating leading and trailing
+ // spaces as combinators
+ var input = [],
+ results = [],
+ matcher = compile( selector.replace( rtrim, "$1" ) );
+
+ return matcher[ expando ] ?
+ markFunction(function( seed, matches, context, xml ) {
+ var elem,
+ unmatched = matcher( seed, null, xml, [] ),
+ i = seed.length;
+
+ // Match elements unmatched by `matcher`
+ while ( i-- ) {
+ if ( (elem = unmatched[i]) ) {
+ seed[i] = !(matches[i] = elem);
+ }
+ }
+ }) :
+ function( elem, context, xml ) {
+ input[0] = elem;
+ matcher( input, null, xml, results );
+ return !results.pop();
+ };
+ }),
+
+ "has": markFunction(function( selector ) {
+ return function( elem ) {
+ return Sizzle( selector, elem ).length > 0;
+ };
+ }),
+
+ "contains": markFunction(function( text ) {
+ return function( elem ) {
+ return ( elem.textContent || elem.innerText || getText( elem ) ).indexOf( text ) > -1;
+ };
+ }),
+
+ "enabled": function( elem ) {
+ return elem.disabled === false;
+ },
+
+ "disabled": function( elem ) {
+ return elem.disabled === true;
+ },
+
+ "checked": function( elem ) {
+ // In CSS3, :checked should return both checked and selected elements
+ // http://www.w3.org/TR/2011/REC-css3-selectors-20110929/#checked
+ var nodeName = elem.nodeName.toLowerCase();
+ return (nodeName === "input" && !!elem.checked) || (nodeName === "option" && !!elem.selected);
+ },
+
+ "selected": function( elem ) {
+ // Accessing this property makes selected-by-default
+ // options in Safari work properly
+ if ( elem.parentNode ) {
+ elem.parentNode.selectedIndex;
+ }
+
+ return elem.selected === true;
+ },
+
+ "parent": function( elem ) {
+ return !Expr.pseudos["empty"]( elem );
+ },
+
+ "empty": function( elem ) {
+ // http://www.w3.org/TR/selectors/#empty-pseudo
+ // :empty is only affected by element nodes and content nodes(including text(3), cdata(4)),
+ // not comment, processing instructions, or others
+ // Thanks to Diego Perini for the nodeName shortcut
+ // Greater than "@" means alpha characters (specifically not starting with "#" or "?")
+ var nodeType;
+ elem = elem.firstChild;
+ while ( elem ) {
+ if ( elem.nodeName > "@" || (nodeType = elem.nodeType) === 3 || nodeType === 4 ) {
+ return false;
+ }
+ elem = elem.nextSibling;
+ }
+ return true;
+ },
+
+ "header": function( elem ) {
+ return rheader.test( elem.nodeName );
+ },
+
+ "text": function( elem ) {
+ var type, attr;
+ // IE6 and 7 will map elem.type to 'text' for new HTML5 types (search, etc)
+ // use getAttribute instead to test this case
+ return elem.nodeName.toLowerCase() === "input" &&
+ (type = elem.type) === "text" &&
+ ( (attr = elem.getAttribute("type")) == null || attr.toLowerCase() === type );
+ },
+
+ // Input types
+ "radio": createInputPseudo("radio"),
+ "checkbox": createInputPseudo("checkbox"),
+ "file": createInputPseudo("file"),
+ "password": createInputPseudo("password"),
+ "image": createInputPseudo("image"),
+
+ "submit": createButtonPseudo("submit"),
+ "reset": createButtonPseudo("reset"),
+
+ "button": function( elem ) {
+ var name = elem.nodeName.toLowerCase();
+ return name === "input" && elem.type === "button" || name === "button";
+ },
+
+ "input": function( elem ) {
+ return rinputs.test( elem.nodeName );
+ },
+
+ "focus": function( elem ) {
+ var doc = elem.ownerDocument;
+ return elem === doc.activeElement && (!doc.hasFocus || doc.hasFocus()) && !!(elem.type || elem.href || ~elem.tabIndex);
+ },
+
+ "active": function( elem ) {
+ return elem === elem.ownerDocument.activeElement;
+ },
+
+ // Positional types
+ "first": createPositionalPseudo(function() {
+ return [ 0 ];
+ }),
+
+ "last": createPositionalPseudo(function( matchIndexes, length ) {
+ return [ length - 1 ];
+ }),
+
+ "eq": createPositionalPseudo(function( matchIndexes, length, argument ) {
+ return [ argument < 0 ? argument + length : argument ];
+ }),
+
+ "even": createPositionalPseudo(function( matchIndexes, length ) {
+ for ( var i = 0; i < length; i += 2 ) {
+ matchIndexes.push( i );
+ }
+ return matchIndexes;
+ }),
+
+ "odd": createPositionalPseudo(function( matchIndexes, length ) {
+ for ( var i = 1; i < length; i += 2 ) {
+ matchIndexes.push( i );
+ }
+ return matchIndexes;
+ }),
+
+ "lt": createPositionalPseudo(function( matchIndexes, length, argument ) {
+ for ( var i = argument < 0 ? argument + length : argument; --i >= 0; ) {
+ matchIndexes.push( i );
+ }
+ return matchIndexes;
+ }),
+
+ "gt": createPositionalPseudo(function( matchIndexes, length, argument ) {
+ for ( var i = argument < 0 ? argument + length : argument; ++i < length; ) {
+ matchIndexes.push( i );
+ }
+ return matchIndexes;
+ })
+ }
+};
+
+function siblingCheck( a, b, ret ) {
+ if ( a === b ) {
+ return ret;
+ }
+
+ var cur = a.nextSibling;
+
+ while ( cur ) {
+ if ( cur === b ) {
+ return -1;
+ }
+
+ cur = cur.nextSibling;
+ }
+
+ return 1;
+}
+
+sortOrder = docElem.compareDocumentPosition ?
+ function( a, b ) {
+ if ( a === b ) {
+ hasDuplicate = true;
+ return 0;
+ }
+
+ return ( !a.compareDocumentPosition || !b.compareDocumentPosition ?
+ a.compareDocumentPosition :
+ a.compareDocumentPosition(b) & 4
+ ) ? -1 : 1;
+ } :
+ function( a, b ) {
+ // The nodes are identical, we can exit early
+ if ( a === b ) {
+ hasDuplicate = true;
+ return 0;
+
+ // Fallback to using sourceIndex (in IE) if it's available on both nodes
+ } else if ( a.sourceIndex && b.sourceIndex ) {
+ return a.sourceIndex - b.sourceIndex;
+ }
+
+ var al, bl,
+ ap = [],
+ bp = [],
+ aup = a.parentNode,
+ bup = b.parentNode,
+ cur = aup;
+
+ // If the nodes are siblings (or identical) we can do a quick check
+ if ( aup === bup ) {
+ return siblingCheck( a, b );
+
+ // If no parents were found then the nodes are disconnected
+ } else if ( !aup ) {
+ return -1;
+
+ } else if ( !bup ) {
+ return 1;
+ }
+
+ // Otherwise they're somewhere else in the tree so we need
+ // to build up a full list of the parentNodes for comparison
+ while ( cur ) {
+ ap.unshift( cur );
+ cur = cur.parentNode;
+ }
+
+ cur = bup;
+
+ while ( cur ) {
+ bp.unshift( cur );
+ cur = cur.parentNode;
+ }
+
+ al = ap.length;
+ bl = bp.length;
+
+ // Start walking down the tree looking for a discrepancy
+ for ( var i = 0; i < al && i < bl; i++ ) {
+ if ( ap[i] !== bp[i] ) {
+ return siblingCheck( ap[i], bp[i] );
+ }
+ }
+
+ // We ended someplace up the tree so do a sibling check
+ return i === al ?
+ siblingCheck( a, bp[i], -1 ) :
+ siblingCheck( ap[i], b, 1 );
+ };
+
+// Always assume the presence of duplicates if sort doesn't
+// pass them to our comparison function (as in Google Chrome).
+[0, 0].sort( sortOrder );
+baseHasDuplicate = !hasDuplicate;
+
+// Document sorting and removing duplicates
+Sizzle.uniqueSort = function( results ) {
+ var elem,
+ duplicates = [],
+ i = 1,
+ j = 0;
+
+ hasDuplicate = baseHasDuplicate;
+ results.sort( sortOrder );
+
+ if ( hasDuplicate ) {
+ for ( ; (elem = results[i]); i++ ) {
+ if ( elem === results[ i - 1 ] ) {
+ j = duplicates.push( i );
+ }
+ }
+ while ( j-- ) {
+ results.splice( duplicates[ j ], 1 );
+ }
+ }
+
+ return results;
+};
+
+Sizzle.error = function( msg ) {
+ throw new Error( "Syntax error, unrecognized expression: " + msg );
+};
+
+function tokenize( selector, parseOnly ) {
+ var matched, match, tokens, type,
+ soFar, groups, preFilters,
+ cached = tokenCache[ expando ][ selector + " " ];
+
+ if ( cached ) {
+ return parseOnly ? 0 : cached.slice( 0 );
+ }
+
+ soFar = selector;
+ groups = [];
+ preFilters = Expr.preFilter;
+
+ while ( soFar ) {
+
+ // Comma and first run
+ if ( !matched || (match = rcomma.exec( soFar )) ) {
+ if ( match ) {
+ // Don't consume trailing commas as valid
+ soFar = soFar.slice( match[0].length ) || soFar;
+ }
+ groups.push( tokens = [] );
+ }
+
+ matched = false;
+
+ // Combinators
+ if ( (match = rcombinators.exec( soFar )) ) {
+ tokens.push( matched = new Token( match.shift() ) );
+ soFar = soFar.slice( matched.length );
+
+ // Cast descendant combinators to space
+ matched.type = match[0].replace( rtrim, " " );
+ }
+
+ // Filters
+ for ( type in Expr.filter ) {
+ if ( (match = matchExpr[ type ].exec( soFar )) && (!preFilters[ type ] ||
+ (match = preFilters[ type ]( match ))) ) {
+
+ tokens.push( matched = new Token( match.shift() ) );
+ soFar = soFar.slice( matched.length );
+ matched.type = type;
+ matched.matches = match;
+ }
+ }
+
+ if ( !matched ) {
+ break;
+ }
+ }
+
+ // Return the length of the invalid excess
+ // if we're just parsing
+ // Otherwise, throw an error or return tokens
+ return parseOnly ?
+ soFar.length :
+ soFar ?
+ Sizzle.error( selector ) :
+ // Cache the tokens
+ tokenCache( selector, groups ).slice( 0 );
+}
+
+function addCombinator( matcher, combinator, base ) {
+ var dir = combinator.dir,
+ checkNonElements = base && combinator.dir === "parentNode",
+ doneName = done++;
+
+ return combinator.first ?
+ // Check against closest ancestor/preceding element
+ function( elem, context, xml ) {
+ while ( (elem = elem[ dir ]) ) {
+ if ( checkNonElements || elem.nodeType === 1 ) {
+ return matcher( elem, context, xml );
+ }
+ }
+ } :
+
+ // Check against all ancestor/preceding elements
+ function( elem, context, xml ) {
+ // We can't set arbitrary data on XML nodes, so they don't benefit from dir caching
+ if ( !xml ) {
+ var cache,
+ dirkey = dirruns + " " + doneName + " ",
+ cachedkey = dirkey + cachedruns;
+ while ( (elem = elem[ dir ]) ) {
+ if ( checkNonElements || elem.nodeType === 1 ) {
+ if ( (cache = elem[ expando ]) === cachedkey ) {
+ return elem.sizset;
+ } else if ( typeof cache === "string" && cache.indexOf(dirkey) === 0 ) {
+ if ( elem.sizset ) {
+ return elem;
+ }
+ } else {
+ elem[ expando ] = cachedkey;
+ if ( matcher( elem, context, xml ) ) {
+ elem.sizset = true;
+ return elem;
+ }
+ elem.sizset = false;
+ }
+ }
+ }
+ } else {
+ while ( (elem = elem[ dir ]) ) {
+ if ( checkNonElements || elem.nodeType === 1 ) {
+ if ( matcher( elem, context, xml ) ) {
+ return elem;
+ }
+ }
+ }
+ }
+ };
+}
+
+function elementMatcher( matchers ) {
+ return matchers.length > 1 ?
+ function( elem, context, xml ) {
+ var i = matchers.length;
+ while ( i-- ) {
+ if ( !matchers[i]( elem, context, xml ) ) {
+ return false;
+ }
+ }
+ return true;
+ } :
+ matchers[0];
+}
+
+function condense( unmatched, map, filter, context, xml ) {
+ var elem,
+ newUnmatched = [],
+ i = 0,
+ len = unmatched.length,
+ mapped = map != null;
+
+ for ( ; i < len; i++ ) {
+ if ( (elem = unmatched[i]) ) {
+ if ( !filter || filter( elem, context, xml ) ) {
+ newUnmatched.push( elem );
+ if ( mapped ) {
+ map.push( i );
+ }
+ }
+ }
+ }
+
+ return newUnmatched;
+}
+
+function setMatcher( preFilter, selector, matcher, postFilter, postFinder, postSelector ) {
+ if ( postFilter && !postFilter[ expando ] ) {
+ postFilter = setMatcher( postFilter );
+ }
+ if ( postFinder && !postFinder[ expando ] ) {
+ postFinder = setMatcher( postFinder, postSelector );
+ }
+ return markFunction(function( seed, results, context, xml ) {
+ var temp, i, elem,
+ preMap = [],
+ postMap = [],
+ preexisting = results.length,
+
+ // Get initial elements from seed or context
+ elems = seed || multipleContexts( selector || "*", context.nodeType ? [ context ] : context, [] ),
+
+ // Prefilter to get matcher input, preserving a map for seed-results synchronization
+ matcherIn = preFilter && ( seed || !selector ) ?
+ condense( elems, preMap, preFilter, context, xml ) :
+ elems,
+
+ matcherOut = matcher ?
+ // If we have a postFinder, or filtered seed, or non-seed postFilter or preexisting results,
+ postFinder || ( seed ? preFilter : preexisting || postFilter ) ?
+
+ // ...intermediate processing is necessary
+ [] :
+
+ // ...otherwise use results directly
+ results :
+ matcherIn;
+
+ // Find primary matches
+ if ( matcher ) {
+ matcher( matcherIn, matcherOut, context, xml );
+ }
+
+ // Apply postFilter
+ if ( postFilter ) {
+ temp = condense( matcherOut, postMap );
+ postFilter( temp, [], context, xml );
+
+ // Un-match failing elements by moving them back to matcherIn
+ i = temp.length;
+ while ( i-- ) {
+ if ( (elem = temp[i]) ) {
+ matcherOut[ postMap[i] ] = !(matcherIn[ postMap[i] ] = elem);
+ }
+ }
+ }
+
+ if ( seed ) {
+ if ( postFinder || preFilter ) {
+ if ( postFinder ) {
+ // Get the final matcherOut by condensing this intermediate into postFinder contexts
+ temp = [];
+ i = matcherOut.length;
+ while ( i-- ) {
+ if ( (elem = matcherOut[i]) ) {
+ // Restore matcherIn since elem is not yet a final match
+ temp.push( (matcherIn[i] = elem) );
+ }
+ }
+ postFinder( null, (matcherOut = []), temp, xml );
+ }
+
+ // Move matched elements from seed to results to keep them synchronized
+ i = matcherOut.length;
+ while ( i-- ) {
+ if ( (elem = matcherOut[i]) &&
+ (temp = postFinder ? indexOf.call( seed, elem ) : preMap[i]) > -1 ) {
+
+ seed[temp] = !(results[temp] = elem);
+ }
+ }
+ }
+
+ // Add elements to results, through postFinder if defined
+ } else {
+ matcherOut = condense(
+ matcherOut === results ?
+ matcherOut.splice( preexisting, matcherOut.length ) :
+ matcherOut
+ );
+ if ( postFinder ) {
+ postFinder( null, results, matcherOut, xml );
+ } else {
+ push.apply( results, matcherOut );
+ }
+ }
+ });
+}
+
+function matcherFromTokens( tokens ) {
+ var checkContext, matcher, j,
+ len = tokens.length,
+ leadingRelative = Expr.relative[ tokens[0].type ],
+ implicitRelative = leadingRelative || Expr.relative[" "],
+ i = leadingRelative ? 1 : 0,
+
+ // The foundational matcher ensures that elements are reachable from top-level context(s)
+ matchContext = addCombinator( function( elem ) {
+ return elem === checkContext;
+ }, implicitRelative, true ),
+ matchAnyContext = addCombinator( function( elem ) {
+ return indexOf.call( checkContext, elem ) > -1;
+ }, implicitRelative, true ),
+ matchers = [ function( elem, context, xml ) {
+ return ( !leadingRelative && ( xml || context !== outermostContext ) ) || (
+ (checkContext = context).nodeType ?
+ matchContext( elem, context, xml ) :
+ matchAnyContext( elem, context, xml ) );
+ } ];
+
+ for ( ; i < len; i++ ) {
+ if ( (matcher = Expr.relative[ tokens[i].type ]) ) {
+ matchers = [ addCombinator( elementMatcher( matchers ), matcher ) ];
+ } else {
+ matcher = Expr.filter[ tokens[i].type ].apply( null, tokens[i].matches );
+
+ // Return special upon seeing a positional matcher
+ if ( matcher[ expando ] ) {
+ // Find the next relative operator (if any) for proper handling
+ j = ++i;
+ for ( ; j < len; j++ ) {
+ if ( Expr.relative[ tokens[j].type ] ) {
+ break;
+ }
+ }
+ return setMatcher(
+ i > 1 && elementMatcher( matchers ),
+ i > 1 && tokens.slice( 0, i - 1 ).join("").replace( rtrim, "$1" ),
+ matcher,
+ i < j && matcherFromTokens( tokens.slice( i, j ) ),
+ j < len && matcherFromTokens( (tokens = tokens.slice( j )) ),
+ j < len && tokens.join("")
+ );
+ }
+ matchers.push( matcher );
+ }
+ }
+
+ return elementMatcher( matchers );
+}
+
+function matcherFromGroupMatchers( elementMatchers, setMatchers ) {
+ var bySet = setMatchers.length > 0,
+ byElement = elementMatchers.length > 0,
+ superMatcher = function( seed, context, xml, results, expandContext ) {
+ var elem, j, matcher,
+ setMatched = [],
+ matchedCount = 0,
+ i = "0",
+ unmatched = seed && [],
+ outermost = expandContext != null,
+ contextBackup = outermostContext,
+ // We must always have either seed elements or context
+ elems = seed || byElement && Expr.find["TAG"]( "*", expandContext && context.parentNode || context ),
+ // Nested matchers should use non-integer dirruns
+ dirrunsUnique = (dirruns += contextBackup == null ? 1 : Math.E);
+
+ if ( outermost ) {
+ outermostContext = context !== document && context;
+ cachedruns = superMatcher.el;
+ }
+
+ // Add elements passing elementMatchers directly to results
+ for ( ; (elem = elems[i]) != null; i++ ) {
+ if ( byElement && elem ) {
+ for ( j = 0; (matcher = elementMatchers[j]); j++ ) {
+ if ( matcher( elem, context, xml ) ) {
+ results.push( elem );
+ break;
+ }
+ }
+ if ( outermost ) {
+ dirruns = dirrunsUnique;
+ cachedruns = ++superMatcher.el;
+ }
+ }
+
+ // Track unmatched elements for set filters
+ if ( bySet ) {
+ // They will have gone through all possible matchers
+ if ( (elem = !matcher && elem) ) {
+ matchedCount--;
+ }
+
+ // Lengthen the array for every element, matched or not
+ if ( seed ) {
+ unmatched.push( elem );
+ }
+ }
+ }
+
+ // Apply set filters to unmatched elements
+ matchedCount += i;
+ if ( bySet && i !== matchedCount ) {
+ for ( j = 0; (matcher = setMatchers[j]); j++ ) {
+ matcher( unmatched, setMatched, context, xml );
+ }
+
+ if ( seed ) {
+ // Reintegrate element matches to eliminate the need for sorting
+ if ( matchedCount > 0 ) {
+ while ( i-- ) {
+ if ( !(unmatched[i] || setMatched[i]) ) {
+ setMatched[i] = pop.call( results );
+ }
+ }
+ }
+
+ // Discard index placeholder values to get only actual matches
+ setMatched = condense( setMatched );
+ }
+
+ // Add matches to results
+ push.apply( results, setMatched );
+
+ // Seedless set matches succeeding multiple successful matchers stipulate sorting
+ if ( outermost && !seed && setMatched.length > 0 &&
+ ( matchedCount + setMatchers.length ) > 1 ) {
+
+ Sizzle.uniqueSort( results );
+ }
+ }
+
+ // Override manipulation of globals by nested matchers
+ if ( outermost ) {
+ dirruns = dirrunsUnique;
+ outermostContext = contextBackup;
+ }
+
+ return unmatched;
+ };
+
+ superMatcher.el = 0;
+ return bySet ?
+ markFunction( superMatcher ) :
+ superMatcher;
+}
+
+compile = Sizzle.compile = function( selector, group /* Internal Use Only */ ) {
+ var i,
+ setMatchers = [],
+ elementMatchers = [],
+ cached = compilerCache[ expando ][ selector + " " ];
+
+ if ( !cached ) {
+ // Generate a function of recursive functions that can be used to check each element
+ if ( !group ) {
+ group = tokenize( selector );
+ }
+ i = group.length;
+ while ( i-- ) {
+ cached = matcherFromTokens( group[i] );
+ if ( cached[ expando ] ) {
+ setMatchers.push( cached );
+ } else {
+ elementMatchers.push( cached );
+ }
+ }
+
+ // Cache the compiled function
+ cached = compilerCache( selector, matcherFromGroupMatchers( elementMatchers, setMatchers ) );
+ }
+ return cached;
+};
+
+function multipleContexts( selector, contexts, results ) {
+ var i = 0,
+ len = contexts.length;
+ for ( ; i < len; i++ ) {
+ Sizzle( selector, contexts[i], results );
+ }
+ return results;
+}
+
+function select( selector, context, results, seed, xml ) {
+ var i, tokens, token, type, find,
+ match = tokenize( selector ),
+ j = match.length;
+
+ if ( !seed ) {
+ // Try to minimize operations if there is only one group
+ if ( match.length === 1 ) {
+
+ // Take a shortcut and set the context if the root selector is an ID
+ tokens = match[0] = match[0].slice( 0 );
+ if ( tokens.length > 2 && (token = tokens[0]).type === "ID" &&
+ context.nodeType === 9 && !xml &&
+ Expr.relative[ tokens[1].type ] ) {
+
+ context = Expr.find["ID"]( token.matches[0].replace( rbackslash, "" ), context, xml )[0];
+ if ( !context ) {
+ return results;
+ }
+
+ selector = selector.slice( tokens.shift().length );
+ }
+
+ // Fetch a seed set for right-to-left matching
+ for ( i = matchExpr["POS"].test( selector ) ? -1 : tokens.length - 1; i >= 0; i-- ) {
+ token = tokens[i];
+
+ // Abort if we hit a combinator
+ if ( Expr.relative[ (type = token.type) ] ) {
+ break;
+ }
+ if ( (find = Expr.find[ type ]) ) {
+ // Search, expanding context for leading sibling combinators
+ if ( (seed = find(
+ token.matches[0].replace( rbackslash, "" ),
+ rsibling.test( tokens[0].type ) && context.parentNode || context,
+ xml
+ )) ) {
+
+ // If seed is empty or no tokens remain, we can return early
+ tokens.splice( i, 1 );
+ selector = seed.length && tokens.join("");
+ if ( !selector ) {
+ push.apply( results, slice.call( seed, 0 ) );
+ return results;
+ }
+
+ break;
+ }
+ }
+ }
+ }
+ }
+
+ // Compile and execute a filtering function
+ // Provide `match` to avoid retokenization if we modified the selector above
+ compile( selector, match )(
+ seed,
+ context,
+ xml,
+ results,
+ rsibling.test( selector )
+ );
+ return results;
+}
+
+if ( document.querySelectorAll ) {
+ (function() {
+ var disconnectedMatch,
+ oldSelect = select,
+ rescape = /'|\\/g,
+ rattributeQuotes = /\=[\x20\t\r\n\f]*([^'"\]]*)[\x20\t\r\n\f]*\]/g,
+
+ // qSa(:focus) reports false when true (Chrome 21), no need to also add to buggyMatches since matches checks buggyQSA
+ // A support test would require too much code (would include document ready)
+ rbuggyQSA = [ ":focus" ],
+
+ // matchesSelector(:active) reports false when true (IE9/Opera 11.5)
+ // A support test would require too much code (would include document ready)
+ // just skip matchesSelector for :active
+ rbuggyMatches = [ ":active" ],
+ matches = docElem.matchesSelector ||
+ docElem.mozMatchesSelector ||
+ docElem.webkitMatchesSelector ||
+ docElem.oMatchesSelector ||
+ docElem.msMatchesSelector;
+
+ // Build QSA regex
+ // Regex strategy adopted from Diego Perini
+ assert(function( div ) {
+ // Select is set to empty string on purpose
+ // This is to test IE's treatment of not explictly
+ // setting a boolean content attribute,
+ // since its presence should be enough
+ // http://bugs.jquery.com/ticket/12359
+ div.innerHTML = " ";
+
+ // IE8 - Some boolean attributes are not treated correctly
+ if ( !div.querySelectorAll("[selected]").length ) {
+ rbuggyQSA.push( "\\[" + whitespace + "*(?:checked|disabled|ismap|multiple|readonly|selected|value)" );
+ }
+
+ // Webkit/Opera - :checked should return selected option elements
+ // http://www.w3.org/TR/2011/REC-css3-selectors-20110929/#checked
+ // IE8 throws error here (do not put tests after this one)
+ if ( !div.querySelectorAll(":checked").length ) {
+ rbuggyQSA.push(":checked");
+ }
+ });
+
+ assert(function( div ) {
+
+ // Opera 10-12/IE9 - ^= $= *= and empty values
+ // Should not select anything
+ div.innerHTML = "
";
+ if ( div.querySelectorAll("[test^='']").length ) {
+ rbuggyQSA.push( "[*^$]=" + whitespace + "*(?:\"\"|'')" );
+ }
+
+ // FF 3.5 - :enabled/:disabled and hidden elements (hidden elements are still enabled)
+ // IE8 throws error here (do not put tests after this one)
+ div.innerHTML = " ";
+ if ( !div.querySelectorAll(":enabled").length ) {
+ rbuggyQSA.push(":enabled", ":disabled");
+ }
+ });
+
+ // rbuggyQSA always contains :focus, so no need for a length check
+ rbuggyQSA = /* rbuggyQSA.length && */ new RegExp( rbuggyQSA.join("|") );
+
+ select = function( selector, context, results, seed, xml ) {
+ // Only use querySelectorAll when not filtering,
+ // when this is not xml,
+ // and when no QSA bugs apply
+ if ( !seed && !xml && !rbuggyQSA.test( selector ) ) {
+ var groups, i,
+ old = true,
+ nid = expando,
+ newContext = context,
+ newSelector = context.nodeType === 9 && selector;
+
+ // qSA works strangely on Element-rooted queries
+ // We can work around this by specifying an extra ID on the root
+ // and working up from there (Thanks to Andrew Dupont for the technique)
+ // IE 8 doesn't work on object elements
+ if ( context.nodeType === 1 && context.nodeName.toLowerCase() !== "object" ) {
+ groups = tokenize( selector );
+
+ if ( (old = context.getAttribute("id")) ) {
+ nid = old.replace( rescape, "\\$&" );
+ } else {
+ context.setAttribute( "id", nid );
+ }
+ nid = "[id='" + nid + "'] ";
+
+ i = groups.length;
+ while ( i-- ) {
+ groups[i] = nid + groups[i].join("");
+ }
+ newContext = rsibling.test( selector ) && context.parentNode || context;
+ newSelector = groups.join(",");
+ }
+
+ if ( newSelector ) {
+ try {
+ push.apply( results, slice.call( newContext.querySelectorAll(
+ newSelector
+ ), 0 ) );
+ return results;
+ } catch(qsaError) {
+ } finally {
+ if ( !old ) {
+ context.removeAttribute("id");
+ }
+ }
+ }
+ }
+
+ return oldSelect( selector, context, results, seed, xml );
+ };
+
+ if ( matches ) {
+ assert(function( div ) {
+ // Check to see if it's possible to do matchesSelector
+ // on a disconnected node (IE 9)
+ disconnectedMatch = matches.call( div, "div" );
+
+ // This should fail with an exception
+ // Gecko does not error, returns false instead
+ try {
+ matches.call( div, "[test!='']:sizzle" );
+ rbuggyMatches.push( "!=", pseudos );
+ } catch ( e ) {}
+ });
+
+ // rbuggyMatches always contains :active and :focus, so no need for a length check
+ rbuggyMatches = /* rbuggyMatches.length && */ new RegExp( rbuggyMatches.join("|") );
+
+ Sizzle.matchesSelector = function( elem, expr ) {
+ // Make sure that attribute selectors are quoted
+ expr = expr.replace( rattributeQuotes, "='$1']" );
+
+ // rbuggyMatches always contains :active, so no need for an existence check
+ if ( !isXML( elem ) && !rbuggyMatches.test( expr ) && !rbuggyQSA.test( expr ) ) {
+ try {
+ var ret = matches.call( elem, expr );
+
+ // IE 9's matchesSelector returns false on disconnected nodes
+ if ( ret || disconnectedMatch ||
+ // As well, disconnected nodes are said to be in a document
+ // fragment in IE 9
+ elem.document && elem.document.nodeType !== 11 ) {
+ return ret;
+ }
+ } catch(e) {}
+ }
+
+ return Sizzle( expr, null, null, [ elem ] ).length > 0;
+ };
+ }
+ })();
+}
+
+// Deprecated
+Expr.pseudos["nth"] = Expr.pseudos["eq"];
+
+// Back-compat
+function setFilters() {}
+Expr.filters = setFilters.prototype = Expr.pseudos;
+Expr.setFilters = new setFilters();
+
+// Override sizzle attribute retrieval
+Sizzle.attr = jQuery.attr;
+jQuery.find = Sizzle;
+jQuery.expr = Sizzle.selectors;
+jQuery.expr[":"] = jQuery.expr.pseudos;
+jQuery.unique = Sizzle.uniqueSort;
+jQuery.text = Sizzle.getText;
+jQuery.isXMLDoc = Sizzle.isXML;
+jQuery.contains = Sizzle.contains;
+
+
+})( window );
+var runtil = /Until$/,
+ rparentsprev = /^(?:parents|prev(?:Until|All))/,
+ isSimple = /^.[^:#\[\.,]*$/,
+ rneedsContext = jQuery.expr.match.needsContext,
+ // methods guaranteed to produce a unique set when starting from a unique set
+ guaranteedUnique = {
+ children: true,
+ contents: true,
+ next: true,
+ prev: true
+ };
+
+jQuery.fn.extend({
+ find: function( selector ) {
+ var i, l, length, n, r, ret,
+ self = this;
+
+ if ( typeof selector !== "string" ) {
+ return jQuery( selector ).filter(function() {
+ for ( i = 0, l = self.length; i < l; i++ ) {
+ if ( jQuery.contains( self[ i ], this ) ) {
+ return true;
+ }
+ }
+ });
+ }
+
+ ret = this.pushStack( "", "find", selector );
+
+ for ( i = 0, l = this.length; i < l; i++ ) {
+ length = ret.length;
+ jQuery.find( selector, this[i], ret );
+
+ if ( i > 0 ) {
+ // Make sure that the results are unique
+ for ( n = length; n < ret.length; n++ ) {
+ for ( r = 0; r < length; r++ ) {
+ if ( ret[r] === ret[n] ) {
+ ret.splice(n--, 1);
+ break;
+ }
+ }
+ }
+ }
+ }
+
+ return ret;
+ },
+
+ has: function( target ) {
+ var i,
+ targets = jQuery( target, this ),
+ len = targets.length;
+
+ return this.filter(function() {
+ for ( i = 0; i < len; i++ ) {
+ if ( jQuery.contains( this, targets[i] ) ) {
+ return true;
+ }
+ }
+ });
+ },
+
+ not: function( selector ) {
+ return this.pushStack( winnow(this, selector, false), "not", selector);
+ },
+
+ filter: function( selector ) {
+ return this.pushStack( winnow(this, selector, true), "filter", selector );
+ },
+
+ is: function( selector ) {
+ return !!selector && (
+ typeof selector === "string" ?
+ // If this is a positional/relative selector, check membership in the returned set
+ // so $("p:first").is("p:last") won't return true for a doc with two "p".
+ rneedsContext.test( selector ) ?
+ jQuery( selector, this.context ).index( this[0] ) >= 0 :
+ jQuery.filter( selector, this ).length > 0 :
+ this.filter( selector ).length > 0 );
+ },
+
+ closest: function( selectors, context ) {
+ var cur,
+ i = 0,
+ l = this.length,
+ ret = [],
+ pos = rneedsContext.test( selectors ) || typeof selectors !== "string" ?
+ jQuery( selectors, context || this.context ) :
+ 0;
+
+ for ( ; i < l; i++ ) {
+ cur = this[i];
+
+ while ( cur && cur.ownerDocument && cur !== context && cur.nodeType !== 11 ) {
+ if ( pos ? pos.index(cur) > -1 : jQuery.find.matchesSelector(cur, selectors) ) {
+ ret.push( cur );
+ break;
+ }
+ cur = cur.parentNode;
+ }
+ }
+
+ ret = ret.length > 1 ? jQuery.unique( ret ) : ret;
+
+ return this.pushStack( ret, "closest", selectors );
+ },
+
+ // Determine the position of an element within
+ // the matched set of elements
+ index: function( elem ) {
+
+ // No argument, return index in parent
+ if ( !elem ) {
+ return ( this[0] && this[0].parentNode ) ? this.prevAll().length : -1;
+ }
+
+ // index in selector
+ if ( typeof elem === "string" ) {
+ return jQuery.inArray( this[0], jQuery( elem ) );
+ }
+
+ // Locate the position of the desired element
+ return jQuery.inArray(
+ // If it receives a jQuery object, the first element is used
+ elem.jquery ? elem[0] : elem, this );
+ },
+
+ add: function( selector, context ) {
+ var set = typeof selector === "string" ?
+ jQuery( selector, context ) :
+ jQuery.makeArray( selector && selector.nodeType ? [ selector ] : selector ),
+ all = jQuery.merge( this.get(), set );
+
+ return this.pushStack( isDisconnected( set[0] ) || isDisconnected( all[0] ) ?
+ all :
+ jQuery.unique( all ) );
+ },
+
+ addBack: function( selector ) {
+ return this.add( selector == null ?
+ this.prevObject : this.prevObject.filter(selector)
+ );
+ }
+});
+
+jQuery.fn.andSelf = jQuery.fn.addBack;
+
+// A painfully simple check to see if an element is disconnected
+// from a document (should be improved, where feasible).
+function isDisconnected( node ) {
+ return !node || !node.parentNode || node.parentNode.nodeType === 11;
+}
+
+function sibling( cur, dir ) {
+ do {
+ cur = cur[ dir ];
+ } while ( cur && cur.nodeType !== 1 );
+
+ return cur;
+}
+
+jQuery.each({
+ parent: function( elem ) {
+ var parent = elem.parentNode;
+ return parent && parent.nodeType !== 11 ? parent : null;
+ },
+ parents: function( elem ) {
+ return jQuery.dir( elem, "parentNode" );
+ },
+ parentsUntil: function( elem, i, until ) {
+ return jQuery.dir( elem, "parentNode", until );
+ },
+ next: function( elem ) {
+ return sibling( elem, "nextSibling" );
+ },
+ prev: function( elem ) {
+ return sibling( elem, "previousSibling" );
+ },
+ nextAll: function( elem ) {
+ return jQuery.dir( elem, "nextSibling" );
+ },
+ prevAll: function( elem ) {
+ return jQuery.dir( elem, "previousSibling" );
+ },
+ nextUntil: function( elem, i, until ) {
+ return jQuery.dir( elem, "nextSibling", until );
+ },
+ prevUntil: function( elem, i, until ) {
+ return jQuery.dir( elem, "previousSibling", until );
+ },
+ siblings: function( elem ) {
+ return jQuery.sibling( ( elem.parentNode || {} ).firstChild, elem );
+ },
+ children: function( elem ) {
+ return jQuery.sibling( elem.firstChild );
+ },
+ contents: function( elem ) {
+ return jQuery.nodeName( elem, "iframe" ) ?
+ elem.contentDocument || elem.contentWindow.document :
+ jQuery.merge( [], elem.childNodes );
+ }
+}, function( name, fn ) {
+ jQuery.fn[ name ] = function( until, selector ) {
+ var ret = jQuery.map( this, fn, until );
+
+ if ( !runtil.test( name ) ) {
+ selector = until;
+ }
+
+ if ( selector && typeof selector === "string" ) {
+ ret = jQuery.filter( selector, ret );
+ }
+
+ ret = this.length > 1 && !guaranteedUnique[ name ] ? jQuery.unique( ret ) : ret;
+
+ if ( this.length > 1 && rparentsprev.test( name ) ) {
+ ret = ret.reverse();
+ }
+
+ return this.pushStack( ret, name, core_slice.call( arguments ).join(",") );
+ };
+});
+
+jQuery.extend({
+ filter: function( expr, elems, not ) {
+ if ( not ) {
+ expr = ":not(" + expr + ")";
+ }
+
+ return elems.length === 1 ?
+ jQuery.find.matchesSelector(elems[0], expr) ? [ elems[0] ] : [] :
+ jQuery.find.matches(expr, elems);
+ },
+
+ dir: function( elem, dir, until ) {
+ var matched = [],
+ cur = elem[ dir ];
+
+ while ( cur && cur.nodeType !== 9 && (until === undefined || cur.nodeType !== 1 || !jQuery( cur ).is( until )) ) {
+ if ( cur.nodeType === 1 ) {
+ matched.push( cur );
+ }
+ cur = cur[dir];
+ }
+ return matched;
+ },
+
+ sibling: function( n, elem ) {
+ var r = [];
+
+ for ( ; n; n = n.nextSibling ) {
+ if ( n.nodeType === 1 && n !== elem ) {
+ r.push( n );
+ }
+ }
+
+ return r;
+ }
+});
+
+// Implement the identical functionality for filter and not
+function winnow( elements, qualifier, keep ) {
+
+ // Can't pass null or undefined to indexOf in Firefox 4
+ // Set to 0 to skip string check
+ qualifier = qualifier || 0;
+
+ if ( jQuery.isFunction( qualifier ) ) {
+ return jQuery.grep(elements, function( elem, i ) {
+ var retVal = !!qualifier.call( elem, i, elem );
+ return retVal === keep;
+ });
+
+ } else if ( qualifier.nodeType ) {
+ return jQuery.grep(elements, function( elem, i ) {
+ return ( elem === qualifier ) === keep;
+ });
+
+ } else if ( typeof qualifier === "string" ) {
+ var filtered = jQuery.grep(elements, function( elem ) {
+ return elem.nodeType === 1;
+ });
+
+ if ( isSimple.test( qualifier ) ) {
+ return jQuery.filter(qualifier, filtered, !keep);
+ } else {
+ qualifier = jQuery.filter( qualifier, filtered );
+ }
+ }
+
+ return jQuery.grep(elements, function( elem, i ) {
+ return ( jQuery.inArray( elem, qualifier ) >= 0 ) === keep;
+ });
+}
+function createSafeFragment( document ) {
+ var list = nodeNames.split( "|" ),
+ safeFrag = document.createDocumentFragment();
+
+ if ( safeFrag.createElement ) {
+ while ( list.length ) {
+ safeFrag.createElement(
+ list.pop()
+ );
+ }
+ }
+ return safeFrag;
+}
+
+var nodeNames = "abbr|article|aside|audio|bdi|canvas|data|datalist|details|figcaption|figure|footer|" +
+ "header|hgroup|mark|meter|nav|output|progress|section|summary|time|video",
+ rinlinejQuery = / jQuery\d+="(?:null|\d+)"/g,
+ rleadingWhitespace = /^\s+/,
+ rxhtmlTag = /<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:]+)[^>]*)\/>/gi,
+ rtagName = /<([\w:]+)/,
+ rtbody = / ]", "i"),
+ rcheckableType = /^(?:checkbox|radio)$/,
+ // checked="checked" or checked
+ rchecked = /checked\s*(?:[^=]|=\s*.checked.)/i,
+ rscriptType = /\/(java|ecma)script/i,
+ rcleanScript = /^\s*\s*$/g,
+ wrapMap = {
+ option: [ 1, "", " " ],
+ legend: [ 1, "", " " ],
+ thead: [ 1, "" ],
+ tr: [ 2, "" ],
+ td: [ 3, "" ],
+ col: [ 2, "" ],
+ area: [ 1, "", " " ],
+ _default: [ 0, "", "" ]
+ },
+ safeFragment = createSafeFragment( document ),
+ fragmentDiv = safeFragment.appendChild( document.createElement("div") );
+
+wrapMap.optgroup = wrapMap.option;
+wrapMap.tbody = wrapMap.tfoot = wrapMap.colgroup = wrapMap.caption = wrapMap.thead;
+wrapMap.th = wrapMap.td;
+
+// IE6-8 can't serialize link, script, style, or any html5 (NoScope) tags,
+// unless wrapped in a div with non-breaking characters in front of it.
+if ( !jQuery.support.htmlSerialize ) {
+ wrapMap._default = [ 1, "X", "
" ];
+}
+
+jQuery.fn.extend({
+ text: function( value ) {
+ return jQuery.access( this, function( value ) {
+ return value === undefined ?
+ jQuery.text( this ) :
+ this.empty().append( ( this[0] && this[0].ownerDocument || document ).createTextNode( value ) );
+ }, null, value, arguments.length );
+ },
+
+ wrapAll: function( html ) {
+ if ( jQuery.isFunction( html ) ) {
+ return this.each(function(i) {
+ jQuery(this).wrapAll( html.call(this, i) );
+ });
+ }
+
+ if ( this[0] ) {
+ // The elements to wrap the target around
+ var wrap = jQuery( html, this[0].ownerDocument ).eq(0).clone(true);
+
+ if ( this[0].parentNode ) {
+ wrap.insertBefore( this[0] );
+ }
+
+ wrap.map(function() {
+ var elem = this;
+
+ while ( elem.firstChild && elem.firstChild.nodeType === 1 ) {
+ elem = elem.firstChild;
+ }
+
+ return elem;
+ }).append( this );
+ }
+
+ return this;
+ },
+
+ wrapInner: function( html ) {
+ if ( jQuery.isFunction( html ) ) {
+ return this.each(function(i) {
+ jQuery(this).wrapInner( html.call(this, i) );
+ });
+ }
+
+ return this.each(function() {
+ var self = jQuery( this ),
+ contents = self.contents();
+
+ if ( contents.length ) {
+ contents.wrapAll( html );
+
+ } else {
+ self.append( html );
+ }
+ });
+ },
+
+ wrap: function( html ) {
+ var isFunction = jQuery.isFunction( html );
+
+ return this.each(function(i) {
+ jQuery( this ).wrapAll( isFunction ? html.call(this, i) : html );
+ });
+ },
+
+ unwrap: function() {
+ return this.parent().each(function() {
+ if ( !jQuery.nodeName( this, "body" ) ) {
+ jQuery( this ).replaceWith( this.childNodes );
+ }
+ }).end();
+ },
+
+ append: function() {
+ return this.domManip(arguments, true, function( elem ) {
+ if ( this.nodeType === 1 || this.nodeType === 11 ) {
+ this.appendChild( elem );
+ }
+ });
+ },
+
+ prepend: function() {
+ return this.domManip(arguments, true, function( elem ) {
+ if ( this.nodeType === 1 || this.nodeType === 11 ) {
+ this.insertBefore( elem, this.firstChild );
+ }
+ });
+ },
+
+ before: function() {
+ if ( !isDisconnected( this[0] ) ) {
+ return this.domManip(arguments, false, function( elem ) {
+ this.parentNode.insertBefore( elem, this );
+ });
+ }
+
+ if ( arguments.length ) {
+ var set = jQuery.clean( arguments );
+ return this.pushStack( jQuery.merge( set, this ), "before", this.selector );
+ }
+ },
+
+ after: function() {
+ if ( !isDisconnected( this[0] ) ) {
+ return this.domManip(arguments, false, function( elem ) {
+ this.parentNode.insertBefore( elem, this.nextSibling );
+ });
+ }
+
+ if ( arguments.length ) {
+ var set = jQuery.clean( arguments );
+ return this.pushStack( jQuery.merge( this, set ), "after", this.selector );
+ }
+ },
+
+ // keepData is for internal use only--do not document
+ remove: function( selector, keepData ) {
+ var elem,
+ i = 0;
+
+ for ( ; (elem = this[i]) != null; i++ ) {
+ if ( !selector || jQuery.filter( selector, [ elem ] ).length ) {
+ if ( !keepData && elem.nodeType === 1 ) {
+ jQuery.cleanData( elem.getElementsByTagName("*") );
+ jQuery.cleanData( [ elem ] );
+ }
+
+ if ( elem.parentNode ) {
+ elem.parentNode.removeChild( elem );
+ }
+ }
+ }
+
+ return this;
+ },
+
+ empty: function() {
+ var elem,
+ i = 0;
+
+ for ( ; (elem = this[i]) != null; i++ ) {
+ // Remove element nodes and prevent memory leaks
+ if ( elem.nodeType === 1 ) {
+ jQuery.cleanData( elem.getElementsByTagName("*") );
+ }
+
+ // Remove any remaining nodes
+ while ( elem.firstChild ) {
+ elem.removeChild( elem.firstChild );
+ }
+ }
+
+ return this;
+ },
+
+ clone: function( dataAndEvents, deepDataAndEvents ) {
+ dataAndEvents = dataAndEvents == null ? false : dataAndEvents;
+ deepDataAndEvents = deepDataAndEvents == null ? dataAndEvents : deepDataAndEvents;
+
+ return this.map( function () {
+ return jQuery.clone( this, dataAndEvents, deepDataAndEvents );
+ });
+ },
+
+ html: function( value ) {
+ return jQuery.access( this, function( value ) {
+ var elem = this[0] || {},
+ i = 0,
+ l = this.length;
+
+ if ( value === undefined ) {
+ return elem.nodeType === 1 ?
+ elem.innerHTML.replace( rinlinejQuery, "" ) :
+ undefined;
+ }
+
+ // See if we can take a shortcut and just use innerHTML
+ if ( typeof value === "string" && !rnoInnerhtml.test( value ) &&
+ ( jQuery.support.htmlSerialize || !rnoshimcache.test( value ) ) &&
+ ( jQuery.support.leadingWhitespace || !rleadingWhitespace.test( value ) ) &&
+ !wrapMap[ ( rtagName.exec( value ) || ["", ""] )[1].toLowerCase() ] ) {
+
+ value = value.replace( rxhtmlTag, "<$1>$2>" );
+
+ try {
+ for (; i < l; i++ ) {
+ // Remove element nodes and prevent memory leaks
+ elem = this[i] || {};
+ if ( elem.nodeType === 1 ) {
+ jQuery.cleanData( elem.getElementsByTagName( "*" ) );
+ elem.innerHTML = value;
+ }
+ }
+
+ elem = 0;
+
+ // If using innerHTML throws an exception, use the fallback method
+ } catch(e) {}
+ }
+
+ if ( elem ) {
+ this.empty().append( value );
+ }
+ }, null, value, arguments.length );
+ },
+
+ replaceWith: function( value ) {
+ if ( !isDisconnected( this[0] ) ) {
+ // Make sure that the elements are removed from the DOM before they are inserted
+ // this can help fix replacing a parent with child elements
+ if ( jQuery.isFunction( value ) ) {
+ return this.each(function(i) {
+ var self = jQuery(this), old = self.html();
+ self.replaceWith( value.call( this, i, old ) );
+ });
+ }
+
+ if ( typeof value !== "string" ) {
+ value = jQuery( value ).detach();
+ }
+
+ return this.each(function() {
+ var next = this.nextSibling,
+ parent = this.parentNode;
+
+ jQuery( this ).remove();
+
+ if ( next ) {
+ jQuery(next).before( value );
+ } else {
+ jQuery(parent).append( value );
+ }
+ });
+ }
+
+ return this.length ?
+ this.pushStack( jQuery(jQuery.isFunction(value) ? value() : value), "replaceWith", value ) :
+ this;
+ },
+
+ detach: function( selector ) {
+ return this.remove( selector, true );
+ },
+
+ domManip: function( args, table, callback ) {
+
+ // Flatten any nested arrays
+ args = [].concat.apply( [], args );
+
+ var results, first, fragment, iNoClone,
+ i = 0,
+ value = args[0],
+ scripts = [],
+ l = this.length;
+
+ // We can't cloneNode fragments that contain checked, in WebKit
+ if ( !jQuery.support.checkClone && l > 1 && typeof value === "string" && rchecked.test( value ) ) {
+ return this.each(function() {
+ jQuery(this).domManip( args, table, callback );
+ });
+ }
+
+ if ( jQuery.isFunction(value) ) {
+ return this.each(function(i) {
+ var self = jQuery(this);
+ args[0] = value.call( this, i, table ? self.html() : undefined );
+ self.domManip( args, table, callback );
+ });
+ }
+
+ if ( this[0] ) {
+ results = jQuery.buildFragment( args, this, scripts );
+ fragment = results.fragment;
+ first = fragment.firstChild;
+
+ if ( fragment.childNodes.length === 1 ) {
+ fragment = first;
+ }
+
+ if ( first ) {
+ table = table && jQuery.nodeName( first, "tr" );
+
+ // Use the original fragment for the last item instead of the first because it can end up
+ // being emptied incorrectly in certain situations (#8070).
+ // Fragments from the fragment cache must always be cloned and never used in place.
+ for ( iNoClone = results.cacheable || l - 1; i < l; i++ ) {
+ callback.call(
+ table && jQuery.nodeName( this[i], "table" ) ?
+ findOrAppend( this[i], "tbody" ) :
+ this[i],
+ i === iNoClone ?
+ fragment :
+ jQuery.clone( fragment, true, true )
+ );
+ }
+ }
+
+ // Fix #11809: Avoid leaking memory
+ fragment = first = null;
+
+ if ( scripts.length ) {
+ jQuery.each( scripts, function( i, elem ) {
+ if ( elem.src ) {
+ if ( jQuery.ajax ) {
+ jQuery.ajax({
+ url: elem.src,
+ type: "GET",
+ dataType: "script",
+ async: false,
+ global: false,
+ "throws": true
+ });
+ } else {
+ jQuery.error("no ajax");
+ }
+ } else {
+ jQuery.globalEval( ( elem.text || elem.textContent || elem.innerHTML || "" ).replace( rcleanScript, "" ) );
+ }
+
+ if ( elem.parentNode ) {
+ elem.parentNode.removeChild( elem );
+ }
+ });
+ }
+ }
+
+ return this;
+ }
+});
+
+function findOrAppend( elem, tag ) {
+ return elem.getElementsByTagName( tag )[0] || elem.appendChild( elem.ownerDocument.createElement( tag ) );
+}
+
+function cloneCopyEvent( src, dest ) {
+
+ if ( dest.nodeType !== 1 || !jQuery.hasData( src ) ) {
+ return;
+ }
+
+ var type, i, l,
+ oldData = jQuery._data( src ),
+ curData = jQuery._data( dest, oldData ),
+ events = oldData.events;
+
+ if ( events ) {
+ delete curData.handle;
+ curData.events = {};
+
+ for ( type in events ) {
+ for ( i = 0, l = events[ type ].length; i < l; i++ ) {
+ jQuery.event.add( dest, type, events[ type ][ i ] );
+ }
+ }
+ }
+
+ // make the cloned public data object a copy from the original
+ if ( curData.data ) {
+ curData.data = jQuery.extend( {}, curData.data );
+ }
+}
+
+function cloneFixAttributes( src, dest ) {
+ var nodeName;
+
+ // We do not need to do anything for non-Elements
+ if ( dest.nodeType !== 1 ) {
+ return;
+ }
+
+ // clearAttributes removes the attributes, which we don't want,
+ // but also removes the attachEvent events, which we *do* want
+ if ( dest.clearAttributes ) {
+ dest.clearAttributes();
+ }
+
+ // mergeAttributes, in contrast, only merges back on the
+ // original attributes, not the events
+ if ( dest.mergeAttributes ) {
+ dest.mergeAttributes( src );
+ }
+
+ nodeName = dest.nodeName.toLowerCase();
+
+ if ( nodeName === "object" ) {
+ // IE6-10 improperly clones children of object elements using classid.
+ // IE10 throws NoModificationAllowedError if parent is null, #12132.
+ if ( dest.parentNode ) {
+ dest.outerHTML = src.outerHTML;
+ }
+
+ // This path appears unavoidable for IE9. When cloning an object
+ // element in IE9, the outerHTML strategy above is not sufficient.
+ // If the src has innerHTML and the destination does not,
+ // copy the src.innerHTML into the dest.innerHTML. #10324
+ if ( jQuery.support.html5Clone && (src.innerHTML && !jQuery.trim(dest.innerHTML)) ) {
+ dest.innerHTML = src.innerHTML;
+ }
+
+ } else if ( nodeName === "input" && rcheckableType.test( src.type ) ) {
+ // IE6-8 fails to persist the checked state of a cloned checkbox
+ // or radio button. Worse, IE6-7 fail to give the cloned element
+ // a checked appearance if the defaultChecked value isn't also set
+
+ dest.defaultChecked = dest.checked = src.checked;
+
+ // IE6-7 get confused and end up setting the value of a cloned
+ // checkbox/radio button to an empty string instead of "on"
+ if ( dest.value !== src.value ) {
+ dest.value = src.value;
+ }
+
+ // IE6-8 fails to return the selected option to the default selected
+ // state when cloning options
+ } else if ( nodeName === "option" ) {
+ dest.selected = src.defaultSelected;
+
+ // IE6-8 fails to set the defaultValue to the correct value when
+ // cloning other types of input fields
+ } else if ( nodeName === "input" || nodeName === "textarea" ) {
+ dest.defaultValue = src.defaultValue;
+
+ // IE blanks contents when cloning scripts
+ } else if ( nodeName === "script" && dest.text !== src.text ) {
+ dest.text = src.text;
+ }
+
+ // Event data gets referenced instead of copied if the expando
+ // gets copied too
+ dest.removeAttribute( jQuery.expando );
+}
+
+jQuery.buildFragment = function( args, context, scripts ) {
+ var fragment, cacheable, cachehit,
+ first = args[ 0 ];
+
+ // Set context from what may come in as undefined or a jQuery collection or a node
+ // Updated to fix #12266 where accessing context[0] could throw an exception in IE9/10 &
+ // also doubles as fix for #8950 where plain objects caused createDocumentFragment exception
+ context = context || document;
+ context = !context.nodeType && context[0] || context;
+ context = context.ownerDocument || context;
+
+ // Only cache "small" (1/2 KB) HTML strings that are associated with the main document
+ // Cloning options loses the selected state, so don't cache them
+ // IE 6 doesn't like it when you put or elements in a fragment
+ // Also, WebKit does not clone 'checked' attributes on cloneNode, so don't cache
+ // Lastly, IE6,7,8 will not correctly reuse cached fragments that were created from unknown elems #10501
+ if ( args.length === 1 && typeof first === "string" && first.length < 512 && context === document &&
+ first.charAt(0) === "<" && !rnocache.test( first ) &&
+ (jQuery.support.checkClone || !rchecked.test( first )) &&
+ (jQuery.support.html5Clone || !rnoshimcache.test( first )) ) {
+
+ // Mark cacheable and look for a hit
+ cacheable = true;
+ fragment = jQuery.fragments[ first ];
+ cachehit = fragment !== undefined;
+ }
+
+ if ( !fragment ) {
+ fragment = context.createDocumentFragment();
+ jQuery.clean( args, context, fragment, scripts );
+
+ // Update the cache, but only store false
+ // unless this is a second parsing of the same content
+ if ( cacheable ) {
+ jQuery.fragments[ first ] = cachehit && fragment;
+ }
+ }
+
+ return { fragment: fragment, cacheable: cacheable };
+};
+
+jQuery.fragments = {};
+
+jQuery.each({
+ appendTo: "append",
+ prependTo: "prepend",
+ insertBefore: "before",
+ insertAfter: "after",
+ replaceAll: "replaceWith"
+}, function( name, original ) {
+ jQuery.fn[ name ] = function( selector ) {
+ var elems,
+ i = 0,
+ ret = [],
+ insert = jQuery( selector ),
+ l = insert.length,
+ parent = this.length === 1 && this[0].parentNode;
+
+ if ( (parent == null || parent && parent.nodeType === 11 && parent.childNodes.length === 1) && l === 1 ) {
+ insert[ original ]( this[0] );
+ return this;
+ } else {
+ for ( ; i < l; i++ ) {
+ elems = ( i > 0 ? this.clone(true) : this ).get();
+ jQuery( insert[i] )[ original ]( elems );
+ ret = ret.concat( elems );
+ }
+
+ return this.pushStack( ret, name, insert.selector );
+ }
+ };
+});
+
+function getAll( elem ) {
+ if ( typeof elem.getElementsByTagName !== "undefined" ) {
+ return elem.getElementsByTagName( "*" );
+
+ } else if ( typeof elem.querySelectorAll !== "undefined" ) {
+ return elem.querySelectorAll( "*" );
+
+ } else {
+ return [];
+ }
+}
+
+// Used in clean, fixes the defaultChecked property
+function fixDefaultChecked( elem ) {
+ if ( rcheckableType.test( elem.type ) ) {
+ elem.defaultChecked = elem.checked;
+ }
+}
+
+jQuery.extend({
+ clone: function( elem, dataAndEvents, deepDataAndEvents ) {
+ var srcElements,
+ destElements,
+ i,
+ clone;
+
+ if ( jQuery.support.html5Clone || jQuery.isXMLDoc(elem) || !rnoshimcache.test( "<" + elem.nodeName + ">" ) ) {
+ clone = elem.cloneNode( true );
+
+ // IE<=8 does not properly clone detached, unknown element nodes
+ } else {
+ fragmentDiv.innerHTML = elem.outerHTML;
+ fragmentDiv.removeChild( clone = fragmentDiv.firstChild );
+ }
+
+ if ( (!jQuery.support.noCloneEvent || !jQuery.support.noCloneChecked) &&
+ (elem.nodeType === 1 || elem.nodeType === 11) && !jQuery.isXMLDoc(elem) ) {
+ // IE copies events bound via attachEvent when using cloneNode.
+ // Calling detachEvent on the clone will also remove the events
+ // from the original. In order to get around this, we use some
+ // proprietary methods to clear the events. Thanks to MooTools
+ // guys for this hotness.
+
+ cloneFixAttributes( elem, clone );
+
+ // Using Sizzle here is crazy slow, so we use getElementsByTagName instead
+ srcElements = getAll( elem );
+ destElements = getAll( clone );
+
+ // Weird iteration because IE will replace the length property
+ // with an element if you are cloning the body and one of the
+ // elements on the page has a name or id of "length"
+ for ( i = 0; srcElements[i]; ++i ) {
+ // Ensure that the destination node is not null; Fixes #9587
+ if ( destElements[i] ) {
+ cloneFixAttributes( srcElements[i], destElements[i] );
+ }
+ }
+ }
+
+ // Copy the events from the original to the clone
+ if ( dataAndEvents ) {
+ cloneCopyEvent( elem, clone );
+
+ if ( deepDataAndEvents ) {
+ srcElements = getAll( elem );
+ destElements = getAll( clone );
+
+ for ( i = 0; srcElements[i]; ++i ) {
+ cloneCopyEvent( srcElements[i], destElements[i] );
+ }
+ }
+ }
+
+ srcElements = destElements = null;
+
+ // Return the cloned set
+ return clone;
+ },
+
+ clean: function( elems, context, fragment, scripts ) {
+ var i, j, elem, tag, wrap, depth, div, hasBody, tbody, len, handleScript, jsTags,
+ safe = context === document && safeFragment,
+ ret = [];
+
+ // Ensure that context is a document
+ if ( !context || typeof context.createDocumentFragment === "undefined" ) {
+ context = document;
+ }
+
+ // Use the already-created safe fragment if context permits
+ for ( i = 0; (elem = elems[i]) != null; i++ ) {
+ if ( typeof elem === "number" ) {
+ elem += "";
+ }
+
+ if ( !elem ) {
+ continue;
+ }
+
+ // Convert html string into DOM nodes
+ if ( typeof elem === "string" ) {
+ if ( !rhtml.test( elem ) ) {
+ elem = context.createTextNode( elem );
+ } else {
+ // Ensure a safe container in which to render the html
+ safe = safe || createSafeFragment( context );
+ div = context.createElement("div");
+ safe.appendChild( div );
+
+ // Fix "XHTML"-style tags in all browsers
+ elem = elem.replace(rxhtmlTag, "<$1>$2>");
+
+ // Go to html and back, then peel off extra wrappers
+ tag = ( rtagName.exec( elem ) || ["", ""] )[1].toLowerCase();
+ wrap = wrapMap[ tag ] || wrapMap._default;
+ depth = wrap[0];
+ div.innerHTML = wrap[1] + elem + wrap[2];
+
+ // Move to the right depth
+ while ( depth-- ) {
+ div = div.lastChild;
+ }
+
+ // Remove IE's autoinserted from table fragments
+ if ( !jQuery.support.tbody ) {
+
+ // String was a , *may* have spurious
+ hasBody = rtbody.test(elem);
+ tbody = tag === "table" && !hasBody ?
+ div.firstChild && div.firstChild.childNodes :
+
+ // String was a bare or
+ wrap[1] === "" && !hasBody ?
+ div.childNodes :
+ [];
+
+ for ( j = tbody.length - 1; j >= 0 ; --j ) {
+ if ( jQuery.nodeName( tbody[ j ], "tbody" ) && !tbody[ j ].childNodes.length ) {
+ tbody[ j ].parentNode.removeChild( tbody[ j ] );
+ }
+ }
+ }
+
+ // IE completely kills leading whitespace when innerHTML is used
+ if ( !jQuery.support.leadingWhitespace && rleadingWhitespace.test( elem ) ) {
+ div.insertBefore( context.createTextNode( rleadingWhitespace.exec(elem)[0] ), div.firstChild );
+ }
+
+ elem = div.childNodes;
+
+ // Take out of fragment container (we need a fresh div each time)
+ div.parentNode.removeChild( div );
+ }
+ }
+
+ if ( elem.nodeType ) {
+ ret.push( elem );
+ } else {
+ jQuery.merge( ret, elem );
+ }
+ }
+
+ // Fix #11356: Clear elements from safeFragment
+ if ( div ) {
+ elem = div = safe = null;
+ }
+
+ // Reset defaultChecked for any radios and checkboxes
+ // about to be appended to the DOM in IE 6/7 (#8060)
+ if ( !jQuery.support.appendChecked ) {
+ for ( i = 0; (elem = ret[i]) != null; i++ ) {
+ if ( jQuery.nodeName( elem, "input" ) ) {
+ fixDefaultChecked( elem );
+ } else if ( typeof elem.getElementsByTagName !== "undefined" ) {
+ jQuery.grep( elem.getElementsByTagName("input"), fixDefaultChecked );
+ }
+ }
+ }
+
+ // Append elements to a provided document fragment
+ if ( fragment ) {
+ // Special handling of each script element
+ handleScript = function( elem ) {
+ // Check if we consider it executable
+ if ( !elem.type || rscriptType.test( elem.type ) ) {
+ // Detach the script and store it in the scripts array (if provided) or the fragment
+ // Return truthy to indicate that it has been handled
+ return scripts ?
+ scripts.push( elem.parentNode ? elem.parentNode.removeChild( elem ) : elem ) :
+ fragment.appendChild( elem );
+ }
+ };
+
+ for ( i = 0; (elem = ret[i]) != null; i++ ) {
+ // Check if we're done after handling an executable script
+ if ( !( jQuery.nodeName( elem, "script" ) && handleScript( elem ) ) ) {
+ // Append to fragment and handle embedded scripts
+ fragment.appendChild( elem );
+ if ( typeof elem.getElementsByTagName !== "undefined" ) {
+ // handleScript alters the DOM, so use jQuery.merge to ensure snapshot iteration
+ jsTags = jQuery.grep( jQuery.merge( [], elem.getElementsByTagName("script") ), handleScript );
+
+ // Splice the scripts into ret after their former ancestor and advance our index beyond them
+ ret.splice.apply( ret, [i + 1, 0].concat( jsTags ) );
+ i += jsTags.length;
+ }
+ }
+ }
+ }
+
+ return ret;
+ },
+
+ cleanData: function( elems, /* internal */ acceptData ) {
+ var data, id, elem, type,
+ i = 0,
+ internalKey = jQuery.expando,
+ cache = jQuery.cache,
+ deleteExpando = jQuery.support.deleteExpando,
+ special = jQuery.event.special;
+
+ for ( ; (elem = elems[i]) != null; i++ ) {
+
+ if ( acceptData || jQuery.acceptData( elem ) ) {
+
+ id = elem[ internalKey ];
+ data = id && cache[ id ];
+
+ if ( data ) {
+ if ( data.events ) {
+ for ( type in data.events ) {
+ if ( special[ type ] ) {
+ jQuery.event.remove( elem, type );
+
+ // This is a shortcut to avoid jQuery.event.remove's overhead
+ } else {
+ jQuery.removeEvent( elem, type, data.handle );
+ }
+ }
+ }
+
+ // Remove cache only if it was not already removed by jQuery.event.remove
+ if ( cache[ id ] ) {
+
+ delete cache[ id ];
+
+ // IE does not allow us to delete expando properties from nodes,
+ // nor does it have a removeAttribute function on Document nodes;
+ // we must handle all of these cases
+ if ( deleteExpando ) {
+ delete elem[ internalKey ];
+
+ } else if ( elem.removeAttribute ) {
+ elem.removeAttribute( internalKey );
+
+ } else {
+ elem[ internalKey ] = null;
+ }
+
+ jQuery.deletedIds.push( id );
+ }
+ }
+ }
+ }
+ }
+});
+// Limit scope pollution from any deprecated API
+(function() {
+
+var matched, browser;
+
+// Use of jQuery.browser is frowned upon.
+// More details: http://api.jquery.com/jQuery.browser
+// jQuery.uaMatch maintained for back-compat
+jQuery.uaMatch = function( ua ) {
+ ua = ua.toLowerCase();
+
+ var match = /(chrome)[ \/]([\w.]+)/.exec( ua ) ||
+ /(webkit)[ \/]([\w.]+)/.exec( ua ) ||
+ /(opera)(?:.*version|)[ \/]([\w.]+)/.exec( ua ) ||
+ /(msie) ([\w.]+)/.exec( ua ) ||
+ ua.indexOf("compatible") < 0 && /(mozilla)(?:.*? rv:([\w.]+)|)/.exec( ua ) ||
+ [];
+
+ return {
+ browser: match[ 1 ] || "",
+ version: match[ 2 ] || "0"
+ };
+};
+
+matched = jQuery.uaMatch( navigator.userAgent );
+browser = {};
+
+if ( matched.browser ) {
+ browser[ matched.browser ] = true;
+ browser.version = matched.version;
+}
+
+// Chrome is Webkit, but Webkit is also Safari.
+if ( browser.chrome ) {
+ browser.webkit = true;
+} else if ( browser.webkit ) {
+ browser.safari = true;
+}
+
+jQuery.browser = browser;
+
+jQuery.sub = function() {
+ function jQuerySub( selector, context ) {
+ return new jQuerySub.fn.init( selector, context );
+ }
+ jQuery.extend( true, jQuerySub, this );
+ jQuerySub.superclass = this;
+ jQuerySub.fn = jQuerySub.prototype = this();
+ jQuerySub.fn.constructor = jQuerySub;
+ jQuerySub.sub = this.sub;
+ jQuerySub.fn.init = function init( selector, context ) {
+ if ( context && context instanceof jQuery && !(context instanceof jQuerySub) ) {
+ context = jQuerySub( context );
+ }
+
+ return jQuery.fn.init.call( this, selector, context, rootjQuerySub );
+ };
+ jQuerySub.fn.init.prototype = jQuerySub.fn;
+ var rootjQuerySub = jQuerySub(document);
+ return jQuerySub;
+};
+
+})();
+var curCSS, iframe, iframeDoc,
+ ralpha = /alpha\([^)]*\)/i,
+ ropacity = /opacity=([^)]*)/,
+ rposition = /^(top|right|bottom|left)$/,
+ // swappable if display is none or starts with table except "table", "table-cell", or "table-caption"
+ // see here for display values: https://developer.mozilla.org/en-US/docs/CSS/display
+ rdisplayswap = /^(none|table(?!-c[ea]).+)/,
+ rmargin = /^margin/,
+ rnumsplit = new RegExp( "^(" + core_pnum + ")(.*)$", "i" ),
+ rnumnonpx = new RegExp( "^(" + core_pnum + ")(?!px)[a-z%]+$", "i" ),
+ rrelNum = new RegExp( "^([-+])=(" + core_pnum + ")", "i" ),
+ elemdisplay = { BODY: "block" },
+
+ cssShow = { position: "absolute", visibility: "hidden", display: "block" },
+ cssNormalTransform = {
+ letterSpacing: 0,
+ fontWeight: 400
+ },
+
+ cssExpand = [ "Top", "Right", "Bottom", "Left" ],
+ cssPrefixes = [ "Webkit", "O", "Moz", "ms" ],
+
+ eventsToggle = jQuery.fn.toggle;
+
+// return a css property mapped to a potentially vendor prefixed property
+function vendorPropName( style, name ) {
+
+ // shortcut for names that are not vendor prefixed
+ if ( name in style ) {
+ return name;
+ }
+
+ // check for vendor prefixed names
+ var capName = name.charAt(0).toUpperCase() + name.slice(1),
+ origName = name,
+ i = cssPrefixes.length;
+
+ while ( i-- ) {
+ name = cssPrefixes[ i ] + capName;
+ if ( name in style ) {
+ return name;
+ }
+ }
+
+ return origName;
+}
+
+function isHidden( elem, el ) {
+ elem = el || elem;
+ return jQuery.css( elem, "display" ) === "none" || !jQuery.contains( elem.ownerDocument, elem );
+}
+
+function showHide( elements, show ) {
+ var elem, display,
+ values = [],
+ index = 0,
+ length = elements.length;
+
+ for ( ; index < length; index++ ) {
+ elem = elements[ index ];
+ if ( !elem.style ) {
+ continue;
+ }
+ values[ index ] = jQuery._data( elem, "olddisplay" );
+ if ( show ) {
+ // Reset the inline display of this element to learn if it is
+ // being hidden by cascaded rules or not
+ if ( !values[ index ] && elem.style.display === "none" ) {
+ elem.style.display = "";
+ }
+
+ // Set elements which have been overridden with display: none
+ // in a stylesheet to whatever the default browser style is
+ // for such an element
+ if ( elem.style.display === "" && isHidden( elem ) ) {
+ values[ index ] = jQuery._data( elem, "olddisplay", css_defaultDisplay(elem.nodeName) );
+ }
+ } else {
+ display = curCSS( elem, "display" );
+
+ if ( !values[ index ] && display !== "none" ) {
+ jQuery._data( elem, "olddisplay", display );
+ }
+ }
+ }
+
+ // Set the display of most of the elements in a second loop
+ // to avoid the constant reflow
+ for ( index = 0; index < length; index++ ) {
+ elem = elements[ index ];
+ if ( !elem.style ) {
+ continue;
+ }
+ if ( !show || elem.style.display === "none" || elem.style.display === "" ) {
+ elem.style.display = show ? values[ index ] || "" : "none";
+ }
+ }
+
+ return elements;
+}
+
+jQuery.fn.extend({
+ css: function( name, value ) {
+ return jQuery.access( this, function( elem, name, value ) {
+ return value !== undefined ?
+ jQuery.style( elem, name, value ) :
+ jQuery.css( elem, name );
+ }, name, value, arguments.length > 1 );
+ },
+ show: function() {
+ return showHide( this, true );
+ },
+ hide: function() {
+ return showHide( this );
+ },
+ toggle: function( state, fn2 ) {
+ var bool = typeof state === "boolean";
+
+ if ( jQuery.isFunction( state ) && jQuery.isFunction( fn2 ) ) {
+ return eventsToggle.apply( this, arguments );
+ }
+
+ return this.each(function() {
+ if ( bool ? state : isHidden( this ) ) {
+ jQuery( this ).show();
+ } else {
+ jQuery( this ).hide();
+ }
+ });
+ }
+});
+
+jQuery.extend({
+ // Add in style property hooks for overriding the default
+ // behavior of getting and setting a style property
+ cssHooks: {
+ opacity: {
+ get: function( elem, computed ) {
+ if ( computed ) {
+ // We should always get a number back from opacity
+ var ret = curCSS( elem, "opacity" );
+ return ret === "" ? "1" : ret;
+
+ }
+ }
+ }
+ },
+
+ // Exclude the following css properties to add px
+ cssNumber: {
+ "fillOpacity": true,
+ "fontWeight": true,
+ "lineHeight": true,
+ "opacity": true,
+ "orphans": true,
+ "widows": true,
+ "zIndex": true,
+ "zoom": true
+ },
+
+ // Add in properties whose names you wish to fix before
+ // setting or getting the value
+ cssProps: {
+ // normalize float css property
+ "float": jQuery.support.cssFloat ? "cssFloat" : "styleFloat"
+ },
+
+ // Get and set the style property on a DOM Node
+ style: function( elem, name, value, extra ) {
+ // Don't set styles on text and comment nodes
+ if ( !elem || elem.nodeType === 3 || elem.nodeType === 8 || !elem.style ) {
+ return;
+ }
+
+ // Make sure that we're working with the right name
+ var ret, type, hooks,
+ origName = jQuery.camelCase( name ),
+ style = elem.style;
+
+ name = jQuery.cssProps[ origName ] || ( jQuery.cssProps[ origName ] = vendorPropName( style, origName ) );
+
+ // gets hook for the prefixed version
+ // followed by the unprefixed version
+ hooks = jQuery.cssHooks[ name ] || jQuery.cssHooks[ origName ];
+
+ // Check if we're setting a value
+ if ( value !== undefined ) {
+ type = typeof value;
+
+ // convert relative number strings (+= or -=) to relative numbers. #7345
+ if ( type === "string" && (ret = rrelNum.exec( value )) ) {
+ value = ( ret[1] + 1 ) * ret[2] + parseFloat( jQuery.css( elem, name ) );
+ // Fixes bug #9237
+ type = "number";
+ }
+
+ // Make sure that NaN and null values aren't set. See: #7116
+ if ( value == null || type === "number" && isNaN( value ) ) {
+ return;
+ }
+
+ // If a number was passed in, add 'px' to the (except for certain CSS properties)
+ if ( type === "number" && !jQuery.cssNumber[ origName ] ) {
+ value += "px";
+ }
+
+ // If a hook was provided, use that value, otherwise just set the specified value
+ if ( !hooks || !("set" in hooks) || (value = hooks.set( elem, value, extra )) !== undefined ) {
+ // Wrapped to prevent IE from throwing errors when 'invalid' values are provided
+ // Fixes bug #5509
+ try {
+ style[ name ] = value;
+ } catch(e) {}
+ }
+
+ } else {
+ // If a hook was provided get the non-computed value from there
+ if ( hooks && "get" in hooks && (ret = hooks.get( elem, false, extra )) !== undefined ) {
+ return ret;
+ }
+
+ // Otherwise just get the value from the style object
+ return style[ name ];
+ }
+ },
+
+ css: function( elem, name, numeric, extra ) {
+ var val, num, hooks,
+ origName = jQuery.camelCase( name );
+
+ // Make sure that we're working with the right name
+ name = jQuery.cssProps[ origName ] || ( jQuery.cssProps[ origName ] = vendorPropName( elem.style, origName ) );
+
+ // gets hook for the prefixed version
+ // followed by the unprefixed version
+ hooks = jQuery.cssHooks[ name ] || jQuery.cssHooks[ origName ];
+
+ // If a hook was provided get the computed value from there
+ if ( hooks && "get" in hooks ) {
+ val = hooks.get( elem, true, extra );
+ }
+
+ // Otherwise, if a way to get the computed value exists, use that
+ if ( val === undefined ) {
+ val = curCSS( elem, name );
+ }
+
+ //convert "normal" to computed value
+ if ( val === "normal" && name in cssNormalTransform ) {
+ val = cssNormalTransform[ name ];
+ }
+
+ // Return, converting to number if forced or a qualifier was provided and val looks numeric
+ if ( numeric || extra !== undefined ) {
+ num = parseFloat( val );
+ return numeric || jQuery.isNumeric( num ) ? num || 0 : val;
+ }
+ return val;
+ },
+
+ // A method for quickly swapping in/out CSS properties to get correct calculations
+ swap: function( elem, options, callback ) {
+ var ret, name,
+ old = {};
+
+ // Remember the old values, and insert the new ones
+ for ( name in options ) {
+ old[ name ] = elem.style[ name ];
+ elem.style[ name ] = options[ name ];
+ }
+
+ ret = callback.call( elem );
+
+ // Revert the old values
+ for ( name in options ) {
+ elem.style[ name ] = old[ name ];
+ }
+
+ return ret;
+ }
+});
+
+// NOTE: To any future maintainer, we've window.getComputedStyle
+// because jsdom on node.js will break without it.
+if ( window.getComputedStyle ) {
+ curCSS = function( elem, name ) {
+ var ret, width, minWidth, maxWidth,
+ computed = window.getComputedStyle( elem, null ),
+ style = elem.style;
+
+ if ( computed ) {
+
+ // getPropertyValue is only needed for .css('filter') in IE9, see #12537
+ ret = computed.getPropertyValue( name ) || computed[ name ];
+
+ if ( ret === "" && !jQuery.contains( elem.ownerDocument, elem ) ) {
+ ret = jQuery.style( elem, name );
+ }
+
+ // A tribute to the "awesome hack by Dean Edwards"
+ // Chrome < 17 and Safari 5.0 uses "computed value" instead of "used value" for margin-right
+ // Safari 5.1.7 (at least) returns percentage for a larger set of values, but width seems to be reliably pixels
+ // this is against the CSSOM draft spec: http://dev.w3.org/csswg/cssom/#resolved-values
+ if ( rnumnonpx.test( ret ) && rmargin.test( name ) ) {
+ width = style.width;
+ minWidth = style.minWidth;
+ maxWidth = style.maxWidth;
+
+ style.minWidth = style.maxWidth = style.width = ret;
+ ret = computed.width;
+
+ style.width = width;
+ style.minWidth = minWidth;
+ style.maxWidth = maxWidth;
+ }
+ }
+
+ return ret;
+ };
+} else if ( document.documentElement.currentStyle ) {
+ curCSS = function( elem, name ) {
+ var left, rsLeft,
+ ret = elem.currentStyle && elem.currentStyle[ name ],
+ style = elem.style;
+
+ // Avoid setting ret to empty string here
+ // so we don't default to auto
+ if ( ret == null && style && style[ name ] ) {
+ ret = style[ name ];
+ }
+
+ // From the awesome hack by Dean Edwards
+ // http://erik.eae.net/archives/2007/07/27/18.54.15/#comment-102291
+
+ // If we're not dealing with a regular pixel number
+ // but a number that has a weird ending, we need to convert it to pixels
+ // but not position css attributes, as those are proportional to the parent element instead
+ // and we can't measure the parent instead because it might trigger a "stacking dolls" problem
+ if ( rnumnonpx.test( ret ) && !rposition.test( name ) ) {
+
+ // Remember the original values
+ left = style.left;
+ rsLeft = elem.runtimeStyle && elem.runtimeStyle.left;
+
+ // Put in the new values to get a computed value out
+ if ( rsLeft ) {
+ elem.runtimeStyle.left = elem.currentStyle.left;
+ }
+ style.left = name === "fontSize" ? "1em" : ret;
+ ret = style.pixelLeft + "px";
+
+ // Revert the changed values
+ style.left = left;
+ if ( rsLeft ) {
+ elem.runtimeStyle.left = rsLeft;
+ }
+ }
+
+ return ret === "" ? "auto" : ret;
+ };
+}
+
+function setPositiveNumber( elem, value, subtract ) {
+ var matches = rnumsplit.exec( value );
+ return matches ?
+ Math.max( 0, matches[ 1 ] - ( subtract || 0 ) ) + ( matches[ 2 ] || "px" ) :
+ value;
+}
+
+function augmentWidthOrHeight( elem, name, extra, isBorderBox ) {
+ var i = extra === ( isBorderBox ? "border" : "content" ) ?
+ // If we already have the right measurement, avoid augmentation
+ 4 :
+ // Otherwise initialize for horizontal or vertical properties
+ name === "width" ? 1 : 0,
+
+ val = 0;
+
+ for ( ; i < 4; i += 2 ) {
+ // both box models exclude margin, so add it if we want it
+ if ( extra === "margin" ) {
+ // we use jQuery.css instead of curCSS here
+ // because of the reliableMarginRight CSS hook!
+ val += jQuery.css( elem, extra + cssExpand[ i ], true );
+ }
+
+ // From this point on we use curCSS for maximum performance (relevant in animations)
+ if ( isBorderBox ) {
+ // border-box includes padding, so remove it if we want content
+ if ( extra === "content" ) {
+ val -= parseFloat( curCSS( elem, "padding" + cssExpand[ i ] ) ) || 0;
+ }
+
+ // at this point, extra isn't border nor margin, so remove border
+ if ( extra !== "margin" ) {
+ val -= parseFloat( curCSS( elem, "border" + cssExpand[ i ] + "Width" ) ) || 0;
+ }
+ } else {
+ // at this point, extra isn't content, so add padding
+ val += parseFloat( curCSS( elem, "padding" + cssExpand[ i ] ) ) || 0;
+
+ // at this point, extra isn't content nor padding, so add border
+ if ( extra !== "padding" ) {
+ val += parseFloat( curCSS( elem, "border" + cssExpand[ i ] + "Width" ) ) || 0;
+ }
+ }
+ }
+
+ return val;
+}
+
+function getWidthOrHeight( elem, name, extra ) {
+
+ // Start with offset property, which is equivalent to the border-box value
+ var val = name === "width" ? elem.offsetWidth : elem.offsetHeight,
+ valueIsBorderBox = true,
+ isBorderBox = jQuery.support.boxSizing && jQuery.css( elem, "boxSizing" ) === "border-box";
+
+ // some non-html elements return undefined for offsetWidth, so check for null/undefined
+ // svg - https://bugzilla.mozilla.org/show_bug.cgi?id=649285
+ // MathML - https://bugzilla.mozilla.org/show_bug.cgi?id=491668
+ if ( val <= 0 || val == null ) {
+ // Fall back to computed then uncomputed css if necessary
+ val = curCSS( elem, name );
+ if ( val < 0 || val == null ) {
+ val = elem.style[ name ];
+ }
+
+ // Computed unit is not pixels. Stop here and return.
+ if ( rnumnonpx.test(val) ) {
+ return val;
+ }
+
+ // we need the check for style in case a browser which returns unreliable values
+ // for getComputedStyle silently falls back to the reliable elem.style
+ valueIsBorderBox = isBorderBox && ( jQuery.support.boxSizingReliable || val === elem.style[ name ] );
+
+ // Normalize "", auto, and prepare for extra
+ val = parseFloat( val ) || 0;
+ }
+
+ // use the active box-sizing model to add/subtract irrelevant styles
+ return ( val +
+ augmentWidthOrHeight(
+ elem,
+ name,
+ extra || ( isBorderBox ? "border" : "content" ),
+ valueIsBorderBox
+ )
+ ) + "px";
+}
+
+
+// Try to determine the default display value of an element
+function css_defaultDisplay( nodeName ) {
+ if ( elemdisplay[ nodeName ] ) {
+ return elemdisplay[ nodeName ];
+ }
+
+ var elem = jQuery( "<" + nodeName + ">" ).appendTo( document.body ),
+ display = elem.css("display");
+ elem.remove();
+
+ // If the simple way fails,
+ // get element's real default display by attaching it to a temp iframe
+ if ( display === "none" || display === "" ) {
+ // Use the already-created iframe if possible
+ iframe = document.body.appendChild(
+ iframe || jQuery.extend( document.createElement("iframe"), {
+ frameBorder: 0,
+ width: 0,
+ height: 0
+ })
+ );
+
+ // Create a cacheable copy of the iframe document on first call.
+ // IE and Opera will allow us to reuse the iframeDoc without re-writing the fake HTML
+ // document to it; WebKit & Firefox won't allow reusing the iframe document.
+ if ( !iframeDoc || !iframe.createElement ) {
+ iframeDoc = ( iframe.contentWindow || iframe.contentDocument ).document;
+ iframeDoc.write("");
+ iframeDoc.close();
+ }
+
+ elem = iframeDoc.body.appendChild( iframeDoc.createElement(nodeName) );
+
+ display = curCSS( elem, "display" );
+ document.body.removeChild( iframe );
+ }
+
+ // Store the correct default display
+ elemdisplay[ nodeName ] = display;
+
+ return display;
+}
+
+jQuery.each([ "height", "width" ], function( i, name ) {
+ jQuery.cssHooks[ name ] = {
+ get: function( elem, computed, extra ) {
+ if ( computed ) {
+ // certain elements can have dimension info if we invisibly show them
+ // however, it must have a current display style that would benefit from this
+ if ( elem.offsetWidth === 0 && rdisplayswap.test( curCSS( elem, "display" ) ) ) {
+ return jQuery.swap( elem, cssShow, function() {
+ return getWidthOrHeight( elem, name, extra );
+ });
+ } else {
+ return getWidthOrHeight( elem, name, extra );
+ }
+ }
+ },
+
+ set: function( elem, value, extra ) {
+ return setPositiveNumber( elem, value, extra ?
+ augmentWidthOrHeight(
+ elem,
+ name,
+ extra,
+ jQuery.support.boxSizing && jQuery.css( elem, "boxSizing" ) === "border-box"
+ ) : 0
+ );
+ }
+ };
+});
+
+if ( !jQuery.support.opacity ) {
+ jQuery.cssHooks.opacity = {
+ get: function( elem, computed ) {
+ // IE uses filters for opacity
+ return ropacity.test( (computed && elem.currentStyle ? elem.currentStyle.filter : elem.style.filter) || "" ) ?
+ ( 0.01 * parseFloat( RegExp.$1 ) ) + "" :
+ computed ? "1" : "";
+ },
+
+ set: function( elem, value ) {
+ var style = elem.style,
+ currentStyle = elem.currentStyle,
+ opacity = jQuery.isNumeric( value ) ? "alpha(opacity=" + value * 100 + ")" : "",
+ filter = currentStyle && currentStyle.filter || style.filter || "";
+
+ // IE has trouble with opacity if it does not have layout
+ // Force it by setting the zoom level
+ style.zoom = 1;
+
+ // if setting opacity to 1, and no other filters exist - attempt to remove filter attribute #6652
+ if ( value >= 1 && jQuery.trim( filter.replace( ralpha, "" ) ) === "" &&
+ style.removeAttribute ) {
+
+ // Setting style.filter to null, "" & " " still leave "filter:" in the cssText
+ // if "filter:" is present at all, clearType is disabled, we want to avoid this
+ // style.removeAttribute is IE Only, but so apparently is this code path...
+ style.removeAttribute( "filter" );
+
+ // if there there is no filter style applied in a css rule, we are done
+ if ( currentStyle && !currentStyle.filter ) {
+ return;
+ }
+ }
+
+ // otherwise, set new filter values
+ style.filter = ralpha.test( filter ) ?
+ filter.replace( ralpha, opacity ) :
+ filter + " " + opacity;
+ }
+ };
+}
+
+// These hooks cannot be added until DOM ready because the support test
+// for it is not run until after DOM ready
+jQuery(function() {
+ if ( !jQuery.support.reliableMarginRight ) {
+ jQuery.cssHooks.marginRight = {
+ get: function( elem, computed ) {
+ // WebKit Bug 13343 - getComputedStyle returns wrong value for margin-right
+ // Work around by temporarily setting element display to inline-block
+ return jQuery.swap( elem, { "display": "inline-block" }, function() {
+ if ( computed ) {
+ return curCSS( elem, "marginRight" );
+ }
+ });
+ }
+ };
+ }
+
+ // Webkit bug: https://bugs.webkit.org/show_bug.cgi?id=29084
+ // getComputedStyle returns percent when specified for top/left/bottom/right
+ // rather than make the css module depend on the offset module, we just check for it here
+ if ( !jQuery.support.pixelPosition && jQuery.fn.position ) {
+ jQuery.each( [ "top", "left" ], function( i, prop ) {
+ jQuery.cssHooks[ prop ] = {
+ get: function( elem, computed ) {
+ if ( computed ) {
+ var ret = curCSS( elem, prop );
+ // if curCSS returns percentage, fallback to offset
+ return rnumnonpx.test( ret ) ? jQuery( elem ).position()[ prop ] + "px" : ret;
+ }
+ }
+ };
+ });
+ }
+
+});
+
+if ( jQuery.expr && jQuery.expr.filters ) {
+ jQuery.expr.filters.hidden = function( elem ) {
+ return ( elem.offsetWidth === 0 && elem.offsetHeight === 0 ) || (!jQuery.support.reliableHiddenOffsets && ((elem.style && elem.style.display) || curCSS( elem, "display" )) === "none");
+ };
+
+ jQuery.expr.filters.visible = function( elem ) {
+ return !jQuery.expr.filters.hidden( elem );
+ };
+}
+
+// These hooks are used by animate to expand properties
+jQuery.each({
+ margin: "",
+ padding: "",
+ border: "Width"
+}, function( prefix, suffix ) {
+ jQuery.cssHooks[ prefix + suffix ] = {
+ expand: function( value ) {
+ var i,
+
+ // assumes a single number if not a string
+ parts = typeof value === "string" ? value.split(" ") : [ value ],
+ expanded = {};
+
+ for ( i = 0; i < 4; i++ ) {
+ expanded[ prefix + cssExpand[ i ] + suffix ] =
+ parts[ i ] || parts[ i - 2 ] || parts[ 0 ];
+ }
+
+ return expanded;
+ }
+ };
+
+ if ( !rmargin.test( prefix ) ) {
+ jQuery.cssHooks[ prefix + suffix ].set = setPositiveNumber;
+ }
+});
+var r20 = /%20/g,
+ rbracket = /\[\]$/,
+ rCRLF = /\r?\n/g,
+ rinput = /^(?:color|date|datetime|datetime-local|email|hidden|month|number|password|range|search|tel|text|time|url|week)$/i,
+ rselectTextarea = /^(?:select|textarea)/i;
+
+jQuery.fn.extend({
+ serialize: function() {
+ return jQuery.param( this.serializeArray() );
+ },
+ serializeArray: function() {
+ return this.map(function(){
+ return this.elements ? jQuery.makeArray( this.elements ) : this;
+ })
+ .filter(function(){
+ return this.name && !this.disabled &&
+ ( this.checked || rselectTextarea.test( this.nodeName ) ||
+ rinput.test( this.type ) );
+ })
+ .map(function( i, elem ){
+ var val = jQuery( this ).val();
+
+ return val == null ?
+ null :
+ jQuery.isArray( val ) ?
+ jQuery.map( val, function( val, i ){
+ return { name: elem.name, value: val.replace( rCRLF, "\r\n" ) };
+ }) :
+ { name: elem.name, value: val.replace( rCRLF, "\r\n" ) };
+ }).get();
+ }
+});
+
+//Serialize an array of form elements or a set of
+//key/values into a query string
+jQuery.param = function( a, traditional ) {
+ var prefix,
+ s = [],
+ add = function( key, value ) {
+ // If value is a function, invoke it and return its value
+ value = jQuery.isFunction( value ) ? value() : ( value == null ? "" : value );
+ s[ s.length ] = encodeURIComponent( key ) + "=" + encodeURIComponent( value );
+ };
+
+ // Set traditional to true for jQuery <= 1.3.2 behavior.
+ if ( traditional === undefined ) {
+ traditional = jQuery.ajaxSettings && jQuery.ajaxSettings.traditional;
+ }
+
+ // If an array was passed in, assume that it is an array of form elements.
+ if ( jQuery.isArray( a ) || ( a.jquery && !jQuery.isPlainObject( a ) ) ) {
+ // Serialize the form elements
+ jQuery.each( a, function() {
+ add( this.name, this.value );
+ });
+
+ } else {
+ // If traditional, encode the "old" way (the way 1.3.2 or older
+ // did it), otherwise encode params recursively.
+ for ( prefix in a ) {
+ buildParams( prefix, a[ prefix ], traditional, add );
+ }
+ }
+
+ // Return the resulting serialization
+ return s.join( "&" ).replace( r20, "+" );
+};
+
+function buildParams( prefix, obj, traditional, add ) {
+ var name;
+
+ if ( jQuery.isArray( obj ) ) {
+ // Serialize array item.
+ jQuery.each( obj, function( i, v ) {
+ if ( traditional || rbracket.test( prefix ) ) {
+ // Treat each array item as a scalar.
+ add( prefix, v );
+
+ } else {
+ // If array item is non-scalar (array or object), encode its
+ // numeric index to resolve deserialization ambiguity issues.
+ // Note that rack (as of 1.0.0) can't currently deserialize
+ // nested arrays properly, and attempting to do so may cause
+ // a server error. Possible fixes are to modify rack's
+ // deserialization algorithm or to provide an option or flag
+ // to force array serialization to be shallow.
+ buildParams( prefix + "[" + ( typeof v === "object" ? i : "" ) + "]", v, traditional, add );
+ }
+ });
+
+ } else if ( !traditional && jQuery.type( obj ) === "object" ) {
+ // Serialize object item.
+ for ( name in obj ) {
+ buildParams( prefix + "[" + name + "]", obj[ name ], traditional, add );
+ }
+
+ } else {
+ // Serialize scalar item.
+ add( prefix, obj );
+ }
+}
+var
+ // Document location
+ ajaxLocParts,
+ ajaxLocation,
+
+ rhash = /#.*$/,
+ rheaders = /^(.*?):[ \t]*([^\r\n]*)\r?$/mg, // IE leaves an \r character at EOL
+ // #7653, #8125, #8152: local protocol detection
+ rlocalProtocol = /^(?:about|app|app\-storage|.+\-extension|file|res|widget):$/,
+ rnoContent = /^(?:GET|HEAD)$/,
+ rprotocol = /^\/\//,
+ rquery = /\?/,
+ rscript = /