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 &#34;caches&#34; 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 &amp; 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 &lt; 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() -&gt; 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 &amp; 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 +
+
January 2013
+ ... +
+``` + +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=''+aG.join("")+"
";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 = "
t
"; + 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>" ); + + 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>"); + + // 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 = /)<[^<]*)*<\/script>/gi, + rts = /([?&])_=[^&]*/, + rurl = /^([\w\+\.\-]+:)(?:\/\/([^\/?#:]*)(?::(\d+)|)|)/, + + // Keep a copy of the old load method + _load = jQuery.fn.load, + + /* Prefilters + * 1) They are useful to introduce custom dataTypes (see ajax/jsonp.js for an example) + * 2) These are called: + * - BEFORE asking for a transport + * - AFTER param serialization (s.data is a string if s.processData is true) + * 3) key is the dataType + * 4) the catchall symbol "*" can be used + * 5) execution will start with transport dataType and THEN continue down to "*" if needed + */ + prefilters = {}, + + /* Transports bindings + * 1) key is the dataType + * 2) the catchall symbol "*" can be used + * 3) selection will start with transport dataType and THEN go to "*" if needed + */ + transports = {}, + + // Avoid comment-prolog char sequence (#10098); must appease lint and evade compression + allTypes = ["*/"] + ["*"]; + +// #8138, IE may throw an exception when accessing +// a field from window.location if document.domain has been set +try { + ajaxLocation = location.href; +} catch( e ) { + // Use the href attribute of an A element + // since IE will modify it given document.location + ajaxLocation = document.createElement( "a" ); + ajaxLocation.href = ""; + ajaxLocation = ajaxLocation.href; +} + +// Segment location into parts +ajaxLocParts = rurl.exec( ajaxLocation.toLowerCase() ) || []; + +// Base "constructor" for jQuery.ajaxPrefilter and jQuery.ajaxTransport +function addToPrefiltersOrTransports( structure ) { + + // dataTypeExpression is optional and defaults to "*" + return function( dataTypeExpression, func ) { + + if ( typeof dataTypeExpression !== "string" ) { + func = dataTypeExpression; + dataTypeExpression = "*"; + } + + var dataType, list, placeBefore, + dataTypes = dataTypeExpression.toLowerCase().split( core_rspace ), + i = 0, + length = dataTypes.length; + + if ( jQuery.isFunction( func ) ) { + // For each dataType in the dataTypeExpression + for ( ; i < length; i++ ) { + dataType = dataTypes[ i ]; + // We control if we're asked to add before + // any existing element + placeBefore = /^\+/.test( dataType ); + if ( placeBefore ) { + dataType = dataType.substr( 1 ) || "*"; + } + list = structure[ dataType ] = structure[ dataType ] || []; + // then we add to the structure accordingly + list[ placeBefore ? "unshift" : "push" ]( func ); + } + } + }; +} + +// Base inspection function for prefilters and transports +function inspectPrefiltersOrTransports( structure, options, originalOptions, jqXHR, + dataType /* internal */, inspected /* internal */ ) { + + dataType = dataType || options.dataTypes[ 0 ]; + inspected = inspected || {}; + + inspected[ dataType ] = true; + + var selection, + list = structure[ dataType ], + i = 0, + length = list ? list.length : 0, + executeOnly = ( structure === prefilters ); + + for ( ; i < length && ( executeOnly || !selection ); i++ ) { + selection = list[ i ]( options, originalOptions, jqXHR ); + // If we got redirected to another dataType + // we try there if executing only and not done already + if ( typeof selection === "string" ) { + if ( !executeOnly || inspected[ selection ] ) { + selection = undefined; + } else { + options.dataTypes.unshift( selection ); + selection = inspectPrefiltersOrTransports( + structure, options, originalOptions, jqXHR, selection, inspected ); + } + } + } + // If we're only executing or nothing was selected + // we try the catchall dataType if not done already + if ( ( executeOnly || !selection ) && !inspected[ "*" ] ) { + selection = inspectPrefiltersOrTransports( + structure, options, originalOptions, jqXHR, "*", inspected ); + } + // unnecessary when only executing (prefilters) + // but it'll be ignored by the caller in that case + return selection; +} + +// A special extend for ajax options +// that takes "flat" options (not to be deep extended) +// Fixes #9887 +function ajaxExtend( target, src ) { + var key, deep, + flatOptions = jQuery.ajaxSettings.flatOptions || {}; + for ( key in src ) { + if ( src[ key ] !== undefined ) { + ( flatOptions[ key ] ? target : ( deep || ( deep = {} ) ) )[ key ] = src[ key ]; + } + } + if ( deep ) { + jQuery.extend( true, target, deep ); + } +} + +jQuery.fn.load = function( url, params, callback ) { + if ( typeof url !== "string" && _load ) { + return _load.apply( this, arguments ); + } + + // Don't do a request if no elements are being requested + if ( !this.length ) { + return this; + } + + var selector, type, response, + self = this, + off = url.indexOf(" "); + + if ( off >= 0 ) { + selector = url.slice( off, url.length ); + url = url.slice( 0, off ); + } + + // If it's a function + if ( jQuery.isFunction( params ) ) { + + // We assume that it's the callback + callback = params; + params = undefined; + + // Otherwise, build a param string + } else if ( params && typeof params === "object" ) { + type = "POST"; + } + + // Request the remote document + jQuery.ajax({ + url: url, + + // if "type" variable is undefined, then "GET" method will be used + type: type, + dataType: "html", + data: params, + complete: function( jqXHR, status ) { + if ( callback ) { + self.each( callback, response || [ jqXHR.responseText, status, jqXHR ] ); + } + } + }).done(function( responseText ) { + + // Save response for use in complete callback + response = arguments; + + // See if a selector was specified + self.html( selector ? + + // Create a dummy div to hold the results + jQuery("
") + + // inject the contents of the document in, removing the scripts + // to avoid any 'Permission Denied' errors in IE + .append( responseText.replace( rscript, "" ) ) + + // Locate the specified elements + .find( selector ) : + + // If not, just inject the full result + responseText ); + + }); + + return this; +}; + +// Attach a bunch of functions for handling common AJAX events +jQuery.each( "ajaxStart ajaxStop ajaxComplete ajaxError ajaxSuccess ajaxSend".split( " " ), function( i, o ){ + jQuery.fn[ o ] = function( f ){ + return this.on( o, f ); + }; +}); + +jQuery.each( [ "get", "post" ], function( i, method ) { + jQuery[ method ] = function( url, data, callback, type ) { + // shift arguments if data argument was omitted + if ( jQuery.isFunction( data ) ) { + type = type || callback; + callback = data; + data = undefined; + } + + return jQuery.ajax({ + type: method, + url: url, + data: data, + success: callback, + dataType: type + }); + }; +}); + +jQuery.extend({ + + getScript: function( url, callback ) { + return jQuery.get( url, undefined, callback, "script" ); + }, + + getJSON: function( url, data, callback ) { + return jQuery.get( url, data, callback, "json" ); + }, + + // Creates a full fledged settings object into target + // with both ajaxSettings and settings fields. + // If target is omitted, writes into ajaxSettings. + ajaxSetup: function( target, settings ) { + if ( settings ) { + // Building a settings object + ajaxExtend( target, jQuery.ajaxSettings ); + } else { + // Extending ajaxSettings + settings = target; + target = jQuery.ajaxSettings; + } + ajaxExtend( target, settings ); + return target; + }, + + ajaxSettings: { + url: ajaxLocation, + isLocal: rlocalProtocol.test( ajaxLocParts[ 1 ] ), + global: true, + type: "GET", + contentType: "application/x-www-form-urlencoded; charset=UTF-8", + processData: true, + async: true, + /* + timeout: 0, + data: null, + dataType: null, + username: null, + password: null, + cache: null, + throws: false, + traditional: false, + headers: {}, + */ + + accepts: { + xml: "application/xml, text/xml", + html: "text/html", + text: "text/plain", + json: "application/json, text/javascript", + "*": allTypes + }, + + contents: { + xml: /xml/, + html: /html/, + json: /json/ + }, + + responseFields: { + xml: "responseXML", + text: "responseText" + }, + + // List of data converters + // 1) key format is "source_type destination_type" (a single space in-between) + // 2) the catchall symbol "*" can be used for source_type + converters: { + + // Convert anything to text + "* text": window.String, + + // Text to html (true = no transformation) + "text html": true, + + // Evaluate text as a json expression + "text json": jQuery.parseJSON, + + // Parse text as xml + "text xml": jQuery.parseXML + }, + + // For options that shouldn't be deep extended: + // you can add your own custom options here if + // and when you create one that shouldn't be + // deep extended (see ajaxExtend) + flatOptions: { + context: true, + url: true + } + }, + + ajaxPrefilter: addToPrefiltersOrTransports( prefilters ), + ajaxTransport: addToPrefiltersOrTransports( transports ), + + // Main method + ajax: function( url, options ) { + + // If url is an object, simulate pre-1.5 signature + if ( typeof url === "object" ) { + options = url; + url = undefined; + } + + // Force options to be an object + options = options || {}; + + var // ifModified key + ifModifiedKey, + // Response headers + responseHeadersString, + responseHeaders, + // transport + transport, + // timeout handle + timeoutTimer, + // Cross-domain detection vars + parts, + // To know if global events are to be dispatched + fireGlobals, + // Loop variable + i, + // Create the final options object + s = jQuery.ajaxSetup( {}, options ), + // Callbacks context + callbackContext = s.context || s, + // Context for global events + // It's the callbackContext if one was provided in the options + // and if it's a DOM node or a jQuery collection + globalEventContext = callbackContext !== s && + ( callbackContext.nodeType || callbackContext instanceof jQuery ) ? + jQuery( callbackContext ) : jQuery.event, + // Deferreds + deferred = jQuery.Deferred(), + completeDeferred = jQuery.Callbacks( "once memory" ), + // Status-dependent callbacks + statusCode = s.statusCode || {}, + // Headers (they are sent all at once) + requestHeaders = {}, + requestHeadersNames = {}, + // The jqXHR state + state = 0, + // Default abort message + strAbort = "canceled", + // Fake xhr + jqXHR = { + + readyState: 0, + + // Caches the header + setRequestHeader: function( name, value ) { + if ( !state ) { + var lname = name.toLowerCase(); + name = requestHeadersNames[ lname ] = requestHeadersNames[ lname ] || name; + requestHeaders[ name ] = value; + } + return this; + }, + + // Raw string + getAllResponseHeaders: function() { + return state === 2 ? responseHeadersString : null; + }, + + // Builds headers hashtable if needed + getResponseHeader: function( key ) { + var match; + if ( state === 2 ) { + if ( !responseHeaders ) { + responseHeaders = {}; + while( ( match = rheaders.exec( responseHeadersString ) ) ) { + responseHeaders[ match[1].toLowerCase() ] = match[ 2 ]; + } + } + match = responseHeaders[ key.toLowerCase() ]; + } + return match === undefined ? null : match; + }, + + // Overrides response content-type header + overrideMimeType: function( type ) { + if ( !state ) { + s.mimeType = type; + } + return this; + }, + + // Cancel the request + abort: function( statusText ) { + statusText = statusText || strAbort; + if ( transport ) { + transport.abort( statusText ); + } + done( 0, statusText ); + return this; + } + }; + + // Callback for when everything is done + // It is defined here because jslint complains if it is declared + // at the end of the function (which would be more logical and readable) + function done( status, nativeStatusText, responses, headers ) { + var isSuccess, success, error, response, modified, + statusText = nativeStatusText; + + // Called once + if ( state === 2 ) { + return; + } + + // State is "done" now + state = 2; + + // Clear timeout if it exists + if ( timeoutTimer ) { + clearTimeout( timeoutTimer ); + } + + // Dereference transport for early garbage collection + // (no matter how long the jqXHR object will be used) + transport = undefined; + + // Cache response headers + responseHeadersString = headers || ""; + + // Set readyState + jqXHR.readyState = status > 0 ? 4 : 0; + + // Get response data + if ( responses ) { + response = ajaxHandleResponses( s, jqXHR, responses ); + } + + // If successful, handle type chaining + if ( status >= 200 && status < 300 || status === 304 ) { + + // Set the If-Modified-Since and/or If-None-Match header, if in ifModified mode. + if ( s.ifModified ) { + + modified = jqXHR.getResponseHeader("Last-Modified"); + if ( modified ) { + jQuery.lastModified[ ifModifiedKey ] = modified; + } + modified = jqXHR.getResponseHeader("Etag"); + if ( modified ) { + jQuery.etag[ ifModifiedKey ] = modified; + } + } + + // If not modified + if ( status === 304 ) { + + statusText = "notmodified"; + isSuccess = true; + + // If we have data + } else { + + isSuccess = ajaxConvert( s, response ); + statusText = isSuccess.state; + success = isSuccess.data; + error = isSuccess.error; + isSuccess = !error; + } + } else { + // We extract error from statusText + // then normalize statusText and status for non-aborts + error = statusText; + if ( !statusText || status ) { + statusText = "error"; + if ( status < 0 ) { + status = 0; + } + } + } + + // Set data for the fake xhr object + jqXHR.status = status; + jqXHR.statusText = ( nativeStatusText || statusText ) + ""; + + // Success/Error + if ( isSuccess ) { + deferred.resolveWith( callbackContext, [ success, statusText, jqXHR ] ); + } else { + deferred.rejectWith( callbackContext, [ jqXHR, statusText, error ] ); + } + + // Status-dependent callbacks + jqXHR.statusCode( statusCode ); + statusCode = undefined; + + if ( fireGlobals ) { + globalEventContext.trigger( "ajax" + ( isSuccess ? "Success" : "Error" ), + [ jqXHR, s, isSuccess ? success : error ] ); + } + + // Complete + completeDeferred.fireWith( callbackContext, [ jqXHR, statusText ] ); + + if ( fireGlobals ) { + globalEventContext.trigger( "ajaxComplete", [ jqXHR, s ] ); + // Handle the global AJAX counter + if ( !( --jQuery.active ) ) { + jQuery.event.trigger( "ajaxStop" ); + } + } + } + + // Attach deferreds + deferred.promise( jqXHR ); + jqXHR.success = jqXHR.done; + jqXHR.error = jqXHR.fail; + jqXHR.complete = completeDeferred.add; + + // Status-dependent callbacks + jqXHR.statusCode = function( map ) { + if ( map ) { + var tmp; + if ( state < 2 ) { + for ( tmp in map ) { + statusCode[ tmp ] = [ statusCode[tmp], map[tmp] ]; + } + } else { + tmp = map[ jqXHR.status ]; + jqXHR.always( tmp ); + } + } + return this; + }; + + // Remove hash character (#7531: and string promotion) + // Add protocol if not provided (#5866: IE7 issue with protocol-less urls) + // We also use the url parameter if available + s.url = ( ( url || s.url ) + "" ).replace( rhash, "" ).replace( rprotocol, ajaxLocParts[ 1 ] + "//" ); + + // Extract dataTypes list + s.dataTypes = jQuery.trim( s.dataType || "*" ).toLowerCase().split( core_rspace ); + + // A cross-domain request is in order when we have a protocol:host:port mismatch + if ( s.crossDomain == null ) { + parts = rurl.exec( s.url.toLowerCase() ); + s.crossDomain = !!( parts && + ( parts[ 1 ] !== ajaxLocParts[ 1 ] || parts[ 2 ] !== ajaxLocParts[ 2 ] || + ( parts[ 3 ] || ( parts[ 1 ] === "http:" ? 80 : 443 ) ) != + ( ajaxLocParts[ 3 ] || ( ajaxLocParts[ 1 ] === "http:" ? 80 : 443 ) ) ) + ); + } + + // Convert data if not already a string + if ( s.data && s.processData && typeof s.data !== "string" ) { + s.data = jQuery.param( s.data, s.traditional ); + } + + // Apply prefilters + inspectPrefiltersOrTransports( prefilters, s, options, jqXHR ); + + // If request was aborted inside a prefilter, stop there + if ( state === 2 ) { + return jqXHR; + } + + // We can fire global events as of now if asked to + fireGlobals = s.global; + + // Uppercase the type + s.type = s.type.toUpperCase(); + + // Determine if request has content + s.hasContent = !rnoContent.test( s.type ); + + // Watch for a new set of requests + if ( fireGlobals && jQuery.active++ === 0 ) { + jQuery.event.trigger( "ajaxStart" ); + } + + // More options handling for requests with no content + if ( !s.hasContent ) { + + // If data is available, append data to url + if ( s.data ) { + s.url += ( rquery.test( s.url ) ? "&" : "?" ) + s.data; + // #9682: remove data so that it's not used in an eventual retry + delete s.data; + } + + // Get ifModifiedKey before adding the anti-cache parameter + ifModifiedKey = s.url; + + // Add anti-cache in url if needed + if ( s.cache === false ) { + + var ts = jQuery.now(), + // try replacing _= if it is there + ret = s.url.replace( rts, "$1_=" + ts ); + + // if nothing was replaced, add timestamp to the end + s.url = ret + ( ( ret === s.url ) ? ( rquery.test( s.url ) ? "&" : "?" ) + "_=" + ts : "" ); + } + } + + // Set the correct header, if data is being sent + if ( s.data && s.hasContent && s.contentType !== false || options.contentType ) { + jqXHR.setRequestHeader( "Content-Type", s.contentType ); + } + + // Set the If-Modified-Since and/or If-None-Match header, if in ifModified mode. + if ( s.ifModified ) { + ifModifiedKey = ifModifiedKey || s.url; + if ( jQuery.lastModified[ ifModifiedKey ] ) { + jqXHR.setRequestHeader( "If-Modified-Since", jQuery.lastModified[ ifModifiedKey ] ); + } + if ( jQuery.etag[ ifModifiedKey ] ) { + jqXHR.setRequestHeader( "If-None-Match", jQuery.etag[ ifModifiedKey ] ); + } + } + + // Set the Accepts header for the server, depending on the dataType + jqXHR.setRequestHeader( + "Accept", + s.dataTypes[ 0 ] && s.accepts[ s.dataTypes[0] ] ? + s.accepts[ s.dataTypes[0] ] + ( s.dataTypes[ 0 ] !== "*" ? ", " + allTypes + "; q=0.01" : "" ) : + s.accepts[ "*" ] + ); + + // Check for headers option + for ( i in s.headers ) { + jqXHR.setRequestHeader( i, s.headers[ i ] ); + } + + // Allow custom headers/mimetypes and early abort + if ( s.beforeSend && ( s.beforeSend.call( callbackContext, jqXHR, s ) === false || state === 2 ) ) { + // Abort if not done already and return + return jqXHR.abort(); + + } + + // aborting is no longer a cancellation + strAbort = "abort"; + + // Install callbacks on deferreds + for ( i in { success: 1, error: 1, complete: 1 } ) { + jqXHR[ i ]( s[ i ] ); + } + + // Get transport + transport = inspectPrefiltersOrTransports( transports, s, options, jqXHR ); + + // If no transport, we auto-abort + if ( !transport ) { + done( -1, "No Transport" ); + } else { + jqXHR.readyState = 1; + // Send global event + if ( fireGlobals ) { + globalEventContext.trigger( "ajaxSend", [ jqXHR, s ] ); + } + // Timeout + if ( s.async && s.timeout > 0 ) { + timeoutTimer = setTimeout( function(){ + jqXHR.abort( "timeout" ); + }, s.timeout ); + } + + try { + state = 1; + transport.send( requestHeaders, done ); + } catch (e) { + // Propagate exception as error if not done + if ( state < 2 ) { + done( -1, e ); + // Simply rethrow otherwise + } else { + throw e; + } + } + } + + return jqXHR; + }, + + // Counter for holding the number of active queries + active: 0, + + // Last-Modified header cache for next request + lastModified: {}, + etag: {} + +}); + +/* Handles responses to an ajax request: + * - sets all responseXXX fields accordingly + * - finds the right dataType (mediates between content-type and expected dataType) + * - returns the corresponding response + */ +function ajaxHandleResponses( s, jqXHR, responses ) { + + var ct, type, finalDataType, firstDataType, + contents = s.contents, + dataTypes = s.dataTypes, + responseFields = s.responseFields; + + // Fill responseXXX fields + for ( type in responseFields ) { + if ( type in responses ) { + jqXHR[ responseFields[type] ] = responses[ type ]; + } + } + + // Remove auto dataType and get content-type in the process + while( dataTypes[ 0 ] === "*" ) { + dataTypes.shift(); + if ( ct === undefined ) { + ct = s.mimeType || jqXHR.getResponseHeader( "content-type" ); + } + } + + // Check if we're dealing with a known content-type + if ( ct ) { + for ( type in contents ) { + if ( contents[ type ] && contents[ type ].test( ct ) ) { + dataTypes.unshift( type ); + break; + } + } + } + + // Check to see if we have a response for the expected dataType + if ( dataTypes[ 0 ] in responses ) { + finalDataType = dataTypes[ 0 ]; + } else { + // Try convertible dataTypes + for ( type in responses ) { + if ( !dataTypes[ 0 ] || s.converters[ type + " " + dataTypes[0] ] ) { + finalDataType = type; + break; + } + if ( !firstDataType ) { + firstDataType = type; + } + } + // Or just use first one + finalDataType = finalDataType || firstDataType; + } + + // If we found a dataType + // We add the dataType to the list if needed + // and return the corresponding response + if ( finalDataType ) { + if ( finalDataType !== dataTypes[ 0 ] ) { + dataTypes.unshift( finalDataType ); + } + return responses[ finalDataType ]; + } +} + +// Chain conversions given the request and the original response +function ajaxConvert( s, response ) { + + var conv, conv2, current, tmp, + // Work with a copy of dataTypes in case we need to modify it for conversion + dataTypes = s.dataTypes.slice(), + prev = dataTypes[ 0 ], + converters = {}, + i = 0; + + // Apply the dataFilter if provided + if ( s.dataFilter ) { + response = s.dataFilter( response, s.dataType ); + } + + // Create converters map with lowercased keys + if ( dataTypes[ 1 ] ) { + for ( conv in s.converters ) { + converters[ conv.toLowerCase() ] = s.converters[ conv ]; + } + } + + // Convert to each sequential dataType, tolerating list modification + for ( ; (current = dataTypes[++i]); ) { + + // There's only work to do if current dataType is non-auto + if ( current !== "*" ) { + + // Convert response if prev dataType is non-auto and differs from current + if ( prev !== "*" && prev !== current ) { + + // Seek a direct converter + conv = converters[ prev + " " + current ] || converters[ "* " + current ]; + + // If none found, seek a pair + if ( !conv ) { + for ( conv2 in converters ) { + + // If conv2 outputs current + tmp = conv2.split(" "); + if ( tmp[ 1 ] === current ) { + + // If prev can be converted to accepted input + conv = converters[ prev + " " + tmp[ 0 ] ] || + converters[ "* " + tmp[ 0 ] ]; + if ( conv ) { + // Condense equivalence converters + if ( conv === true ) { + conv = converters[ conv2 ]; + + // Otherwise, insert the intermediate dataType + } else if ( converters[ conv2 ] !== true ) { + current = tmp[ 0 ]; + dataTypes.splice( i--, 0, current ); + } + + break; + } + } + } + } + + // Apply converter (if not an equivalence) + if ( conv !== true ) { + + // Unless errors are allowed to bubble, catch and return them + if ( conv && s["throws"] ) { + response = conv( response ); + } else { + try { + response = conv( response ); + } catch ( e ) { + return { state: "parsererror", error: conv ? e : "No conversion from " + prev + " to " + current }; + } + } + } + } + + // Update prev for next iteration + prev = current; + } + } + + return { state: "success", data: response }; +} +var oldCallbacks = [], + rquestion = /\?/, + rjsonp = /(=)\?(?=&|$)|\?\?/, + nonce = jQuery.now(); + +// Default jsonp settings +jQuery.ajaxSetup({ + jsonp: "callback", + jsonpCallback: function() { + var callback = oldCallbacks.pop() || ( jQuery.expando + "_" + ( nonce++ ) ); + this[ callback ] = true; + return callback; + } +}); + +// Detect, normalize options and install callbacks for jsonp requests +jQuery.ajaxPrefilter( "json jsonp", function( s, originalSettings, jqXHR ) { + + var callbackName, overwritten, responseContainer, + data = s.data, + url = s.url, + hasCallback = s.jsonp !== false, + replaceInUrl = hasCallback && rjsonp.test( url ), + replaceInData = hasCallback && !replaceInUrl && typeof data === "string" && + !( s.contentType || "" ).indexOf("application/x-www-form-urlencoded") && + rjsonp.test( data ); + + // Handle iff the expected data type is "jsonp" or we have a parameter to set + if ( s.dataTypes[ 0 ] === "jsonp" || replaceInUrl || replaceInData ) { + + // Get callback name, remembering preexisting value associated with it + callbackName = s.jsonpCallback = jQuery.isFunction( s.jsonpCallback ) ? + s.jsonpCallback() : + s.jsonpCallback; + overwritten = window[ callbackName ]; + + // Insert callback into url or form data + if ( replaceInUrl ) { + s.url = url.replace( rjsonp, "$1" + callbackName ); + } else if ( replaceInData ) { + s.data = data.replace( rjsonp, "$1" + callbackName ); + } else if ( hasCallback ) { + s.url += ( rquestion.test( url ) ? "&" : "?" ) + s.jsonp + "=" + callbackName; + } + + // Use data converter to retrieve json after script execution + s.converters["script json"] = function() { + if ( !responseContainer ) { + jQuery.error( callbackName + " was not called" ); + } + return responseContainer[ 0 ]; + }; + + // force json dataType + s.dataTypes[ 0 ] = "json"; + + // Install callback + window[ callbackName ] = function() { + responseContainer = arguments; + }; + + // Clean-up function (fires after converters) + jqXHR.always(function() { + // Restore preexisting value + window[ callbackName ] = overwritten; + + // Save back as free + if ( s[ callbackName ] ) { + // make sure that re-using the options doesn't screw things around + s.jsonpCallback = originalSettings.jsonpCallback; + + // save the callback name for future use + oldCallbacks.push( callbackName ); + } + + // Call if it was a function and we have a response + if ( responseContainer && jQuery.isFunction( overwritten ) ) { + overwritten( responseContainer[ 0 ] ); + } + + responseContainer = overwritten = undefined; + }); + + // Delegate to script + return "script"; + } +}); +// Install script dataType +jQuery.ajaxSetup({ + accepts: { + script: "text/javascript, application/javascript, application/ecmascript, application/x-ecmascript" + }, + contents: { + script: /javascript|ecmascript/ + }, + converters: { + "text script": function( text ) { + jQuery.globalEval( text ); + return text; + } + } +}); + +// Handle cache's special case and global +jQuery.ajaxPrefilter( "script", function( s ) { + if ( s.cache === undefined ) { + s.cache = false; + } + if ( s.crossDomain ) { + s.type = "GET"; + s.global = false; + } +}); + +// Bind script tag hack transport +jQuery.ajaxTransport( "script", function(s) { + + // This transport only deals with cross domain requests + if ( s.crossDomain ) { + + var script, + head = document.head || document.getElementsByTagName( "head" )[0] || document.documentElement; + + return { + + send: function( _, callback ) { + + script = document.createElement( "script" ); + + script.async = "async"; + + if ( s.scriptCharset ) { + script.charset = s.scriptCharset; + } + + script.src = s.url; + + // Attach handlers for all browsers + script.onload = script.onreadystatechange = function( _, isAbort ) { + + if ( isAbort || !script.readyState || /loaded|complete/.test( script.readyState ) ) { + + // Handle memory leak in IE + script.onload = script.onreadystatechange = null; + + // Remove the script + if ( head && script.parentNode ) { + head.removeChild( script ); + } + + // Dereference the script + script = undefined; + + // Callback if not abort + if ( !isAbort ) { + callback( 200, "success" ); + } + } + }; + // Use insertBefore instead of appendChild to circumvent an IE6 bug. + // This arises when a base node is used (#2709 and #4378). + head.insertBefore( script, head.firstChild ); + }, + + abort: function() { + if ( script ) { + script.onload( 0, 1 ); + } + } + }; + } +}); +var xhrCallbacks, + // #5280: Internet Explorer will keep connections alive if we don't abort on unload + xhrOnUnloadAbort = window.ActiveXObject ? function() { + // Abort all pending requests + for ( var key in xhrCallbacks ) { + xhrCallbacks[ key ]( 0, 1 ); + } + } : false, + xhrId = 0; + +// Functions to create xhrs +function createStandardXHR() { + try { + return new window.XMLHttpRequest(); + } catch( e ) {} +} + +function createActiveXHR() { + try { + return new window.ActiveXObject( "Microsoft.XMLHTTP" ); + } catch( e ) {} +} + +// Create the request object +// (This is still attached to ajaxSettings for backward compatibility) +jQuery.ajaxSettings.xhr = window.ActiveXObject ? + /* Microsoft failed to properly + * implement the XMLHttpRequest in IE7 (can't request local files), + * so we use the ActiveXObject when it is available + * Additionally XMLHttpRequest can be disabled in IE7/IE8 so + * we need a fallback. + */ + function() { + return !this.isLocal && createStandardXHR() || createActiveXHR(); + } : + // For all other browsers, use the standard XMLHttpRequest object + createStandardXHR; + +// Determine support properties +(function( xhr ) { + jQuery.extend( jQuery.support, { + ajax: !!xhr, + cors: !!xhr && ( "withCredentials" in xhr ) + }); +})( jQuery.ajaxSettings.xhr() ); + +// Create transport if the browser can provide an xhr +if ( jQuery.support.ajax ) { + + jQuery.ajaxTransport(function( s ) { + // Cross domain only allowed if supported through XMLHttpRequest + if ( !s.crossDomain || jQuery.support.cors ) { + + var callback; + + return { + send: function( headers, complete ) { + + // Get a new xhr + var handle, i, + xhr = s.xhr(); + + // Open the socket + // Passing null username, generates a login popup on Opera (#2865) + if ( s.username ) { + xhr.open( s.type, s.url, s.async, s.username, s.password ); + } else { + xhr.open( s.type, s.url, s.async ); + } + + // Apply custom fields if provided + if ( s.xhrFields ) { + for ( i in s.xhrFields ) { + xhr[ i ] = s.xhrFields[ i ]; + } + } + + // Override mime type if needed + if ( s.mimeType && xhr.overrideMimeType ) { + xhr.overrideMimeType( s.mimeType ); + } + + // X-Requested-With header + // For cross-domain requests, seeing as conditions for a preflight are + // akin to a jigsaw puzzle, we simply never set it to be sure. + // (it can always be set on a per-request basis or even using ajaxSetup) + // For same-domain requests, won't change header if already provided. + if ( !s.crossDomain && !headers["X-Requested-With"] ) { + headers[ "X-Requested-With" ] = "XMLHttpRequest"; + } + + // Need an extra try/catch for cross domain requests in Firefox 3 + try { + for ( i in headers ) { + xhr.setRequestHeader( i, headers[ i ] ); + } + } catch( _ ) {} + + // Do send the request + // This may raise an exception which is actually + // handled in jQuery.ajax (so no try/catch here) + xhr.send( ( s.hasContent && s.data ) || null ); + + // Listener + callback = function( _, isAbort ) { + + var status, + statusText, + responseHeaders, + responses, + xml; + + // Firefox throws exceptions when accessing properties + // of an xhr when a network error occurred + // http://helpful.knobs-dials.com/index.php/Component_returned_failure_code:_0x80040111_(NS_ERROR_NOT_AVAILABLE) + try { + + // Was never called and is aborted or complete + if ( callback && ( isAbort || xhr.readyState === 4 ) ) { + + // Only called once + callback = undefined; + + // Do not keep as active anymore + if ( handle ) { + xhr.onreadystatechange = jQuery.noop; + if ( xhrOnUnloadAbort ) { + delete xhrCallbacks[ handle ]; + } + } + + // If it's an abort + if ( isAbort ) { + // Abort it manually if needed + if ( xhr.readyState !== 4 ) { + xhr.abort(); + } + } else { + status = xhr.status; + responseHeaders = xhr.getAllResponseHeaders(); + responses = {}; + xml = xhr.responseXML; + + // Construct response list + if ( xml && xml.documentElement /* #4958 */ ) { + responses.xml = xml; + } + + // When requesting binary data, IE6-9 will throw an exception + // on any attempt to access responseText (#11426) + try { + responses.text = xhr.responseText; + } catch( e ) { + } + + // Firefox throws an exception when accessing + // statusText for faulty cross-domain requests + try { + statusText = xhr.statusText; + } catch( e ) { + // We normalize with Webkit giving an empty statusText + statusText = ""; + } + + // Filter status for non standard behaviors + + // If the request is local and we have data: assume a success + // (success with no data won't get notified, that's the best we + // can do given current implementations) + if ( !status && s.isLocal && !s.crossDomain ) { + status = responses.text ? 200 : 404; + // IE - #1450: sometimes returns 1223 when it should be 204 + } else if ( status === 1223 ) { + status = 204; + } + } + } + } catch( firefoxAccessException ) { + if ( !isAbort ) { + complete( -1, firefoxAccessException ); + } + } + + // Call complete if needed + if ( responses ) { + complete( status, statusText, responses, responseHeaders ); + } + }; + + if ( !s.async ) { + // if we're in sync mode we fire the callback + callback(); + } else if ( xhr.readyState === 4 ) { + // (IE6 & IE7) if it's in cache and has been + // retrieved directly we need to fire the callback + setTimeout( callback, 0 ); + } else { + handle = ++xhrId; + if ( xhrOnUnloadAbort ) { + // Create the active xhrs callbacks list if needed + // and attach the unload handler + if ( !xhrCallbacks ) { + xhrCallbacks = {}; + jQuery( window ).unload( xhrOnUnloadAbort ); + } + // Add to list of active xhrs callbacks + xhrCallbacks[ handle ] = callback; + } + xhr.onreadystatechange = callback; + } + }, + + abort: function() { + if ( callback ) { + callback(0,1); + } + } + }; + } + }); +} +var fxNow, timerId, + rfxtypes = /^(?:toggle|show|hide)$/, + rfxnum = new RegExp( "^(?:([-+])=|)(" + core_pnum + ")([a-z%]*)$", "i" ), + rrun = /queueHooks$/, + animationPrefilters = [ defaultPrefilter ], + tweeners = { + "*": [function( prop, value ) { + var end, unit, + tween = this.createTween( prop, value ), + parts = rfxnum.exec( value ), + target = tween.cur(), + start = +target || 0, + scale = 1, + maxIterations = 20; + + if ( parts ) { + end = +parts[2]; + unit = parts[3] || ( jQuery.cssNumber[ prop ] ? "" : "px" ); + + // We need to compute starting value + if ( unit !== "px" && start ) { + // Iteratively approximate from a nonzero starting point + // Prefer the current property, because this process will be trivial if it uses the same units + // Fallback to end or a simple constant + start = jQuery.css( tween.elem, prop, true ) || end || 1; + + do { + // If previous iteration zeroed out, double until we get *something* + // Use a string for doubling factor so we don't accidentally see scale as unchanged below + scale = scale || ".5"; + + // Adjust and apply + start = start / scale; + jQuery.style( tween.elem, prop, start + unit ); + + // Update scale, tolerating zero or NaN from tween.cur() + // And breaking the loop if scale is unchanged or perfect, or if we've just had enough + } while ( scale !== (scale = tween.cur() / target) && scale !== 1 && --maxIterations ); + } + + tween.unit = unit; + tween.start = start; + // If a +=/-= token was provided, we're doing a relative animation + tween.end = parts[1] ? start + ( parts[1] + 1 ) * end : end; + } + return tween; + }] + }; + +// Animations created synchronously will run synchronously +function createFxNow() { + setTimeout(function() { + fxNow = undefined; + }, 0 ); + return ( fxNow = jQuery.now() ); +} + +function createTweens( animation, props ) { + jQuery.each( props, function( prop, value ) { + var collection = ( tweeners[ prop ] || [] ).concat( tweeners[ "*" ] ), + index = 0, + length = collection.length; + for ( ; index < length; index++ ) { + if ( collection[ index ].call( animation, prop, value ) ) { + + // we're done with this property + return; + } + } + }); +} + +function Animation( elem, properties, options ) { + var result, + index = 0, + tweenerIndex = 0, + length = animationPrefilters.length, + deferred = jQuery.Deferred().always( function() { + // don't match elem in the :animated selector + delete tick.elem; + }), + tick = function() { + var currentTime = fxNow || createFxNow(), + remaining = Math.max( 0, animation.startTime + animation.duration - currentTime ), + // archaic crash bug won't allow us to use 1 - ( 0.5 || 0 ) (#12497) + temp = remaining / animation.duration || 0, + percent = 1 - temp, + index = 0, + length = animation.tweens.length; + + for ( ; index < length ; index++ ) { + animation.tweens[ index ].run( percent ); + } + + deferred.notifyWith( elem, [ animation, percent, remaining ]); + + if ( percent < 1 && length ) { + return remaining; + } else { + deferred.resolveWith( elem, [ animation ] ); + return false; + } + }, + animation = deferred.promise({ + elem: elem, + props: jQuery.extend( {}, properties ), + opts: jQuery.extend( true, { specialEasing: {} }, options ), + originalProperties: properties, + originalOptions: options, + startTime: fxNow || createFxNow(), + duration: options.duration, + tweens: [], + createTween: function( prop, end, easing ) { + var tween = jQuery.Tween( elem, animation.opts, prop, end, + animation.opts.specialEasing[ prop ] || animation.opts.easing ); + animation.tweens.push( tween ); + return tween; + }, + stop: function( gotoEnd ) { + var index = 0, + // if we are going to the end, we want to run all the tweens + // otherwise we skip this part + length = gotoEnd ? animation.tweens.length : 0; + + for ( ; index < length ; index++ ) { + animation.tweens[ index ].run( 1 ); + } + + // resolve when we played the last frame + // otherwise, reject + if ( gotoEnd ) { + deferred.resolveWith( elem, [ animation, gotoEnd ] ); + } else { + deferred.rejectWith( elem, [ animation, gotoEnd ] ); + } + return this; + } + }), + props = animation.props; + + propFilter( props, animation.opts.specialEasing ); + + for ( ; index < length ; index++ ) { + result = animationPrefilters[ index ].call( animation, elem, props, animation.opts ); + if ( result ) { + return result; + } + } + + createTweens( animation, props ); + + if ( jQuery.isFunction( animation.opts.start ) ) { + animation.opts.start.call( elem, animation ); + } + + jQuery.fx.timer( + jQuery.extend( tick, { + anim: animation, + queue: animation.opts.queue, + elem: elem + }) + ); + + // attach callbacks from options + return animation.progress( animation.opts.progress ) + .done( animation.opts.done, animation.opts.complete ) + .fail( animation.opts.fail ) + .always( animation.opts.always ); +} + +function propFilter( props, specialEasing ) { + var index, name, easing, value, hooks; + + // camelCase, specialEasing and expand cssHook pass + for ( index in props ) { + name = jQuery.camelCase( index ); + easing = specialEasing[ name ]; + value = props[ index ]; + if ( jQuery.isArray( value ) ) { + easing = value[ 1 ]; + value = props[ index ] = value[ 0 ]; + } + + if ( index !== name ) { + props[ name ] = value; + delete props[ index ]; + } + + hooks = jQuery.cssHooks[ name ]; + if ( hooks && "expand" in hooks ) { + value = hooks.expand( value ); + delete props[ name ]; + + // not quite $.extend, this wont overwrite keys already present. + // also - reusing 'index' from above because we have the correct "name" + for ( index in value ) { + if ( !( index in props ) ) { + props[ index ] = value[ index ]; + specialEasing[ index ] = easing; + } + } + } else { + specialEasing[ name ] = easing; + } + } +} + +jQuery.Animation = jQuery.extend( Animation, { + + tweener: function( props, callback ) { + if ( jQuery.isFunction( props ) ) { + callback = props; + props = [ "*" ]; + } else { + props = props.split(" "); + } + + var prop, + index = 0, + length = props.length; + + for ( ; index < length ; index++ ) { + prop = props[ index ]; + tweeners[ prop ] = tweeners[ prop ] || []; + tweeners[ prop ].unshift( callback ); + } + }, + + prefilter: function( callback, prepend ) { + if ( prepend ) { + animationPrefilters.unshift( callback ); + } else { + animationPrefilters.push( callback ); + } + } +}); + +function defaultPrefilter( elem, props, opts ) { + var index, prop, value, length, dataShow, toggle, tween, hooks, oldfire, + anim = this, + style = elem.style, + orig = {}, + handled = [], + hidden = elem.nodeType && isHidden( elem ); + + // handle queue: false promises + if ( !opts.queue ) { + hooks = jQuery._queueHooks( elem, "fx" ); + if ( hooks.unqueued == null ) { + hooks.unqueued = 0; + oldfire = hooks.empty.fire; + hooks.empty.fire = function() { + if ( !hooks.unqueued ) { + oldfire(); + } + }; + } + hooks.unqueued++; + + anim.always(function() { + // doing this makes sure that the complete handler will be called + // before this completes + anim.always(function() { + hooks.unqueued--; + if ( !jQuery.queue( elem, "fx" ).length ) { + hooks.empty.fire(); + } + }); + }); + } + + // height/width overflow pass + if ( elem.nodeType === 1 && ( "height" in props || "width" in props ) ) { + // Make sure that nothing sneaks out + // Record all 3 overflow attributes because IE does not + // change the overflow attribute when overflowX and + // overflowY are set to the same value + opts.overflow = [ style.overflow, style.overflowX, style.overflowY ]; + + // Set display property to inline-block for height/width + // animations on inline elements that are having width/height animated + if ( jQuery.css( elem, "display" ) === "inline" && + jQuery.css( elem, "float" ) === "none" ) { + + // inline-level elements accept inline-block; + // block-level elements need to be inline with layout + if ( !jQuery.support.inlineBlockNeedsLayout || css_defaultDisplay( elem.nodeName ) === "inline" ) { + style.display = "inline-block"; + + } else { + style.zoom = 1; + } + } + } + + if ( opts.overflow ) { + style.overflow = "hidden"; + if ( !jQuery.support.shrinkWrapBlocks ) { + anim.done(function() { + style.overflow = opts.overflow[ 0 ]; + style.overflowX = opts.overflow[ 1 ]; + style.overflowY = opts.overflow[ 2 ]; + }); + } + } + + + // show/hide pass + for ( index in props ) { + value = props[ index ]; + if ( rfxtypes.exec( value ) ) { + delete props[ index ]; + toggle = toggle || value === "toggle"; + if ( value === ( hidden ? "hide" : "show" ) ) { + continue; + } + handled.push( index ); + } + } + + length = handled.length; + if ( length ) { + dataShow = jQuery._data( elem, "fxshow" ) || jQuery._data( elem, "fxshow", {} ); + if ( "hidden" in dataShow ) { + hidden = dataShow.hidden; + } + + // store state if its toggle - enables .stop().toggle() to "reverse" + if ( toggle ) { + dataShow.hidden = !hidden; + } + if ( hidden ) { + jQuery( elem ).show(); + } else { + anim.done(function() { + jQuery( elem ).hide(); + }); + } + anim.done(function() { + var prop; + jQuery.removeData( elem, "fxshow", true ); + for ( prop in orig ) { + jQuery.style( elem, prop, orig[ prop ] ); + } + }); + for ( index = 0 ; index < length ; index++ ) { + prop = handled[ index ]; + tween = anim.createTween( prop, hidden ? dataShow[ prop ] : 0 ); + orig[ prop ] = dataShow[ prop ] || jQuery.style( elem, prop ); + + if ( !( prop in dataShow ) ) { + dataShow[ prop ] = tween.start; + if ( hidden ) { + tween.end = tween.start; + tween.start = prop === "width" || prop === "height" ? 1 : 0; + } + } + } + } +} + +function Tween( elem, options, prop, end, easing ) { + return new Tween.prototype.init( elem, options, prop, end, easing ); +} +jQuery.Tween = Tween; + +Tween.prototype = { + constructor: Tween, + init: function( elem, options, prop, end, easing, unit ) { + this.elem = elem; + this.prop = prop; + this.easing = easing || "swing"; + this.options = options; + this.start = this.now = this.cur(); + this.end = end; + this.unit = unit || ( jQuery.cssNumber[ prop ] ? "" : "px" ); + }, + cur: function() { + var hooks = Tween.propHooks[ this.prop ]; + + return hooks && hooks.get ? + hooks.get( this ) : + Tween.propHooks._default.get( this ); + }, + run: function( percent ) { + var eased, + hooks = Tween.propHooks[ this.prop ]; + + if ( this.options.duration ) { + this.pos = eased = jQuery.easing[ this.easing ]( + percent, this.options.duration * percent, 0, 1, this.options.duration + ); + } else { + this.pos = eased = percent; + } + this.now = ( this.end - this.start ) * eased + this.start; + + if ( this.options.step ) { + this.options.step.call( this.elem, this.now, this ); + } + + if ( hooks && hooks.set ) { + hooks.set( this ); + } else { + Tween.propHooks._default.set( this ); + } + return this; + } +}; + +Tween.prototype.init.prototype = Tween.prototype; + +Tween.propHooks = { + _default: { + get: function( tween ) { + var result; + + if ( tween.elem[ tween.prop ] != null && + (!tween.elem.style || tween.elem.style[ tween.prop ] == null) ) { + return tween.elem[ tween.prop ]; + } + + // passing any value as a 4th parameter to .css will automatically + // attempt a parseFloat and fallback to a string if the parse fails + // so, simple values such as "10px" are parsed to Float. + // complex values such as "rotate(1rad)" are returned as is. + result = jQuery.css( tween.elem, tween.prop, false, "" ); + // Empty strings, null, undefined and "auto" are converted to 0. + return !result || result === "auto" ? 0 : result; + }, + set: function( tween ) { + // use step hook for back compat - use cssHook if its there - use .style if its + // available and use plain properties where available + if ( jQuery.fx.step[ tween.prop ] ) { + jQuery.fx.step[ tween.prop ]( tween ); + } else if ( tween.elem.style && ( tween.elem.style[ jQuery.cssProps[ tween.prop ] ] != null || jQuery.cssHooks[ tween.prop ] ) ) { + jQuery.style( tween.elem, tween.prop, tween.now + tween.unit ); + } else { + tween.elem[ tween.prop ] = tween.now; + } + } + } +}; + +// Remove in 2.0 - this supports IE8's panic based approach +// to setting things on disconnected nodes + +Tween.propHooks.scrollTop = Tween.propHooks.scrollLeft = { + set: function( tween ) { + if ( tween.elem.nodeType && tween.elem.parentNode ) { + tween.elem[ tween.prop ] = tween.now; + } + } +}; + +jQuery.each([ "toggle", "show", "hide" ], function( i, name ) { + var cssFn = jQuery.fn[ name ]; + jQuery.fn[ name ] = function( speed, easing, callback ) { + return speed == null || typeof speed === "boolean" || + // special check for .toggle( handler, handler, ... ) + ( !i && jQuery.isFunction( speed ) && jQuery.isFunction( easing ) ) ? + cssFn.apply( this, arguments ) : + this.animate( genFx( name, true ), speed, easing, callback ); + }; +}); + +jQuery.fn.extend({ + fadeTo: function( speed, to, easing, callback ) { + + // show any hidden elements after setting opacity to 0 + return this.filter( isHidden ).css( "opacity", 0 ).show() + + // animate to the value specified + .end().animate({ opacity: to }, speed, easing, callback ); + }, + animate: function( prop, speed, easing, callback ) { + var empty = jQuery.isEmptyObject( prop ), + optall = jQuery.speed( speed, easing, callback ), + doAnimation = function() { + // Operate on a copy of prop so per-property easing won't be lost + var anim = Animation( this, jQuery.extend( {}, prop ), optall ); + + // Empty animations resolve immediately + if ( empty ) { + anim.stop( true ); + } + }; + + return empty || optall.queue === false ? + this.each( doAnimation ) : + this.queue( optall.queue, doAnimation ); + }, + stop: function( type, clearQueue, gotoEnd ) { + var stopQueue = function( hooks ) { + var stop = hooks.stop; + delete hooks.stop; + stop( gotoEnd ); + }; + + if ( typeof type !== "string" ) { + gotoEnd = clearQueue; + clearQueue = type; + type = undefined; + } + if ( clearQueue && type !== false ) { + this.queue( type || "fx", [] ); + } + + return this.each(function() { + var dequeue = true, + index = type != null && type + "queueHooks", + timers = jQuery.timers, + data = jQuery._data( this ); + + if ( index ) { + if ( data[ index ] && data[ index ].stop ) { + stopQueue( data[ index ] ); + } + } else { + for ( index in data ) { + if ( data[ index ] && data[ index ].stop && rrun.test( index ) ) { + stopQueue( data[ index ] ); + } + } + } + + for ( index = timers.length; index--; ) { + if ( timers[ index ].elem === this && (type == null || timers[ index ].queue === type) ) { + timers[ index ].anim.stop( gotoEnd ); + dequeue = false; + timers.splice( index, 1 ); + } + } + + // start the next in the queue if the last step wasn't forced + // timers currently will call their complete callbacks, which will dequeue + // but only if they were gotoEnd + if ( dequeue || !gotoEnd ) { + jQuery.dequeue( this, type ); + } + }); + } +}); + +// Generate parameters to create a standard animation +function genFx( type, includeWidth ) { + var which, + attrs = { height: type }, + i = 0; + + // if we include width, step value is 1 to do all cssExpand values, + // if we don't include width, step value is 2 to skip over Left and Right + includeWidth = includeWidth? 1 : 0; + for( ; i < 4 ; i += 2 - includeWidth ) { + which = cssExpand[ i ]; + attrs[ "margin" + which ] = attrs[ "padding" + which ] = type; + } + + if ( includeWidth ) { + attrs.opacity = attrs.width = type; + } + + return attrs; +} + +// Generate shortcuts for custom animations +jQuery.each({ + slideDown: genFx("show"), + slideUp: genFx("hide"), + slideToggle: genFx("toggle"), + fadeIn: { opacity: "show" }, + fadeOut: { opacity: "hide" }, + fadeToggle: { opacity: "toggle" } +}, function( name, props ) { + jQuery.fn[ name ] = function( speed, easing, callback ) { + return this.animate( props, speed, easing, callback ); + }; +}); + +jQuery.speed = function( speed, easing, fn ) { + var opt = speed && typeof speed === "object" ? jQuery.extend( {}, speed ) : { + complete: fn || !fn && easing || + jQuery.isFunction( speed ) && speed, + duration: speed, + easing: fn && easing || easing && !jQuery.isFunction( easing ) && easing + }; + + opt.duration = jQuery.fx.off ? 0 : typeof opt.duration === "number" ? opt.duration : + opt.duration in jQuery.fx.speeds ? jQuery.fx.speeds[ opt.duration ] : jQuery.fx.speeds._default; + + // normalize opt.queue - true/undefined/null -> "fx" + if ( opt.queue == null || opt.queue === true ) { + opt.queue = "fx"; + } + + // Queueing + opt.old = opt.complete; + + opt.complete = function() { + if ( jQuery.isFunction( opt.old ) ) { + opt.old.call( this ); + } + + if ( opt.queue ) { + jQuery.dequeue( this, opt.queue ); + } + }; + + return opt; +}; + +jQuery.easing = { + linear: function( p ) { + return p; + }, + swing: function( p ) { + return 0.5 - Math.cos( p*Math.PI ) / 2; + } +}; + +jQuery.timers = []; +jQuery.fx = Tween.prototype.init; +jQuery.fx.tick = function() { + var timer, + timers = jQuery.timers, + i = 0; + + fxNow = jQuery.now(); + + for ( ; i < timers.length; i++ ) { + timer = timers[ i ]; + // Checks the timer has not already been removed + if ( !timer() && timers[ i ] === timer ) { + timers.splice( i--, 1 ); + } + } + + if ( !timers.length ) { + jQuery.fx.stop(); + } + fxNow = undefined; +}; + +jQuery.fx.timer = function( timer ) { + if ( timer() && jQuery.timers.push( timer ) && !timerId ) { + timerId = setInterval( jQuery.fx.tick, jQuery.fx.interval ); + } +}; + +jQuery.fx.interval = 13; + +jQuery.fx.stop = function() { + clearInterval( timerId ); + timerId = null; +}; + +jQuery.fx.speeds = { + slow: 600, + fast: 200, + // Default speed + _default: 400 +}; + +// Back Compat <1.8 extension point +jQuery.fx.step = {}; + +if ( jQuery.expr && jQuery.expr.filters ) { + jQuery.expr.filters.animated = function( elem ) { + return jQuery.grep(jQuery.timers, function( fn ) { + return elem === fn.elem; + }).length; + }; +} +var rroot = /^(?:body|html)$/i; + +jQuery.fn.offset = function( options ) { + if ( arguments.length ) { + return options === undefined ? + this : + this.each(function( i ) { + jQuery.offset.setOffset( this, options, i ); + }); + } + + var docElem, body, win, clientTop, clientLeft, scrollTop, scrollLeft, + box = { top: 0, left: 0 }, + elem = this[ 0 ], + doc = elem && elem.ownerDocument; + + if ( !doc ) { + return; + } + + if ( (body = doc.body) === elem ) { + return jQuery.offset.bodyOffset( elem ); + } + + docElem = doc.documentElement; + + // Make sure it's not a disconnected DOM node + if ( !jQuery.contains( docElem, elem ) ) { + return box; + } + + // If we don't have gBCR, just use 0,0 rather than error + // BlackBerry 5, iOS 3 (original iPhone) + if ( typeof elem.getBoundingClientRect !== "undefined" ) { + box = elem.getBoundingClientRect(); + } + win = getWindow( doc ); + clientTop = docElem.clientTop || body.clientTop || 0; + clientLeft = docElem.clientLeft || body.clientLeft || 0; + scrollTop = win.pageYOffset || docElem.scrollTop; + scrollLeft = win.pageXOffset || docElem.scrollLeft; + return { + top: box.top + scrollTop - clientTop, + left: box.left + scrollLeft - clientLeft + }; +}; + +jQuery.offset = { + + bodyOffset: function( body ) { + var top = body.offsetTop, + left = body.offsetLeft; + + if ( jQuery.support.doesNotIncludeMarginInBodyOffset ) { + top += parseFloat( jQuery.css(body, "marginTop") ) || 0; + left += parseFloat( jQuery.css(body, "marginLeft") ) || 0; + } + + return { top: top, left: left }; + }, + + setOffset: function( elem, options, i ) { + var position = jQuery.css( elem, "position" ); + + // set position first, in-case top/left are set even on static elem + if ( position === "static" ) { + elem.style.position = "relative"; + } + + var curElem = jQuery( elem ), + curOffset = curElem.offset(), + curCSSTop = jQuery.css( elem, "top" ), + curCSSLeft = jQuery.css( elem, "left" ), + calculatePosition = ( position === "absolute" || position === "fixed" ) && jQuery.inArray("auto", [curCSSTop, curCSSLeft]) > -1, + props = {}, curPosition = {}, curTop, curLeft; + + // need to be able to calculate position if either top or left is auto and position is either absolute or fixed + if ( calculatePosition ) { + curPosition = curElem.position(); + curTop = curPosition.top; + curLeft = curPosition.left; + } else { + curTop = parseFloat( curCSSTop ) || 0; + curLeft = parseFloat( curCSSLeft ) || 0; + } + + if ( jQuery.isFunction( options ) ) { + options = options.call( elem, i, curOffset ); + } + + if ( options.top != null ) { + props.top = ( options.top - curOffset.top ) + curTop; + } + if ( options.left != null ) { + props.left = ( options.left - curOffset.left ) + curLeft; + } + + if ( "using" in options ) { + options.using.call( elem, props ); + } else { + curElem.css( props ); + } + } +}; + + +jQuery.fn.extend({ + + position: function() { + if ( !this[0] ) { + return; + } + + var elem = this[0], + + // Get *real* offsetParent + offsetParent = this.offsetParent(), + + // Get correct offsets + offset = this.offset(), + parentOffset = rroot.test(offsetParent[0].nodeName) ? { top: 0, left: 0 } : offsetParent.offset(); + + // Subtract element margins + // note: when an element has margin: auto the offsetLeft and marginLeft + // are the same in Safari causing offset.left to incorrectly be 0 + offset.top -= parseFloat( jQuery.css(elem, "marginTop") ) || 0; + offset.left -= parseFloat( jQuery.css(elem, "marginLeft") ) || 0; + + // Add offsetParent borders + parentOffset.top += parseFloat( jQuery.css(offsetParent[0], "borderTopWidth") ) || 0; + parentOffset.left += parseFloat( jQuery.css(offsetParent[0], "borderLeftWidth") ) || 0; + + // Subtract the two offsets + return { + top: offset.top - parentOffset.top, + left: offset.left - parentOffset.left + }; + }, + + offsetParent: function() { + return this.map(function() { + var offsetParent = this.offsetParent || document.body; + while ( offsetParent && (!rroot.test(offsetParent.nodeName) && jQuery.css(offsetParent, "position") === "static") ) { + offsetParent = offsetParent.offsetParent; + } + return offsetParent || document.body; + }); + } +}); + + +// Create scrollLeft and scrollTop methods +jQuery.each( {scrollLeft: "pageXOffset", scrollTop: "pageYOffset"}, function( method, prop ) { + var top = /Y/.test( prop ); + + jQuery.fn[ method ] = function( val ) { + return jQuery.access( this, function( elem, method, val ) { + var win = getWindow( elem ); + + if ( val === undefined ) { + return win ? (prop in win) ? win[ prop ] : + win.document.documentElement[ method ] : + elem[ method ]; + } + + if ( win ) { + win.scrollTo( + !top ? val : jQuery( win ).scrollLeft(), + top ? val : jQuery( win ).scrollTop() + ); + + } else { + elem[ method ] = val; + } + }, method, val, arguments.length, null ); + }; +}); + +function getWindow( elem ) { + return jQuery.isWindow( elem ) ? + elem : + elem.nodeType === 9 ? + elem.defaultView || elem.parentWindow : + false; +} +// Create innerHeight, innerWidth, height, width, outerHeight and outerWidth methods +jQuery.each( { Height: "height", Width: "width" }, function( name, type ) { + jQuery.each( { padding: "inner" + name, content: type, "": "outer" + name }, function( defaultExtra, funcName ) { + // margin is only for outerHeight, outerWidth + jQuery.fn[ funcName ] = function( margin, value ) { + var chainable = arguments.length && ( defaultExtra || typeof margin !== "boolean" ), + extra = defaultExtra || ( margin === true || value === true ? "margin" : "border" ); + + return jQuery.access( this, function( elem, type, value ) { + var doc; + + if ( jQuery.isWindow( elem ) ) { + // As of 5/8/2012 this will yield incorrect results for Mobile Safari, but there + // isn't a whole lot we can do. See pull request at this URL for discussion: + // https://github.com/jquery/jquery/pull/764 + return elem.document.documentElement[ "client" + name ]; + } + + // Get document width or height + if ( elem.nodeType === 9 ) { + doc = elem.documentElement; + + // Either scroll[Width/Height] or offset[Width/Height] or client[Width/Height], whichever is greatest + // unfortunately, this causes bug #3838 in IE6/8 only, but there is currently no good, small way to fix it. + return Math.max( + elem.body[ "scroll" + name ], doc[ "scroll" + name ], + elem.body[ "offset" + name ], doc[ "offset" + name ], + doc[ "client" + name ] + ); + } + + return value === undefined ? + // Get width or height on the element, requesting but not forcing parseFloat + jQuery.css( elem, type, value, extra ) : + + // Set width or height on the element + jQuery.style( elem, type, value, extra ); + }, type, chainable ? margin : undefined, chainable, null ); + }; + }); +}); +// Expose jQuery to the global object +window.jQuery = window.$ = jQuery; + +// Expose jQuery as an AMD module, but only for AMD loaders that +// understand the issues with loading multiple versions of jQuery +// in a page that all might call define(). The loader will indicate +// they have special allowances for multiple jQuery versions by +// specifying define.amd.jQuery = true. Register as a named module, +// since jQuery can be concatenated with other files that may use define, +// but not use a proper concatenation script that understands anonymous +// AMD modules. A named AMD is safest and most robust way to register. +// Lowercase jquery is used because AMD module names are derived from +// file names, and jQuery is normally delivered in a lowercase file name. +// Do this after creating the global so that if an AMD module wants to call +// noConflict to hide this version of jQuery, it will work. +if ( typeof define === "function" && define.amd && define.amd.jQuery ) { + define( "jquery", [], function () { return jQuery; } ); +} + +})( window ); diff --git a/djangoproject/static/js/lib/jquery-flot/package.json b/djangoproject/static/js/lib/jquery-flot/package.json new file mode 100644 index 000000000..deb965177 --- /dev/null +++ b/djangoproject/static/js/lib/jquery-flot/package.json @@ -0,0 +1,11 @@ +{ + "name": "Flot", + "version": "0.8.3", + "main": "jquery.flot.js", + "scripts": { + "test": "make test" + }, + "devDependencies": { + "jshint": "0.9.1" + } +} diff --git a/djangoproject/static/js/main.js b/djangoproject/static/js/main.js index 8613d82a5..266879527 100644 --- a/djangoproject/static/js/main.js +++ b/djangoproject/static/js/main.js @@ -67,4 +67,11 @@ define(function(){ } require(mods); + + if (document.getElementById('dashboard')) { + require(['dashboard/index']); + } else if (document.getElementById('dashboard-detail')) { + require(['dashboard/detail']); + } + }); diff --git a/djangoproject/static/scss/_dashboard.scss b/djangoproject/static/scss/_dashboard.scss new file mode 100644 index 000000000..dfc1a88a7 --- /dev/null +++ b/djangoproject/static/scss/_dashboard.scss @@ -0,0 +1,73 @@ +#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 $gray-line; + @include border-radius(4px); + display: inline-block; + position: relative; + + h3 { + padding: 10px; + margin: 0 -20px; + @include font-size(18); + font-weight: 200; + display: block; + text-align: left; + color: $green-very-light; + background: $green-medium; + a { + color: $green-very-light; + } + } + + .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; + @include sans-serif(); + z-index: 10; + pointer-events:none; + + a { + text-decoration: none; + z-index: 10; + } + .timestamp { + font-size: 0.16em; + line-height: 1em; + } + } + + .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/scss/output.scss b/djangoproject/static/scss/output.scss index df28fcfa6..61a64ba3d 100755 --- a/djangoproject/static/scss/output.scss +++ b/djangoproject/static/scss/output.scss @@ -6,3 +6,4 @@ @import "pygments"; // code highlighting @import "styleguide"; // code highlighting @import "print"; // print styles +@import "dashboard"; diff --git a/djangoproject/templates/base.html b/djangoproject/templates/base.html index 2a3ebb3f9..60963c8c8 100644 --- a/djangoproject/templates/base.html +++ b/djangoproject/templates/base.html @@ -88,6 +88,7 @@ 'jquery': [], 'jquery.inview': ["jquery"], 'jquery.payment': ["jquery"], + 'jquery.flot': ["jquery"], 'stripe': { exports: 'Stripe' } @@ -97,6 +98,7 @@ "jquery.inview": extless("{% static 'js/lib/jquery.inview/jquery.inview.min.js' %}"), "jquery.payment": extless("{% static 'js/lib/jquery.payment/lib/jquery.payment.js' %}"), "modernizr-retina-test": extless("{% static 'js/lib/modernizr-retina-test/modernizr-retina-test.js' %}"), + "jquery.flot": extless("{% static 'js/lib/jquery-flot/jquery.flot.min.js' %}"), "mod/floating-warning": extless("{% static 'js/mod/floating-warning.js' %}"), "mod/list-collapsing": extless("{% static 'js/mod/list-collapsing.js' %}"), "mod/list-feature": extless("{% static 'js/mod/list-feature.js' %}"), diff --git a/djangoproject/templates/base_dashboard.html b/djangoproject/templates/base_dashboard.html new file mode 100644 index 000000000..be90120f6 --- /dev/null +++ b/djangoproject/templates/base_dashboard.html @@ -0,0 +1,8 @@ +{% extends "base.html" %} +{% load i18n %} + +{% block sectionid %}dashboard{% endblock %} + +{% block title %}{% trans 'Dashboard' %}{% endblock %} +{% block layout_class %}full-width{% endblock %} +{% block header %}

{% trans 'Django development dashboard' %}

{% endblock %} diff --git a/djangoproject/templates/dashboard/detail.html b/djangoproject/templates/dashboard/detail.html new file mode 100644 index 000000000..9b90f4a7e --- /dev/null +++ b/djangoproject/templates/dashboard/detail.html @@ -0,0 +1,10 @@ +{% extends "base_dashboard.html" %} + +{% block title %}{{ metric }} - {{ block.super }}{% endblock %} + +{% block sectionid %}dashboard-detail{% endblock %} + +{% block content %} +

{{ metric }}

+
+{% endblock %} diff --git a/djangoproject/templates/dashboard/index.html b/djangoproject/templates/dashboard/index.html new file mode 100644 index 000000000..ef8a59f1b --- /dev/null +++ b/djangoproject/templates/dashboard/index.html @@ -0,0 +1,23 @@ +{% extends "base_dashboard.html" %} +{% load i18n %} + +{% block content %} + {% for report in data %} + {% ifchanged report.metric.category %} + {% if report.metric.category %}

{{ report.metric.category }}

{% endif %} + {% endifchanged %} +
+

{{ report.metric.name }}

+ + {% if report.metric.show_sparkline %} +
+ {% endif %} +
+ {% endfor %} +

+ {% blocktrans with timestamp=data.0.latest.timestamp|timesince %}Updated {{ timestamp }} ago.{% endblocktrans %} +

+{% endblock %}