diff --git a/Vendorfile b/Vendorfile
index 02d9fdd20..e67cd169d 100644
--- a/Vendorfile
+++ b/Vendorfile
@@ -37,4 +37,14 @@ folder 'vendor/assets' do
file 'ohauth.js'
end
end
+
+ folder 'iD' do
+ from 'git://github.com/systemed/iD', :branch => 'embed' do
+ folder 'iD/img', 'dist/img'
+ file 'iD.css.erb', 'dist/iD.css' do |path|
+ rewrite(path) { |content| content.gsub(/url\('?(img\/[^')]+)'?\)/, 'url(<%= asset_path("iD/\1") %>)') }
+ end
+ file 'iD.js', 'dist/iD.js'
+ end
+ end
end
diff --git a/app/assets/stylesheets/common.css.scss b/app/assets/stylesheets/common.css.scss
index b2a23745e..d94f51114 100644
--- a/app/assets/stylesheets/common.css.scss
+++ b/app/assets/stylesheets/common.css.scss
@@ -1784,3 +1784,11 @@ a.button.submit {
text-align: right;
}
}
+
+/*
+ * Rules for the iD editor
+ */
+.id-embed {
+ width: 100%;
+ height: 100%;
+}
diff --git a/app/controllers/site_controller.rb b/app/controllers/site_controller.rb
index 1ea3f7cb0..744b65399 100644
--- a/app/controllers/site_controller.rb
+++ b/app/controllers/site_controller.rb
@@ -65,4 +65,8 @@ class SiteController < ApplicationController
def preview
render :text => RichText.new(params[:format], params[:text]).to_html
end
+
+ def id_iframe
+ render "id_iframe", :layout => false
+ end
end
diff --git a/app/views/site/_id.html.erb b/app/views/site/_id.html.erb
new file mode 100644
index 000000000..680c45cde
--- /dev/null
+++ b/app/views/site/_id.html.erb
@@ -0,0 +1,34 @@
+<% if defined? ID_KEY %>
+
+<% token = @user.access_token(ID_KEY) %>
+
+<% else%>
+
+<% end %>
+
+
diff --git a/app/views/site/id_iframe.html.erb b/app/views/site/id_iframe.html.erb
new file mode 100644
index 000000000..0422446b3
--- /dev/null
+++ b/app/views/site/id_iframe.html.erb
@@ -0,0 +1,37 @@
+
+
+
+
+ <%= stylesheet_link_tag 'iD' %>
+
+ <%= javascript_include_tag 'iD' %>
+
+
+
+
+
+
+
diff --git a/config/environments/production.rb b/config/environments/production.rb
index d7689ca2b..6486be003 100644
--- a/config/environments/production.rb
+++ b/config/environments/production.rb
@@ -62,6 +62,7 @@ OpenStreetMap::Application.configure do
config.assets.precompile += %w( large-rtl.css small-rtl.css print-rtl.css )
config.assets.precompile += %w( browse.css leaflet-all.css leaflet.ie.css )
config.assets.precompile += %w( embed.js embed.css )
+ config.assets.precompile += %w( iD.js iD.css )
# Disable delivery errors, bad email addresses will be ignored
# config.action_mailer.raise_delivery_errors = false
diff --git a/config/example.application.yml b/config/example.application.yml
index 30f367f1c..9a6d83aba 100644
--- a/config/example.application.yml
+++ b/config/example.application.yml
@@ -78,6 +78,8 @@ defaults: &defaults
#potlatch2_key: ""
# OAuth consumer key for the web site
#oauth_key: ""
+ # OAuth consumer key for iD
+ #id_key: ""
# Whether to require users to view the CTs before continuing to edit...
require_terms_seen: false
# Whether to require users to agree to the CTs before editing
diff --git a/config/locales/en.yml b/config/locales/en.yml
index 67ef5d09b..0186d446d 100644
--- a/config/locales/en.yml
+++ b/config/locales/en.yml
@@ -85,6 +85,9 @@ en:
potlatch:
name: "Potlatch 1"
description: "Potlatch 1 (in-browser editor)"
+ id:
+ name: "iD"
+ description: "iD (in-browser editor)"
potlatch2:
name: "Potlatch 2"
description: "Potlatch 2 (in-browser editor)"
@@ -1312,6 +1315,7 @@ en:
potlatch_unsaved_changes: "You have unsaved changes. (To save in Potlatch, you should deselect the current way or point, if editing in live mode, or click save if you have a save button.)"
potlatch2_not_configured: "Potlatch 2 has not been configured - please see http://wiki.openstreetmap.org/wiki/The_Rails_Port#Potlatch_2 for more information"
potlatch2_unsaved_changes: "You have unsaved changes. (To save in Potlatch 2, you should click save.)"
+ id_not_configured: "iD has not been configured"
no_iframe_support: "Your browser doesn't support HTML iframes, which are necessary for this feature."
sidebar:
search_results: Search Results
diff --git a/config/routes.rb b/config/routes.rb
index a4a8faf43..99dca52ff 100644
--- a/config/routes.rb
+++ b/config/routes.rb
@@ -131,6 +131,7 @@ OpenStreetMap::Application.routes.draw do
match '/logout' => 'user#logout', :via => [:get, :post]
match '/offline' => 'site#offline', :via => :get
match '/key' => 'site#key', :via => :get
+ match '/id_iframe' => 'site#id_iframe', :via => :get
match '/user/new' => 'user#new', :via => :get
match '/user/terms' => 'user#terms', :via => [:get, :post]
match '/user/save' => 'user#save', :via => :post
diff --git a/lib/editors.rb b/lib/editors.rb
index 3b5de3dec..101a6f80c 100644
--- a/lib/editors.rb
+++ b/lib/editors.rb
@@ -1,4 +1,4 @@
module Editors
- ALL_EDITORS = [ "potlatch", "potlatch2", "remote" ]
- RECOMMENDED_EDITORS = [ "potlatch2", "remote" ]
+ ALL_EDITORS = [ "potlatch", "potlatch2", "id", "remote" ]
+ RECOMMENDED_EDITORS = [ "id", "potlatch2", "remote" ]
end
diff --git a/vendor/assets/iD/iD.css.erb b/vendor/assets/iD/iD.css.erb
new file mode 100644
index 000000000..323b2bae4
--- /dev/null
+++ b/vendor/assets/iD/iD.css.erb
@@ -0,0 +1,4320 @@
+/* http://meyerweb.com/eric/tools/css/reset/
+ v2.0 | 20110126
+ License: none (public domain)
+*/
+
+html, body, div, span, applet, object, iframe,
+h1, h2, h3, h4, h5, h6, p, blockquote, pre,
+a, abbr, acronym, address, big, cite, code,
+del, dfn, em, img, ins, kbd, q, s, samp,
+small, strike, strong, sub, sup, tt, var,
+b, u, i, center,
+dl, dt, dd, ol, ul, li,
+fieldset, form, label, legend,
+table, caption, tbody, tfoot, thead, tr, th, td,
+article, aside, canvas, details, embed,
+figure, figcaption, footer, header, hgroup,
+menu, nav, output, ruby, section, summary,
+time, mark, audio, video {
+ margin: 0;
+ padding: 0;
+ border: 0;
+ font-size: 100%;
+ font: inherit;
+ vertical-align: baseline;
+}
+/* HTML5 display-role reset for older browsers */
+article, aside, details, figcaption, figure,
+footer, header, hgroup, menu, nav, section {
+ display: block;
+}
+body {
+ line-height: 1;
+}
+ol, ul {
+ list-style: none;
+}
+blockquote, q {
+ quotes: none;
+}
+blockquote:before, blockquote:after,
+q:before, q:after {
+ content: '';
+ content: none;
+}
+table {
+ border-collapse: collapse;
+ border-spacing: 0;
+}
+a { text-decoration: none;}
+/*
+ * 1. Corrects font family not being inherited in all browsers.
+ * 2. Corrects font size not being inherited in all browsers.
+ * 3. Addresses margins set differently in Firefox 4+, Safari 5, and Chrome
+ */
+
+button,
+input,
+select,
+textarea {
+ font-family: inherit; /* 1 */
+ font-size: 100%; /* 2 */
+ margin: 0; /* 3 */
+ padding: 0;
+}
+
+/*
+ * Addresses Firefox 4+ setting `line-height` on `input` using `!important` in
+ * the UA stylesheet.
+ */
+
+button,
+input {
+ line-height: normal;
+}
+
+/* Hide default number spinner controls */
+input[type="number"]::-webkit-inner-spin-button,
+input[type="number"]::-webkit-outer-spin-button {
+display: none;
+}
+
+/*
+ * 1. Avoid the WebKit bug in Android 4.0.* where (2) destroys native `audio`
+ * and `video` controls.
+ * 2. Corrects inability to style clickable `input` types in iOS.
+ * 3. Improves usability and consistency of cursor style between image-type
+ * `input` and others.
+ */
+
+button,
+html input[type="button"], /* 1 */
+input[type="reset"],
+input[type="submit"] {
+ -webkit-appearance: button; /* 2 */
+ cursor: pointer; /* 3 */
+}
+
+/*
+ * Re-set default cursor for disabled elements.
+ */
+
+button[disabled],
+input[disabled] {
+ cursor: default;
+}
+
+/*
+ * 1. Addresses box sizing set to `content-box` in IE 8/9.
+ * 2. Removes excess padding in IE 8/9.
+ */
+
+input[type="checkbox"],
+input[type="radio"] {
+ box-sizing: border-box; /* 1 */
+ padding: 0; /* 2 */
+}
+
+/*
+ * 1. Addresses `appearance` set to `searchfield` in Safari 5 and Chrome.
+ * 2. Addresses `box-sizing` set to `border-box` in Safari 5 and Chrome
+ * (include `-moz` to future-proof).
+ */
+
+input[type="search"] {
+ -webkit-appearance: textfield; /* 1 */
+ -moz-box-sizing: border-box;
+ -webkit-box-sizing: border-box; /* 2 */
+ box-sizing: border-box;
+}
+
+/*
+ * Removes inner padding and search cancel button in Safari 5 and Chrome
+ * on OS X.
+ */
+
+input[type="search"]::-webkit-search-cancel-button,
+input[type="search"]::-webkit-search-decoration {
+ -webkit-appearance: none;
+}
+
+/*
+ * Removes inner padding and border in Firefox 4+.
+ */
+
+button::-moz-focus-inner,
+input::-moz-focus-inner {
+ border: 0;
+ padding: 0;
+}
+
+/*
+** Markup free clearing
+** Details: http://www.positioniseverything.net/easyclearing.html
+*/
+.cf:before,
+.cf:after {
+ content: " "; /* 1 */
+ display: table; /* 2 */
+}
+
+.cf:after {
+ clear: both;
+}
+/* tiles */
+img.tile {
+ position:absolute;
+ transform-origin:0 0;
+ -ms-transform-origin:0 0;
+ -webkit-transform-origin:0 0;
+ -moz-transform-origin:0 0;
+ -o-transform-origin:0 0;
+
+ opacity: 0;
+
+ -webkit-transition: opacity 200ms linear;
+ transition: opacity 200ms linear;
+ -moz-transition: opacity 200ms linear;
+}
+
+img.tile-loaded {
+ opacity: 1;
+}
+
+/* base styles */
+path {
+ fill: none;
+}
+
+use {
+ pointer-events: none;
+}
+
+g.point .shadow,
+g.vertex .shadow,
+g.midpoint .shadow {
+ pointer-events: all;
+}
+
+path.shadow {
+ pointer-events: stroke;
+}
+
+.shadow {
+ -webkit-transition: 200ms;
+ -moz-transition: 200ms;
+ transition: 200ms;
+}
+
+/* points */
+
+g.point .stroke {
+ stroke: #444;
+ stroke-width: 1;
+ fill: #fff;
+}
+
+g.point .shadow {
+ fill: none;
+ stroke: #f6634f;
+ stroke-width: 8;
+ stroke-opacity: 0;
+}
+
+.behavior-hover g.point.hover:not(.selected) .shadow {
+ stroke-opacity: 0.5;
+}
+
+g.point.selected .shadow {
+ stroke-opacity: 0.7;
+}
+
+g.point.active, g.point.active * {
+ pointer-events: none;
+}
+
+/* vertices and midpoints */
+
+g.vertex .fill {
+ fill: none;
+}
+
+g.vertex .stroke {
+ stroke: #666;
+ stroke-width: 1;
+ fill: white;
+}
+
+g.vertex.shared .stroke {
+ fill: #aaa;
+}
+
+g.vertex.tagged .fill {
+ fill: #000;
+}
+
+g.midpoint .fill {
+ fill: #ddd;
+ stroke: black;
+ stroke-opacity: .5;
+ opacity: .5;
+}
+
+g.vertex .shadow,
+g.midpoint .shadow {
+ fill: #f6634f;
+ fill-opacity: 0;
+}
+
+.behavior-hover g.vertex.hover:not(.selected) .shadow,
+.behavior-hover g.midpoint.hover:not(.selected) .shadow {
+ fill-opacity: 0.3;
+}
+
+g.vertex.selected .shadow {
+ fill-opacity: 0.5;
+}
+
+.mode-draw-area g.midpoint,
+.mode-draw-line g.midpoint,
+.mode-add-area g.midpoint,
+.mode-add-line g.midpoint,
+.mode-add-point g.midpoint,
+.mode-drag-node g.midpoint {
+ display: none;
+}
+
+/* lines */
+
+path.line {
+ stroke-linecap: round;
+ stroke-linejoin: bevel;
+}
+
+path.stroke {
+ stroke: black;
+ stroke-width: 4;
+}
+
+path.shadow {
+ stroke: #f6634f;
+ stroke-width: 10;
+ stroke-opacity: 0;
+}
+
+.behavior-hover path.shadow.hover:not(.selected) {
+ stroke-opacity: 0.3;
+}
+
+path.shadow.selected {
+ stroke-opacity: 0.7;
+}
+
+path.area.stroke,
+path.line.member-type-multipolygon.stroke {
+ stroke-width:2;
+}
+path.area.stroke.selected,
+path.line.member-type-multipolygon.stroke.selected {
+ stroke-width:4 !important;
+}
+
+path.area.stroke {
+ stroke:#fff;
+}
+path.area.fill {
+ fill:#fff;
+ fill-opacity:0.3;
+ fill-rule: evenodd;
+}
+
+path.line.stroke {
+ stroke: white;
+ stroke-width: 2;
+}
+
+path.stroke.tag-natural {
+ stroke: #b6e199;
+ stroke-width:1;
+}
+path.fill.tag-natural {
+ fill: #b6e199;
+}
+
+path.stroke.tag-natural-water {
+ stroke: #77d3de;
+}
+path.fill.tag-natural-water {
+ fill: #77d3de;
+}
+
+path.stroke.tag-building {
+ stroke: #e06e5f;
+ stroke-width: 1;
+}
+path.fill.tag-building {
+ fill: #e06e5f;
+}
+
+/* Landuse */
+
+
+path.stroke.tag-landuse,
+path.stroke.tag-natural-wood,
+path.stroke.tag-natural-tree,
+path.stroke.tag-natural-grassland,
+path.stroke.tag-leisure-park {
+ stroke: #8cd05f;
+ stroke-width: 1;
+}
+
+path.stroke.tag-landuse-residential {
+ stroke: #e06e5f;
+}
+
+path.stroke.tag-landuse-retail,
+path.stroke.tag-landuse-commercial {
+ stroke: #eab056;
+}
+
+path.stroke.tag-landuse-industrial {
+ stroke: #e4a4f5;
+}
+
+path.stroke.tag-landuse-basin,
+path.stroke.tag-landuse-reservoir {
+ stroke: #77d3de;
+}
+
+path.stroke.tag-landuse-quarry {
+ stroke: #a6957b;
+}
+
+path.stroke.tag-landuse-residential,
+path.stroke.tag-landuse-construction {
+ stroke: #e06e5f;
+}
+
+path.stroke.tag-landuse-meadow,
+path.stroke.tag-natural-wetland {
+ stroke: #b6e199;
+}
+
+path.stroke.tag-natural-beach {
+ stroke: #ffff7e;
+}
+
+path.stroke.tag-natural-scrub {
+ stroke: #dbf08b;
+}
+
+path.fill.tag-landuse,
+path.fill.tag-natural-wood,
+path.fill.tag-natural-tree,
+path.fill.tag-natural-grassland,
+path.fill.tag-natural-grass,
+path.fill.tag-leisure-park {
+ fill: #8cd05f;
+ fill-opacity: 0.2;
+}
+
+path.fill.tag-landuse-retail,
+path.fill.tag-landuse-residential,
+path.fill.tag-landuse-commercial,
+path.fill.tag-landuse-industrial {
+ fill-opacity: 0.1;
+}
+
+path.fill.tag-natural-wetland,
+path.fill.tag-natural-beach,
+path.fill.tag-natural-scrub,
+path.fill.tag-landuse-cemetery,
+path.fill.tag-landuse-meadow,
+path.fill.tag-landuse-farm,
+path.fill.tag-landuse-farmland,
+path.fill.tag-landuse-construction,
+path.fill.tag-landuse-orchard {
+ /* background color is applied a further opacity later */
+ fill-opacity: 0.8;
+}
+
+.pattern-color-beach,
+.pattern-color-scrub,
+.pattern-color-meadow,
+.pattern-color-wetland,
+.pattern-color-cemetery,
+.pattern-color-farm,
+.pattern-color-farmland,
+.pattern-color-construction,
+.pattern-color-orchard {
+ fill-opacity: 0.2;
+}
+
+path.fill.tag-landuse-basin,
+path.fill.tag-landuse-reservoir {
+ fill: #77d3de;
+}
+
+path.fill.tag-landuse-quarry {
+ fill: #a6957b;
+}
+
+path.fill.tag-landuse-residential {
+ fill: #e06e5f;
+}
+
+path.fill.tag-landuse-farm,
+path.fill.tag-landuse-farmland {
+ fill: url(#pattern-farmland) #8cd05f;
+}
+
+.pattern-color-farm,
+.pattern-color-farmland {
+ fill: url(#pattern-farmland) #8cd05f;
+}
+
+path.fill.tag-landuse-meadow {
+ fill: url(#pattern-meadow) #b6e199;
+}
+.pattern-color-meadow {
+ fill: #b6e199;
+}
+
+path.fill.tag-natural-wetland {
+ fill: url(#pattern-wetland) #b6e199;
+}
+.pattern-color-wetland {
+ fill: #B6E199;
+}
+
+path.fill.tag-natural-beach {
+ fill: url(#pattern-beach) #ffff7e;
+}
+.pattern-color-beach {
+ fill: #ffff7e;
+}
+
+path.fill.tag-natural-scrub {
+ fill: url(#pattern-scrub) #dbf08b;
+}
+.pattern-color-scrub {
+ fill: #dbf08b;
+}
+
+path.fill.tag-landuse-cemetery {
+ fill: url(#pattern-cemetery) #8cd05f;
+}
+.pattern-color-cemetery {
+ fill: #8cd05f
+}
+
+path.fill.tag-landuse-orchard {
+ fill: url(#pattern-orchard) #8cd05f;
+}
+.pattern-color-orchard {
+ fill: #8cd05f
+}
+
+path.fill.tag-landuse-construction {
+ fill: url(#pattern-construction) #e06e5f;
+}
+.pattern-color-construction {
+ fill: #e06e5f;
+}
+
+path.fill.tag-landuse-retail,
+path.fill.tag-landuse-commercial {
+ fill: #eab056;
+}
+
+path.fill.tag-landuse-industrial {
+ fill: #e4a4f5;
+}
+
+path.stroke.tag-amenity-parking {
+ stroke: #aaa;
+ stroke-width: 1;
+}
+path.fill.tag-amenity-parking {
+ fill: #aaa;
+}
+
+path.fill.tag-place,
+path.fill.tag-boundary {
+ fill: none;
+}
+
+/* highways */
+
+path.shadow.tag-highway {
+ stroke-width:16;
+}
+path.casing.tag-highway {
+ stroke:#444;
+ stroke-width:10;
+}
+path.stroke.tag-highway {
+ stroke:#ccc;
+ stroke-width:8;
+}
+
+svg[data-zoom="16"] path.shadow.tag-highway {
+ stroke-width:12;
+}
+svg[data-zoom="16"] path.casing.tag-highway {
+ stroke-width:6;
+}
+svg[data-zoom="16"] path.stroke.tag-highway {
+ stroke-width:4;
+}
+
+path.stroke.tag-highway-motorway,
+path.stroke.tag-highway-motorway_link,
+path.stroke.tag-construction-motorway {
+ stroke:#58a9ed;
+}
+
+path.casing.tag-highway-motorway,
+path.casing.tag-highway-motorway_link,
+path.casing.tag-construction-motorway {
+ stroke:#2c5476;
+}
+
+path.stroke.tag-highway-trunk,
+path.stroke.tag-highway-trunk_link,
+path.stroke.tag-construction-trunk {
+ stroke:#8cd05f;
+}
+path.casing.tag-highway-trunk,
+path.casing.tag-highway-trunk_link,
+path.casing.tag-construction-trunk {
+ stroke:#46682f;
+}
+
+path.stroke.tag-highway-primary,
+path.stroke.tag-highway-primary_link,
+path.stroke.tag-construction-primary {
+ stroke:#e06d5f;
+}
+path.casing.tag-highway-primary,
+path.casing.tag-highway-primary_link,
+path.casing.tag-construction-primary {
+ stroke:#70372f;
+}
+
+path.stroke.tag-highway-secondary,
+path.stroke.tag-highway-secondary_link,
+path.stroke.tag-construction-secondary {
+ stroke:#eab056;
+}
+path.casing.tag-highway-secondary,
+path.casing.tag-highway-secondary_link,
+path.casing.tag-construction-secondary {
+ stroke:#75582b;
+}
+
+path.stroke.tag-highway-tertiary,
+path.stroke.tag-highway-tertiary_link,
+path.stroke.tag-construction-tertiary {
+ stroke:#ffff7e;
+}
+path.casing.tag-highway-tertiary,
+path.casing.tag-highway-tertiary_link,
+path.casing.tag-construction-tertiary {
+ stroke:#7f7f3f;
+}
+
+path.stroke.tag-highway-unclassified,
+path.stroke.tag-construction-unclassified {
+ stroke:#eaeaea;
+}
+path.casing.tag-highway-unclassified,
+path.casing.tag-construction-unclassified {
+ stroke:#444;
+}
+
+path.stroke.tag-highway-residential,
+path.stroke.tag-construction-residential {
+ stroke:#fff;
+}
+path.casing.tag-highway-residential,
+path.casing.tag-construction-residential {
+ stroke:#444;
+}
+
+path.stroke.tag-highway-living_street {
+ stroke:#ccc;
+ stroke-width:4;
+}
+path.casing.tag-highway-living_street {
+ stroke:#fff;
+ stroke-width:6;
+}
+
+path.stroke.line.tag-highway-pedestrian {
+ stroke:#fff;
+ stroke-dasharray: 2, 8;
+ stroke-width:4 !important;
+ shapeRendering: auto;
+}
+path.casing.line.tag-highway-pedestrian {
+ stroke:#8cd05f;
+ stroke-width:6 !important;
+}
+path.stroke.area.tag-highway-pedestrian {
+ stroke:#fff;
+ stroke-width: 2;
+}
+path.fill.area.tag-highway-pedestrian {
+ fill:#ccc;
+}
+
+path.stroke.tag-highway-service {
+ stroke:#fff;
+ stroke-width:4;
+}
+path.casing.tag-highway-service {
+ stroke:#666;
+ stroke-width:6;
+}
+svg[data-zoom="16"] path.stroke.tag-highway-service {
+ stroke-width:2;
+}
+svg[data-zoom="16"] path.casing.tag-highway-service {
+ stroke-width:4;
+}
+
+path.stroke.tag-highway-track {
+ stroke: #fff;
+ stroke-width: 4;
+}
+path.casing.tag-highway-track {
+ stroke: #996600;
+ stroke-width: 6;
+ stroke-linecap: butt;
+ stroke-dasharray: 6, 6;
+}
+svg[data-zoom="16"] path.stroke.tag-highway-track {
+ stroke-width:2;
+}
+svg[data-zoom="16"] path.casing.tag-highway-track {
+ stroke-width:4;
+}
+
+path.stroke.tag-highway-path {
+ stroke: #000;
+ stroke-width: 1 !important;
+ stroke-linecap: butt;
+ stroke-dasharray: 8, 4;
+}
+path.casing.tag-highway-path {
+ stroke-width: 1 !important;
+ stroke: #fff;
+}
+
+path.stroke.tag-highway-footway,
+path.stroke.tag-highway-cycleway,
+path.stroke.tag-highway-bridleway {
+ stroke-width: 4;
+ stroke-linecap: butt;
+ stroke-dasharray: 6, 6;
+}
+path.casing.tag-highway-footway,
+path.casing.tag-highway-cycleway,
+path.casing.tag-highway-bridleway {
+ stroke-width: 6;
+ stroke: #fff;
+}
+
+svg[data-zoom="16"] path.stroke.tag-highway-footway,
+svg[data-zoom="16"] path.stroke.tag-highway-cycleway,
+svg[data-zoom="16"] path.stroke.tag-highway-bridleway {
+ stroke-width: 2;
+}
+svg[data-zoom="16"] path.casing.tag-highway-footway,
+svg[data-zoom="16"] path.casing.tag-highway-cycleway,
+svg[data-zoom="16"] path.casing.tag-highway-bridleway {
+ stroke-width: 4;
+}
+
+path.stroke.tag-highway-footway {
+ stroke: #ae8681;
+}
+path.stroke.tag-highway-cycleway {
+ stroke: #58a9ed;
+}
+path.stroke.tag-highway-bridleway {
+ stroke: #e06d5f;
+}
+
+path.stroke.tag-highway-steps {
+ stroke: #81d25c;
+ stroke-width: 4;
+ stroke-linecap: butt;
+ stroke-dasharray: 3, 3;
+}
+path.casing.tag-highway-steps {
+ stroke-width: 6;
+ stroke: #fff;
+}
+
+/* aeroways */
+
+path.stroke.tag-aeroway-taxiway {
+ stroke: #805C80;
+ stroke-width: 4;
+}
+path.shadow.tag-aeroway-runway {
+ stroke-width: 20;
+}
+path.stroke.tag-aeroway-runway {
+ stroke: #fff;
+ stroke-width: 2;
+ stroke-linecap: butt;
+ stroke-dasharray: 24, 48;
+}
+path.casing.tag-aeroway-runway {
+ stroke-width: 10;
+ stroke: #000;
+ stroke-linecap: square;
+}
+path.fill.tag-aeroway-runway {
+ fill: #000;
+ fill-opacity: 0.6;
+}
+path.stroke.tag-aeroway-apron {
+ stroke: #805C80;
+}
+path.fill.tag-aeroway-apron {
+ fill: #805C80;
+ fill-opacity: 0.2;
+}
+
+
+/* bridges */
+
+path.casing.tag-bridge-yes {
+ stroke-width: 14;
+ stroke-opacity: 0.5;
+ stroke: #000;
+}
+
+path.casing.tag-highway-living_street.tag-bridge-yes,
+path.casing.tag-highway-path.tag-bridge-yes {
+ stroke-width: 6;
+}
+
+path.casing.line.tag-highway-pedestrian,
+path.casing.tag-highway-service.tag-bridge-yes,
+path.casing.tag-highway-track.tag-bridge-yes,
+path.casing.tag-highway-steps.tag-bridge-yes,
+path.casing.tag-highway-footway.tag-bridge-yes,
+path.casing.tag-highway-cycleway.tag-bridge-yes,
+path.casing.tag-highway-bridleway.tag-bridge-yes {
+ stroke-width: 8;
+}
+
+path.shadow.tag-highway-residential.tag-bridge {
+ stroke-width:22;
+}
+
+path.shadow.tag-highway-living_street.tag-bridge-yes,
+path.shadow.tag-highway-path.tag-bridge-yes,
+path.shadow.line.tag-highway-pedestrian,
+path.shadow.tag-highway-service.tag-bridge-yes,
+path.shadow.tag-highway-track.tag-bridge-yes,
+path.shadow.tag-highway-steps.tag-bridge-yes,
+path.shadow.tag-highway-footway.tag-bridge-yes,
+path.shadow.tag-highway-cycleway.tag-bridge-yes,
+path.shadow.tag-highway-bridleway.tag-bridge-yes {
+ stroke-width: 16;
+}
+
+/* tunnels */
+
+path.stroke.tag-highway.tag-tunnel-yes {
+ stroke-opacity: 0.3;
+}
+
+path.casing.tag-highway.tag-tunnel-yes {
+ stroke-opacity: 0.5;
+}
+
+path.stroke.tag-highway-construction,
+path.casing.tag-highway-construction {
+ stroke-linecap: butt;
+ stroke-dasharray: 7, 7;
+}
+
+/* construction */
+
+svg[data-zoom="16"] path.stroke.tag-highway-construction,
+svg[data-zoom="16"] path.casing.tag-highway-construction {
+ stroke-linecap: butt;
+ stroke-dasharray: 5, 5;
+}
+
+/* railways */
+
+.line.stroke.tag-railway {
+ stroke: #eee;
+ stroke-width: 2;
+ stroke-linecap: butt;
+ stroke-dasharray: 12,12;
+}
+.line.casing.tag-railway {
+ stroke: #555;
+ stroke-width: 4;
+}
+
+.line.stroke.tag-railway-abandoned {
+ stroke: #eee;
+}
+.line.casing.tag-railway-abandoned {
+ stroke: #999;
+}
+
+.line.stroke.tag-railway-subway {
+ stroke: #666;
+}
+.line.casing.tag-railway-subway {
+ stroke: #222;
+}
+
+.line.stroke.tag-railway-platform {
+ stroke: #999;
+ stroke-width: 4;
+ stroke-dasharray: none;
+}
+.line.casing.tag-railway-platform {
+ stroke: none;
+}
+
+/* waterways */
+
+path.fill.tag-waterway {
+ fill: #77d3de;
+}
+
+path.stroke.tag-waterway {
+ stroke: #77d3de;
+ stroke-width: 2;
+}
+path.casing.tag-waterway {
+ stroke: #77d3de;
+ stroke-width: 4;
+}
+
+path.stroke.tag-waterway-river {
+ stroke-width: 4;
+}
+path.casing.tag-waterway-river {
+ stroke-width: 6;
+}
+
+svg[data-zoom="16"] path.stroke.tag-waterway-river {
+ stroke-width: 4;
+}
+svg[data-zoom="16"] path.casing.tag-waterway-river {
+ stroke-width: 6;
+}
+
+path.stroke.tag-waterway-ditch {
+ stroke: #6591ff;
+ stroke-width: 1;
+}
+path.casing.tag-waterway-ditch {
+ stroke: #6591ff;
+ stroke-width: 3;
+}
+
+/* power */
+
+path.stroke.tag-power {
+ stroke: #939393;
+ stroke-width: 2;
+}
+path.casing.tag-power {
+ stroke: none;
+}
+
+/* boundary */
+
+path.stroke.tag-boundary {
+ stroke: #fff;
+ stroke-width: 2;
+ stroke-linecap: butt;
+ stroke-dasharray: 20, 5, 5, 5;
+}
+path.casing.tag-boundary {
+ stroke: #82B5FE;
+ stroke-width: 6;
+}
+
+path.casing.tag-boundary-protected_area,
+path.casing.tag-boundary-national_park {
+ stroke: #b0e298;
+}
+
+
+text {
+ font-size:10px;
+ pointer-events: none;
+ color: #222;
+ opacity: 1;
+}
+
+.oneway .textpath.tag-waterway {
+ fill: #002F35;
+}
+
+marker#oneway-marker path {
+ fill:#000;
+ opacity: .5;
+}
+
+text.tag-oneway {
+ fill:#91CFFF;
+ stroke:#2C6B9B;
+ stroke-width:1;
+ pointer-events:none;
+}
+
+/*
+ * Labels
+ */
+
+text.arealabel-halo,
+text.linelabel-halo,
+text.pointlabel-halo,
+text.arealabel,
+text.linelabel,
+text.pointlabel {
+ font-size: 12px;
+ font-weight: bold;
+ fill: #333;
+ text-anchor: middle;
+ pointer-events: none;
+ -webkit-transition: opacity 100ms linear;
+ transition: opacity 100ms linear;
+ -moz-transition: opacity 100ms linear;
+}
+
+.linelabel-halo .textpath,
+.linelabel .textpath {
+ dominant-baseline: middle;
+}
+
+/* Opera doesn't support dominant-baseline. See #715 */
+.opera .linelabel-halo .textpath,
+.opera .linelabel .textpath {
+ baseline-shift: -33%;
+ dominant-baseline: auto;
+}
+
+.layer-halo text {
+ opacity: 0.7;
+ stroke: #fff;
+ stroke-width: 5px;
+ stroke-miterlimit: 1;
+}
+
+text.point {
+ font-size: 10px;
+}
+
+/* Cursors */
+
+#map:hover {
+ cursor: auto; /* Opera */
+ cursor: url(<%= asset_path("iD/img/cursor-grab.png") %>) 9 9, auto; /* FF */
+ cursor: -webkit-image-set(
+ url(<%= asset_path("iD/img/cursor-grab.png") %>) 1x,
+ url(<%= asset_path("iD/img/cursor-grab2x.png") %>) 2x
+ ) 9 9, auto;
+}
+
+#map:active {
+ cursor: pointer; /* Opera */
+ cursor: url(<%= asset_path("iD/img/cursor-grabbing.png") %>) 9 9, auto; /* FF */
+ cursor: -webkit-image-set(
+ url(<%= asset_path("iD/img/cursor-grabbing.png") %>) 1x,
+ url(<%= asset_path("iD/img/cursor-grabbing2x.png") %>) 2x
+ ) 9 9, auto;
+}
+
+.mode-browse .point,
+.mode-select .point {
+ cursor: pointer; /* Opera */
+ cursor: url(<%= asset_path("iD/img/cursor-select-point.png") %>), pointer; /* FF */
+ cursor: -webkit-image-set(
+ url(<%= asset_path("iD/img/cursor-select-point.png") %>) 1x,
+ url(<%= asset_path("iD/img/cursor-select-point2x.png") %>) 2x
+ ), pointer;
+}
+
+.mode-select .vertex,
+.mode-browse .vertex {
+ cursor: pointer; /* Opera */
+ cursor: url(<%= asset_path("iD/img/cursor-select-vertex.png") %>), pointer; /* FF */
+ cursor: -webkit-image-set(
+ url(<%= asset_path("iD/img/cursor-select-vertex.png") %>) 1x,
+ url(<%= asset_path("iD/img/cursor-select-vertex2x.png") %>) 2x
+ ), pointer;
+}
+
+.mode-browse .line,
+.mode-select .line {
+ cursor: pointer; /* Opera */
+ cursor: url(<%= asset_path("iD/img/cursor-select-line.png") %>), pointer; /* FF */
+ cursor: -webkit-image-set(
+ url(<%= asset_path("iD/img/cursor-select-line.png") %>) 1x,
+ url(<%= asset_path("iD/img/cursor-select-line2x.png") %>) 2x
+ ), pointer;
+}
+
+.mode-select .area,
+.mode-browse .area {
+ cursor: pointer; /* Opera */
+ cursor: url(<%= asset_path("iD/img/cursor-select-area.png") %>), pointer; /* FF */
+ cursor: -webkit-image-set(
+ url(<%= asset_path("iD/img/cursor-select-area.png") %>) 1x,
+ url(<%= asset_path("iD/img/cursor-select-area2x.png") %>) 2x
+ ), pointer;
+}
+
+.mode-select .midpoint,
+.mode-browse .midpoint {
+ cursor: pointer; /* Opera */
+ cursor: url(<%= asset_path("iD/img/cursor-select-split.png") %>), pointer; /* FF */
+ cursor: -webkit-image-set(
+ url(<%= asset_path("iD/img/cursor-select-split.png") %>) 1x,
+ url(<%= asset_path("iD/img/cursor-select-split2x.png") %>) 2x
+ ), pointer;
+}
+
+.mode-select .behavior-multiselect .point,
+.mode-select .behavior-multiselect .vertex,
+.mode-select .behavior-multiselect .line,
+.mode-select .behavior-multiselect .area {
+ cursor: pointer; /* Opera */
+ cursor: url(<%= asset_path("iD/img/cursor-select-add.png") %>), pointer; /* FF */
+ cursor: -webkit-image-set(
+ url(<%= asset_path("iD/img/cursor-select-add.png") %>) 1x,
+ url(<%= asset_path("iD/img/cursor-select-add2x.png") %>) 2x
+ ), pointer;
+}
+
+.mode-select .behavior-multiselect .selected {
+ cursor: pointer; /* Opera */
+ cursor: url(<%= asset_path("iD/img/cursor-select-remove.png") %>), pointer; /* FF */
+ cursor: -webkit-image-set(
+ url(<%= asset_path("iD/img/cursor-select-remove.png") %>) 1x,
+ url(<%= asset_path("iD/img/cursor-select-remove2x.png") %>) 2x
+ ), pointer;
+}
+
+#map .point:active,
+#map .vertex:active,
+#map .line:active,
+#map .area:active,
+#map .midpoint:active,
+#map .mode-select .selected {
+ cursor: pointer; /* Opera */
+ cursor: url(<%= asset_path("iD/img/cursor-select-acting.png") %>), pointer; /* FF */
+ cursor: -webkit-image-set(
+ url(<%= asset_path("iD/img/cursor-select-acting.png") %>) 1x,
+ url(<%= asset_path("iD/img/cursor-select-acting2x.png") %>) 2x
+ ), pointer;
+}
+
+.mode-draw-line #map:hover,
+.mode-draw-area #map:hover,
+.mode-add-line #map:hover,
+.mode-add-area #map:hover,
+.mode-drag-node #map:hover {
+ cursor: crosshair; /* Opera */
+ cursor: url(<%= asset_path("iD/img/cursor-draw.png") %>) 9 9, crosshair; /* FF */
+ cursor: -webkit-image-set(
+ url(<%= asset_path("iD/img/cursor-draw.png") %>) 1x,
+ url(<%= asset_path("iD/img/cursor-draw2x.png") %>) 2x
+ ) 9 9, crosshair;
+}
+
+.mode-draw-line .behavior-hover .way,
+.mode-draw-area .behavior-hover .way,
+.mode-add-line .behavior-hover .way,
+.mode-add-area .behavior-hover .way,
+.mode-drag-node .behavior-hover .way {
+ cursor: crosshair; /* Opera */
+ cursor: url(<%= asset_path("iD/img/cursor-draw-connect-line.png") %>) 9 9, crosshair; /* FF */
+ cursor: -webkit-image-set(
+ url(<%= asset_path("iD/img/cursor-draw-connect-line.png") %>) 1x,
+ url(<%= asset_path("iD/img/cursor-draw-connect-line2x.png") %>) 2x
+ ) 9 9, crosshair;
+}
+
+.mode-draw-line .behavior-hover .vertex,
+.mode-draw-area .behavior-hover .vertex,
+.mode-add-line .behavior-hover .vertex,
+.mode-add-area .behavior-hover .vertex,
+.mode-drag-node .behavior-hover .vertex {
+ cursor: crosshair; /* Opera */
+ cursor: url(<%= asset_path("iD/img/cursor-draw-connect-vertex.png") %>) 9 9, crosshair; /* FF */
+ cursor: -webkit-image-set(
+ url(<%= asset_path("iD/img/cursor-draw-connect-vertex.png") %>) 1x,
+ url(<%= asset_path("iD/img/cursor-draw-connect-vertex2x.png") %>) 2x
+ ) 9 9, crosshair;
+}
+
+.mode-add-point #map:hover,
+.lasso #map:hover,
+.lasso .way,
+.lasso .vertex {
+ cursor: crosshair; /* Opera */
+ cursor: url(<%= asset_path("iD/img/cursor-draw.png") %>) 9 9, crosshair; /* FF */
+ cursor: -webkit-image-set(
+ url(<%= asset_path("iD/img/cursor-draw.png") %>) 1x,
+ url(<%= asset_path("iD/img/cursor-draw2x.png") %>) 2x
+ ) 9 9, crosshair;
+}
+
+.lasso #map {
+ pointer-events: visibleStroke;
+}
+
+/* GPX Paths */
+path.gpx {
+ stroke:#6AFF25;
+ stroke-width:2;
+ fill:transparent;
+ pointer-events: none;
+}
+
+/* Modes */
+
+.mode-draw-line .vertex.active,
+.mode-draw-area .vertex.active,
+.mode-drag-node .vertex.active {
+ display: none;
+}
+
+.mode-draw-line .way.active,
+.mode-draw-area .way.active,
+.mode-drag-node .active {
+ pointer-events: none;
+}
+
+/* Ensure drawing doesn't interact with area fills. */
+.mode-add-point .area.fill,
+.mode-draw-line .area.fill,
+.mode-draw-area .area.fill,
+.mode-add-line .area.fill,
+.mode-add-area .area.fill,
+.mode-drag-node .area.fill {
+ pointer-events: none;
+}
+/* Basics
+------------------------------------------------------- */
+
+body {
+ font:normal 12px/1.6666 'Helvetica Neue', Arial, sans-serif;
+ margin:0;
+ padding:0;
+ min-width: 768px;
+ color:#333;
+ overflow: hidden;
+ -webkit-font-smoothing: subpixel-antialiased;
+}
+
+.unsupported {
+ text-align: center;
+ vertical-align: middle;
+ padding-top: 100px;
+ font-size: 15px;
+}
+
+.id-container {
+ height: 100%;
+ width: 100%;
+ position: fixed;
+ min-width: 768px;
+}
+
+.limiter {
+ position: relative;
+ max-width: 1200px;
+}
+
+.spinner {
+ opacity: .5;
+ z-index: 2;
+ position: relative;
+}
+
+.spinner img {
+ position: fixed;
+ height: 40px;
+ width: 40px;
+ right: 10px;
+ top: 10px;
+ margin: auto;
+ border-radius: 4px;
+ background: black;
+}
+
+div, textarea, label, input, form, span, ul, li, ol, a, button, h1, h2, h3, h4, h5, p, img {
+ -moz-box-sizing: border-box;
+ -webkit-box-sizing: border-box;
+ box-sizing: border-box;
+}
+
+a, button, input, textarea {
+ -webkit-tap-highlight-color:rgba(0,0,0,0);
+ -webkit-touch-callout:none;
+}
+
+a,
+button,
+.checkselect label:hover,
+.opacity-options li,
+.radial-menu-item {
+ cursor: pointer; /* Opera */
+ cursor: url(<%= asset_path("iD/img/cursor-pointer.png") %>) 6 1, pointer; /* FF */
+ cursor: -webkit-image-set(
+ url(<%= asset_path("iD/img/cursor-pointer.png") %>) 1x,
+ url(<%= asset_path("iD/img/cursor-pointer2x.png") %>) 2x
+ ) 6 1, pointer;
+}
+
+h2 {
+ font-size: 25px;
+ line-height: 1.25;
+ font-weight: bold;
+ margin-bottom: 20px;
+}
+
+h3:last-child,
+h2:last-child,
+h4:last-child { margin-bottom: 0;}
+
+h3 {
+ font-size: 16px;
+ line-height: 1.25;
+ font-weight: bold;
+ margin-bottom: 10px;
+}
+
+h4, h5 {
+ font-size: 12px;
+ font-weight: bold;
+ padding-bottom: 10px;
+}
+
+:focus {
+ outline-color: transparent;
+ outline-style: none;
+}
+
+p {
+ font-size: 12px;
+ margin:0;
+ padding:0;
+}
+
+p:last-child {
+ padding-bottom: 0;
+}
+
+em {
+ font-style: italic;
+}
+
+strong {
+ font-weight: bold;
+}
+
+a:visited, a {
+ color: #7092ff;
+ -webkit-transition: all 100ms;
+ -moz-transition: all 100ms;
+ -o-transition: all 100ms;
+ transition: all 100ms;
+}
+
+a:hover {
+ color:#597be7;
+}
+
+/* Forms
+------------------------------------------------------- */
+
+textarea {
+ resize: vertical;
+ font:normal 12px/20px 'Helvetica Neue', Arial, sans-serif;
+}
+
+textarea,
+input[type=text],
+input[type=search],
+input[type=number],
+input[type=url],
+input[type=tel],
+input[type=email] {
+ background-color: white;
+ border:1px solid #ccc;
+ padding:5px 10px;
+ height:30px;
+ width: 100%;
+ border-radius:4px;
+ -webkit-transition: all 100ms;
+ -moz-transition: all 100ms;
+ -o-transition: all 100ms;
+ transition: all 100ms;
+}
+
+textarea:focus,
+input:focus {
+ background-color: #F1F1F1;
+}
+
+input.major {
+ width: 100%;
+ padding:5px 10px;
+ font-size: 18px;
+ font-weight: bold;
+ height:60px;
+}
+
+/* remove bottom border radius when combox is open */
+.combobox + div textarea:focus,
+.combobox + div input:focus {
+ border-bottom-left-radius: 0;
+ border-bottom-right-radius: 0;
+}
+
+/* tables */
+
+table {
+ background-color: white;
+ border-collapse: collapse;
+ width:100%;
+ border-spacing:0;
+}
+
+table th {
+ text-align:left;
+}
+
+table.tags, table.tags td, table.tags th {
+ border: 1px solid #CCC;
+ padding: 4px;
+}
+
+/* Grid
+------------------------------------------------------- */
+
+.col0 { float:left; width:04.1666%; }
+.col1 { float:left; width:08.3333%; }
+.col2 { float:left; width:16.6666%; }
+.col3 { float:left; width:25.0000%; max-width: 300px; }
+.col4 { float:left; width:33.3333%; max-width: 400px; }
+.col5 { float:left; width:41.6666%; max-width: 500px; }
+.col6 { float:left; width:50.0000%; max-width: 600px; }
+.col7 { float:left; width:58.3333%; }
+.col8 { float:left; width:66.6666%; }
+.col9 { float:left; width:75.0000%; }
+.col10 { float:left; width:83.3333%; }
+.col11 { float:left; width:91.6666%; }
+.col12 { float:left; width:100.0000%; }
+
+/* UI Lists
+------------------------------------------------------- */
+
+ul li { list-style: none;}
+
+ul.toggle-list {
+ border-radius: 4px;
+ border: 1px solid #CCC;
+ margin-bottom: 10px;
+}
+
+ul.toggle-list li a {
+ position: relative;
+ padding: 5px 10px 5px 25px;
+ display:block;
+ border-top: 1px solid #ccc;
+}
+
+ul.toggle-list li:first-child a {
+ border-top: 0;
+ border-radius: 3px 3px 0 0;
+}
+
+ul.toggle-list li:last-child a {
+ border-radius: 0 0 3px 3px;
+}
+
+ul.toggle-list li:only-child a {
+ border-radius: 3px;
+}
+
+ul.toggle-list li a:hover { background-color: #ececec;}
+
+ul.toggle-list li a.selected { background-color: #e8ebff;}
+
+ul.link-list li {
+ float: right;
+ border-left: 1px solid rgba(255,255,255,.5);
+ padding: 5px 0 5px 5px;
+ margin-left: 5px;
+}
+
+ul.link-list li:last-child {
+ border-left: 0;
+ margin-left: 0;
+ padding-left: 0;
+}
+
+.toggle-list a::before {
+ content: "";
+ display: inline-block;
+ border-radius: 50%;
+ height: 12px;
+ width: 12px;
+ margin-right: 10px;
+ border: 1px solid #CCC;
+ position: absolute;
+ left: 5px;
+ top: 8px;
+}
+
+.toggle-list a:hover::before {
+ box-shadow: inset 0 0 0 2px white;
+}
+
+.toggle-list a.selected::before {
+ background: #7092ff;
+ box-shadow: inset 0 0 0 2px white;
+}
+
+/* Utility Classes
+------------------------------------------------------- */
+.fillL {
+ background: white;
+ color: #333;
+}
+
+.fillL2 {
+ background: #f7f7f7 url(<%= asset_path("iD/img/background-pattern-1.png") %>) repeat;
+ color: #333;
+}
+
+.fillL3 {
+ background: #f1f1f1;
+ color: #333;
+}
+
+.fillD {
+ background:rgba(0,0,0,.5);
+ color: white;
+}
+
+.fl { float: left;}
+.fr { float: right;}
+
+div.hide,
+form.hide,
+button.hide,
+a.hide {
+ display: none;
+}
+
+.deemphasize {
+ color: #a9a9a9;
+}
+
+.content {
+ box-shadow: 0 0 30px 0 rgba(0, 0, 0, 0.25);
+}
+
+.loading {
+ background: url(<%= asset_path("iD/img/loader_bg.gif") %>);
+ background-size:5px 5px;
+}
+
+.panewrap {
+ position:absolute;
+ width:200%;
+ height:100%;
+ right: -100%;
+}
+
+.pane {
+ position:absolute;
+ width:50%;
+ height:100%;
+}
+
+.pane:first-child {
+ left: 0;
+}
+
+.pane:last-child {
+ right: 0;
+}
+
+/* Buttons */
+
+button {
+ text-align: center;
+ font-weight:bold;
+ line-height:20px;
+ border:0;
+ background: white;
+ color:#333;
+ font-size:12px;
+ display: inline-block;
+ height:40px;
+ border-radius:4px;
+ /* Crashes Safari: https://github.com/systemed/iD/issues/1188 */
+ /*-webkit-transition: all 100ms;*/
+ -moz-transition: all 100ms;
+ -o-transition: all 100ms;
+ transition: all 100ms;
+}
+
+button:focus,
+button:hover {
+ background-color: #ececec;
+}
+
+button[disabled],
+button.disabled {
+ background-color: rgba(255,255,255,.25);
+ color: rgba(0,0,0,.5);
+ cursor: auto;
+}
+
+button.active:not([disabled]):not(.disabled) {
+ background: #7092ff;
+}
+
+button.minor {
+ position: absolute;
+ top: 0;
+ right: 0;
+ height: 100%;
+ width: 20px;
+ opacity: .5;
+ border-radius: 0;
+}
+
+.button-wrap {
+ display: inline-block;
+ padding-right:10px;
+ margin: 0;
+}
+
+.button-wrap button:only-child { width: 100%;}
+.button-wrap:last-of-type { padding-right: 0;}
+
+.joined button {
+ border-radius:0;
+ border-right: 1px solid rgba(0,0,0,.5);
+}
+
+.joined button:first-child {
+ border-radius:4px 0 0 4px;
+}
+
+.joined button:last-child {
+ border-right-width: 0;
+ border-radius:0 4px 4px 0;
+}
+
+button.action {
+ background: #7092ff;
+}
+
+button.action:hover {
+ background: #597BE7;
+}
+
+button.save.has-count {
+ padding: 9px;
+}
+
+button.save .count {
+ display: none;
+}
+
+button.save.has-count .count {
+ display: block;
+ position: absolute;
+ top: 5px;
+ background: rgba(255, 255, 255, .5);
+ color: #333;
+ padding: 10px;
+ height: 30px;
+ line-height: 12px;
+ border-radius: 4px;
+ margin: auto;
+ margin-left: 8.3333%;
+}
+
+button.save.has-count .count::before {
+ content: "";
+ margin: auto;
+ width: 0;
+ height: 0;
+ position: absolute;
+ left: -6px;
+ top: 0;
+ bottom: 0;
+ border-top: 6px solid transparent;
+ border-bottom: 6px solid transparent;
+ border-right: 6px solid rgba(255,255,255,.5);
+}
+
+/* Icons */
+
+.icon {
+ display:inline-block;
+ vertical-align:top;
+ width:20px;
+ height:20px;
+ background:transparent url(<%= asset_path("iD/img/sprite.svg") %>) no-repeat 0 0;
+ text-indent:-9999px;
+ overflow:hidden;
+ }
+
+.icon-pre-text {
+ margin-right: 3px;
+}
+
+.user-icon {
+ max-height: 20px;
+ max-width: 20px;
+ height: auto;
+ width: auto;
+ border-radius: 3px;
+}
+
+/* Definitions for every icon */
+.icon.browse { background-position: 0 0;}
+.icon.add-point { background-position: -20px 0;}
+.icon.add-line { background-position: -40px 0;}
+.icon.add-area { background-position: -60px 0;}
+.icon.undo { background-position: -80px 0;}
+.icon.redo { background-position: -100px 0;}
+.icon.apply { background-position: -120px 0;}
+.icon.save { background-position: -140px 0;}
+.icon.close { background-position: -160px 0;}
+.icon.delete { background-position: -180px 0;}
+.icon.remove { background-position: -200px 0;}
+.icon.inspect { background-position: -220px 0;}
+.icon.zoom-in { background-position: -240px 0;}
+.icon.zoom-out { background-position: -260px 0;}
+.icon.plus { background-position: -240px 0;}
+.icon.search { background-position: -280px 0;}
+.icon.geocode { background-position: -280px 0;}
+.icon.layers { background-position: -300px 0;}
+.icon.avatar { background-position: -320px 0;}
+.icon.nearby { background-position: -340px 0;}
+.icon.geolocate { background-position: -360px 0;}
+.icon.warning { background-position: -380px 0;}
+.icon.back { background-position: -420px 0;}
+.icon.forward { background-position: -440px 0;}
+.icon.help { background-position: -460px 0;}
+
+.icon.inspect.light { background-position: -220px -20px;}
+.icon.geocode.light { background-position: -280px -20px;}
+.icon.help.light { background-position: -460px -20px;}
+.icon.avatar.light { background-position: -320px -20px;}
+.icon.nearby.light { background-position: -340px -20px;}
+
+.icon.back.blue { background-position: -420px -20px;}
+.icon.forward.blue { background-position: -440px -20px;}
+
+button[disabled] .icon.browse { background-position: 0 -40px;}
+button[disabled] .icon.add-point { background-position: -20px -40px;}
+button[disabled] .icon.add-line { background-position: -40px -40px;}
+button[disabled] .icon.add-area { background-position: -60px -40px;}
+button.disabled .icon.undo { background-position: -80px -40px;}
+button.disabled .icon.redo { background-position: -100px -40px;}
+button[disabled] .apply.icon { background-position: -120px -40px;}
+button[disabled] .close.icon { background-position: -160px -40px;}
+button[disabled] .delete.icon { background-position: -180px -40px;}
+button[disabled] .icon.remove { background-position: -200px -40px;}
+button[disabled] .icon.inspect { background-position: -220px -40px;}
+button[disabled] .icon.zoom-in { background-position: -240px -40px;}
+button[disabled] .icon.zoom-out { background-position: -260px -40px;}
+button[disabled] .icon.geocode { background-position: -280px -40px;}
+button[disabled] .icon.layers { background-position: -300px -40px;}
+button[disabled] .icon.avatar { background-position: -320px -40px;}
+button[disabled] .icon.nearby { background-position: -340px -40px;}
+
+.icon-operation-delete { background-position: 0 -140px;}
+.icon-operation-circularize { background-position: -20px -140px;}
+.icon-operation-straighten { background-position: -40px -140px;}
+.icon-operation-split { background-position: -60px -140px;}
+.icon-operation-disconnect { background-position: -80px -140px;}
+.icon-operation-reverse { background-position: -100px -140px;}
+.icon-operation-move { background-position: -120px -140px;}
+.icon-operation-merge { background-position: -140px -140px;}
+.icon-operation-orthogonalize { background-position: -160px -140px;}
+.icon-operation-rotate { background-position: -180px -140px;}
+.icon-operation-simplify { background-position: -200px -140px;}
+
+.icon-operation-disabled-delete { background-position: 0 -160px;}
+.icon-operation-disabled-circularize { background-position: -20px -160px;}
+.icon-operation-disabled-straighten { background-position: -40px -160px;}
+.icon-operation-disabled-split { background-position: -60px -160px;}
+.icon-operation-disabled-disconnect { background-position: -80px -160px;}
+.icon-operation-disabled-reverse { background-position: -100px -160px;}
+.icon-operation-disabled-move { background-position: -120px -160px;}
+.icon-operation-disabled-merge { background-position: -140px -160px;}
+.icon-operation-disabled-orthogonalize { background-position: -160px -160px;}
+.icon-operation-disabled-rotate { background-position: -180px -160px;}
+.icon-operation-disabled-simplify { background-position: -200px -160px;}
+
+/* Out link is special */
+
+.icon.out-link { height: 14px; width: 14px; background-position: -500px 0;}
+a:hover .icon.out-link { background-position: -500px -14px;}
+
+/* Universal preset icons */
+
+.icon.source { background-position: 0 -200px;}
+.icon.address { background-position: -20px -200px;}
+.icon.telephone { background-position: -40px -200px;}
+.icon.website { background-position: -60px -200px;}
+.icon.elevation { background-position: -80px -200px;}
+.icon.wikipedia { background-position: -100px -200px;}
+.icon.note { background-position: -120px -200px;}
+.icon.wheelchair { background-position: -140px -200px;}
+
+/* ToolBar / Persistent UI Elements
+------------------------------------------------------- */
+
+#bar {
+ position:absolute;
+ padding: 10px;
+ left:0;
+ top:0;
+ right:0;
+ height:60px;
+}
+
+/* Header for modals / panes
+------------------------------------------------------- */
+
+.header {
+ border-bottom: 1px solid #ccc;
+ z-index: 2;
+ height: 60px;
+ position: relative;
+}
+
+.header h3 {
+ margin-right: 40px;
+ margin-bottom: 0;
+ white-space: nowrap;
+ text-overflow: ellipsis;
+ overflow: hidden;
+}
+
+.modal > button,
+.header button {
+ height: 100%;
+ border-radius: 0;
+ border-left: 1px solid #CCC;
+ width: 40px;
+ text-align: center;
+ overflow: hidden;
+ position: absolute;
+ right: 0;
+ top: 0;
+}
+
+.modal > button {
+ height: 59px;
+ z-index: 3;
+}
+
+/* Inspector
+------------------------------------------------------- */
+
+.inspector-wrap {
+ position: absolute;
+ height: 100%;
+ right: 0;
+ overflow: hidden;
+}
+
+.inspector-body {
+ overflow-y: scroll;
+ overflow-x: hidden;
+ position: absolute;
+ right: 0;
+ left: 0;
+ bottom: 30px;
+ top: 60px;
+}
+
+.pane:first-child .inspector-body {
+ top: 120px;
+}
+
+.inspector-inner {
+ padding: 20px;
+ position: relative;
+}
+
+.inspector-wrap .header button.preset-reset {
+ border-right: 1px solid #CCC;
+ position: relative;
+}
+
+.inspector-wrap .header button.preset-reset > div {
+ height: 100%;
+ padding: 20px 0;
+}
+
+.inspector-wrap .header button.preset-reset .col12:last-child {
+ position: absolute;
+ width: 100%;
+ padding: 20px 0;
+ opacity: 0;
+}
+
+.inspector-wrap .header button:hover .col12:first-child {
+ opacity: 0;
+}
+
+.inspector-wrap .header button:hover .col12:last-child {
+ opacity: 1;
+}
+
+.inspector-wrap .header button.line > div {
+ padding: 0;
+}
+
+.inspector-toggle {
+ color:#fff;
+ width: 100%;
+ display: block;
+ background:#7092ff;
+ border: 0;
+}
+
+/* Presets
+------------------------------------------------------- */
+
+/* Preset grid */
+
+.preset-grid {
+ width:100%;
+ padding: 20px 10px 10px 20px;
+ border-bottom: 1px solid #ccc;
+}
+
+.grid-button-wrap {
+ padding: 0 10px 10px 0;
+ height: 120px;
+}
+
+.grid-entry {
+ width: 100%;
+ height: 100%;
+ position: relative;
+ border: 1px solid #ccc;
+ overflow: hidden;
+ float: left;
+}
+
+.grid-inner {
+ margin-bottom: 20px;
+}
+
+.preset-grid.filtered .grid-button-wrap:first-child .grid-entry {
+ background: #ececec;
+}
+
+.preset-icon {
+ position: absolute;
+ top: 30px;
+ left: 0;
+ right: 0;
+ margin: auto;
+}
+
+.preset-icon-line {
+ top: 15px;
+ left: -10px;
+ right: -10px;
+}
+
+.grid-entry .label {
+ background: #f6f6f6;
+ text-align: left;
+ position: absolute;
+ padding: 5px 10px;
+ height: 30px;
+ bottom: 0;
+ left: 0; right: 0;
+ white-space: nowrap;
+ text-overflow: ellipsis;
+ overflow: hidden;
+ border-top: 1px solid rgba(0, 0, 0, .1);
+ }
+
+.grid-button-wrap button.tag-reference-button {
+ float: right;
+ position: static;
+ margin-top: -30px;
+ margin-right: 1px;
+ height: 29px;
+ border-left: 1px solid #CCC;
+ border-radius: 0 0 3px 0;
+}
+
+.current .grid-entry,
+.current .grid-entry .label {
+ background-color: #E8EBFF;
+}
+
+.category .grid-entry:after,
+.category .grid-entry:before {
+ content: "";
+ position: absolute;
+ top: 7px;
+ left: 0; right: 0;
+ border-top: 1px solid #ccc;
+ border-radius: 6px;
+ height: 6px;
+}
+
+.category .grid-entry:before {
+ top: 3px;
+}
+
+.preset-grid-search-wrap .icon {
+ display: block;
+ position: absolute;
+ left: 10px;
+ top: 80px;
+ pointer-events: none;
+}
+
+.preset-grid-search-wrap input {
+ position: absolute;
+ top: 60px;
+ border-radius: 0;
+ border-width: 0;
+ border-bottom-width: 1px;
+ text-indent: 30px;
+}
+
+.preset-search-result {
+ padding: 0 10px;
+ height:30px;
+ margin: 5px;
+}
+
+.subgrid {
+ width: 100%;
+ width: -webkit-calc(100% + 10px);
+ width: calc(100% + 10px);
+ margin-left: -10px;
+ overflow: hidden;
+}
+
+.subgrid .preset-grid {
+ padding: 10px 0 0 10px;
+ border: 1px solid #CCC;
+ margin-top: 0;
+ border-radius: 8px;
+}
+
+.subgrid .arrow {
+ border: solid rgba(0, 0, 0, 0);
+ border-width: 10px;
+ border-bottom-color: #CCC;
+ width: 0;
+ height: 0;
+ margin-left: 33.3333%;
+ margin-left: -webkit-calc(16.6666% - 10px);
+ margin-left: calc(16.6666% - 10px);
+ margin-top: -10px;
+}
+.subgrid.arrow-1 .arrow {
+ margin-left: 50%;
+ margin-left: -webkit-calc(50% - 10px);
+ margin-left: calc(50% - 10px);
+}
+
+.subgrid.arrow-2 .arrow {
+ margin-left: 280px;
+ margin-left: -webkit-calc(84.4444% - 10px);
+ margin-left: calc(84.4444% - 10px);
+}
+
+.show-more {
+ text-align: center;
+ width: 100%;
+ border-bottom: 1px solid #ccc;
+ border-radius: 0;
+}
+
+.show-more a {
+ color: #222;
+}
+
+/* Preset icon colors */
+
+.inspector-body-line .icon.feature-marker-stroked {
+ top: 30px;
+}
+
+.preset-icon-fill.icon-area {
+ cursor: inherit;
+ height: 45px;
+ width: 45px;
+ margin: auto;
+ position: absolute;
+ left: 0; right: 0; top: 19px;
+ background:transparent url(<%= asset_path("iD/img/sprite.svg") %>) no-repeat -240px -80px;
+}
+
+.preset-icon-fill.tag-shop,
+.preset-icon-fill.tag-building {
+ background:transparent url(<%= asset_path("iD/img/sprite.svg") %>) no-repeat 0 -80px;
+}
+
+.preset-icon-fill.tag-natural-water {
+ background:transparent url(<%= asset_path("iD/img/sprite.svg") %>) no-repeat -60px -80px;
+}
+
+.preset-icon-fill.tag-landuse,
+.preset-icon-fill.tag-natural-wood,
+.preset-icon-fill.tag-natural-tree,
+.preset-icon-fill.tag-natural-grassland,
+.preset-icon-fill.tag-leisure-park {
+ background:transparent url(<%= asset_path("iD/img/sprite.svg") %>) no-repeat -120px -80px;
+}
+
+.preset-icon-fill.tag-amenity-parking {
+ background:transparent url(<%= asset_path("iD/img/sprite.svg") %>) no-repeat -180px -80px;
+}
+
+/* preset form basics */
+
+.tag-wrap .preset-icon-wrap {
+ border-bottom: 1px solid #CCC;
+ background-color: #e8ebff;
+}
+
+.tag-wrap .preset-icon-wrap::after {
+ content: "";
+ position: absolute;
+ height: 0;
+ width: 0;
+ bottom: 0;
+ left: 0;
+ right: 0;
+ margin: auto;
+ border: solid rgba(0, 0, 0, 0);
+ border-width: 10px;
+ border-bottom-color: #CCC;
+}
+
+.tag-wrap .preset-icon-wrap > div {
+ height: 90px;
+ width: 33.3333%;
+ width: -webkit-calc(33.3333% - 10px);
+ width: calc(33.3333% - 10px);
+ margin: auto;
+ border-radius: 4px;
+ border: 1px solid #CCC;
+ position: relative;
+}
+
+.inspector-preset .form-field {
+ padding-left: 20px;
+ padding-right: 20px;
+}
+
+.form-label {
+ position: relative;
+ font-weight: bold;
+ border: 1px solid #cfcfcf;
+ padding: 5px 0 5px 10px;
+ background: #f6f6f6;
+ display: block;
+ border-radius: 4px 4px 0 0;
+}
+
+.form-label button {
+ border-left: 1px solid #CCC;
+}
+
+.form-label .modified-icon {
+ border-right: 0;
+ opacity: 0;
+ right: 20px;
+}
+
+.modified .form-label .modified-icon {
+ opacity: .5;
+}
+
+.form-label button.tag-reference-button {
+ border-top-right-radius: 3px;
+}
+
+.form-field > input,
+.form-field > textarea,
+.form-field .preset-input-wrap {
+ border: 1px solid #CCC;
+ border-top: 0;
+ border-radius: 0 0 4px 4px;
+}
+
+.form-field textarea {
+ height: 65px;
+}
+
+.form-field-name input.localized-main {
+ height: 35px;
+ font-size: 18px;
+ font-weight: bold;
+}
+
+/* adding additional preset fields */
+
+.more-buttons {
+ margin-top: 20px;
+ border-top: 1px solid #CCC;
+}
+
+.more-buttons:nth-last-child(2) {
+ border-bottom: 1px solid #CCC;
+}
+
+button.preset-add-field {
+ width: 25%;
+ height: 40px;
+ -webkit-transition: width 200ms;
+ -moz-transition: width 200ms;
+ -o-transition: width 200ms;
+ transition: width 200ms;
+}
+
+/* set width based on # of buttons */
+
+button.preset-add-field:nth-last-child(4),
+button.preset-add-field:nth-last-child(4) ~ button.preset-add-field {
+ width: 25%;
+}
+
+button.preset-add-field:nth-last-child(5),
+button.preset-add-field:nth-last-child(5) ~ button.preset-add-field {
+ width: 20%;
+}
+
+button.preset-add-field:nth-last-child(6),
+button.preset-add-field:nth-last-child(6) ~ button.preset-add-field {
+ width: 16.6666%;
+}
+
+button.preset-add-field:nth-last-child(7),
+button.preset-add-field:nth-last-child(7) ~ button.preset-add-field {
+ width: 14.2857%;
+}
+
+button.preset-add-field:nth-last-child(8),
+button.preset-add-field:nth-last-child(8) ~ button.preset-add-field {
+ width: 12.5%;
+}
+
+.preset-add-field .tooltip.top .tooltip-arrow {
+ border-top-color: #000;
+}
+
+.preset-add-field .tooltip-inner {
+ background: #000;
+ color: #ccc;
+}
+
+.preset-fav button.fav {
+ height: 30px;
+ margin: 5px;
+ padding: 0 10px;
+}
+
+/* preset form access */
+
+.preset-input-wrap li {
+ border-bottom: 1px solid #CCC;
+}
+.preset-input-wrap li:last-child {
+ border-bottom: 0;
+}
+
+.preset-input-wrap .label {
+ background: #F6F6F6;
+ padding: 5px;
+}
+
+.preset-input-access-wrap input {
+ border-radius: 0;
+ border-width: 0;
+ border-left-width: 1px;
+}
+
+.preset-input-wrap li:last-child input {
+ border-bottom-right-radius: 4px;
+}
+
+/* preset form numbers */
+
+input[type=number] {
+ position: relative;
+ padding-right: 65px;
+}
+
+.spin-control {
+ width: 41px;
+ height: 29px;
+ border-left: 1px solid #CCC;
+ display: inline-block;
+ margin-left: -41px;
+ margin-bottom: -11px;
+ position: relative;
+}
+
+.spin-control button {
+ position: relative;
+ float: left;
+ height: 100%;
+ width: 50%;
+ border-left: 1px solid #CCC;
+ border-right: 1px solid #CCC;
+ border-radius: 0;
+ border-left: 0;
+ background: rgba(0, 0, 0, 0);
+}
+
+.spin-control button.decrement {
+ border-bottom-right-radius: 3px;
+}
+
+.spin-control button.decrement::after,
+.spin-control button.increment::after {
+ content:"";
+ height: 0; width: 0;
+ position: absolute;
+ left: 0; right: 0; bottom: 0; top: 0;
+ margin: auto;
+}
+
+.spin-control button.decrement::after {
+ border-top: 5px solid #CCC;
+ border-left: 5px solid transparent;
+ border-right: 5px solid transparent;
+}
+
+.spin-control button.increment::after {
+ border-bottom: 5px solid #CCC;
+ border-left: 5px solid transparent;
+ border-right: 5px solid transparent;
+}
+
+/* preset form checkbox */
+
+.checkselect label:last-of-type {
+ display: block;
+ padding: 5px;
+ box-sizing: border-box;
+ color: #999;
+}
+
+.checkselect label:hover {
+ background: #f1f1f1;
+}
+
+.checkselect .set {
+ color: inherit;
+}
+
+.checkselect input[type="checkbox"] {
+ margin-right: 5px;
+ width: 20px;
+ vertical-align: middle;
+ opacity: 0.5;
+}
+
+.checkselect .set input[type="checkbox"] {
+ opacity: 1;
+}
+
+/* Preset form radio button */
+
+.radio-wrap button {
+ position: relative;
+ text-align: left;
+ font-weight: normal;
+ height: 30px;
+ border-radius: 0;
+ border-bottom: 1px solid #CCC;
+ color: #7092FF;
+ width: 100%;
+ padding-left: 25px;
+}
+
+.radio-wrap button::before {
+ content: "";
+ display: inline-block;
+ border-radius: 50%;
+ height: 12px;
+ width: 12px;
+ margin-right: 10px;
+ border: 1px solid #CCC;
+ position: absolute;
+ left: 5px;
+ top: 8px;
+}
+
+.radio-wrap button:hover::before {
+ box-shadow: inset 0 0 0 2px white;
+}
+
+.radio-wrap button.active::before {
+ background: #7092ff;
+ box-shadow: inset 0 0 0 2px white;
+}
+
+.radio-wrap button:last-child {
+ border-bottom: 0;
+}
+
+.radio-wrap button.active {
+ background-color: #E8EBFF !important;
+}
+
+.radio-wrap button.remove {
+ border-radius: 0 0 3px 3px;
+}
+.radio-wrap button.remove .icon {
+ position: absolute;
+ left: 2px;
+}
+
+.radio-wrap button.remove::before {
+ content: none;
+}
+
+.form-field .localized-main {
+ width: 90%;
+ border-radius: 0 0 0 4px;
+}
+
+.form-field .localized-add {
+ width: 10%;
+ height: 35px;
+ border-radius: 0 0 4px 0;
+ border-bottom: 1px solid #ccc;
+ border-right: 1px solid #ccc;
+ vertical-align: top;
+}
+
+.form-field .localized-wrap .entry .localized-lang {
+ border-top: none;
+ border-right: none;
+ border-radius: 0;
+ width: 30%;
+}
+
+.form-field .localized-wrap .entry .localized-value {
+ border-top: none;
+ border-radius: 0;
+ width: 60%;
+}
+
+.form-field .localized-wrap .entry .localized-remove {
+ height: 30px;
+ border-radius: 0;
+ border-bottom: 1px solid #ccc;
+ border-right: 1px solid #ccc;
+ vertical-align: top;
+ width: 10%;
+}
+
+.form-field .localized-wrap .entry:last-child .localized-lang {
+ border-radius: 0 0 0 4px;
+}
+
+.form-field .localized-wrap .entry:last-child .localized-remove {
+ border-radius: 0 0 4px 0;
+}
+
+.form-field .wiki-lang {
+ width: 30%;
+ border-right: none;
+ border-radius: 0 0 0 4px;
+}
+
+.form-field .wiki-title {
+ width: 60%;
+ border-right: none;
+ border-radius: 0;
+}
+
+.form-field .wiki-link {
+ border-radius: 0 0 4px 0;
+ border: 1px solid #ccc;
+ border-top: none;
+ height: 30px;
+ width: 10%;
+ float: right;
+ padding: 5px;
+ text-align: center;
+ -webkit-transition: all 100ms;
+ -moz-transition: all 100ms;
+ -o-transition: all 100ms;
+ transition: all 100ms;
+}
+
+.form-field .wiki-link:hover {
+ background: #ececec;
+}
+
+#preset-input-maxspeed {
+ border-right: none;
+ border-radius: 0 0 0 4px;
+ width: 80%;
+}
+
+.form-field .maxspeed-unit {
+ border-radius: 0 0 4px 0;
+ border: 1px solid #ccc;
+ border-top: none;
+ height: 30px;
+ width: 20%;
+ float: right;
+ padding: 5px;
+ text-align: center;
+ color: #A9A9A9;
+ font-weight: bold;
+}
+
+/* Preset form address */
+
+.form-field .addr-housename {
+ border: 0;
+}
+
+.form-field .addr-number {
+ width: 20%;
+ border-left: 0;
+ border-right: 0;
+ border-bottom: 0;
+ border-radius: 0;
+}
+
+.form-field .addr-street {
+ width: 80%;
+ border-right: 0;
+ border-bottom: 0;
+ border-radius: 0;
+}
+
+.form-field .addr-city {
+ border-left: 0;
+ border-right: 0;
+ border-bottom: 0;
+ border-radius: 0 0 4px 4px;
+}
+
+/* combobox dropdown */
+
+div.combobox {
+ z-index: 9999;
+ display: none;
+ box-shadow: 0 0 10px 0 rgba(0,0,0,.1);
+ margin-top: -1px;
+ background: white;
+ max-height: 120px;
+ overflow-y: auto;
+ overflow-x: hidden;
+ border: 1px solid #ccc;
+ border-radius: 0 0 4px 4px;
+}
+
+.combobox a {
+ display: block;
+ padding: 5px 10px;
+ border-top:1px solid #ccc;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+ overflow: hidden;
+}
+
+.combobox a.selected,
+.combobox a:hover {
+ background: #ececec;
+}
+
+.combobox a:first-child {
+ border-top: 0;
+}
+
+.combobox-carat {
+ margin-left: -20px;
+ margin-right: 10px;
+ display:inline-block;
+ border-top: 5px solid #ccc;
+ border-left: 5px solid transparent;
+ border-right: 5px solid transparent;
+}
+
+/* tag editor */
+
+.inspector-inner.additional-tags {
+ border-top: 1px solid #ccc;
+}
+
+.tag-list {
+ margin-right: 40px;
+}
+
+.tag-row {
+ width: 100%;
+ position: relative;
+ clear: both;
+}
+
+.tag-row input {
+ border: 0;
+ border-radius: 0;
+ border-bottom: 1px solid #CCC;
+ border-left: 1px solid #CCC;
+}
+
+.tag-row input.key {
+ background-color: #f6f6f6;
+}
+
+.tag-row input.value {
+ border-right: 1px solid #CCC;
+}
+
+.tag-row:first-child input.key {
+ border-top: 1px solid #CCC;
+ border-top-left-radius: 4px;
+}
+
+.tag-row:first-child input.value {
+ border-top: 1px solid #CCC;
+}
+
+.tag-row button {
+ position: absolute;
+ height: 30px;
+ right: -20px;
+ border: 1px solid #CCC;
+ border-top-width: 0; border-left-width: 0;
+ border-radius: 0;
+ opacity: 1;
+ background: #fafafa;
+}
+
+.tag-row button:hover {
+ background: #f1f1f1;
+}
+
+.tag-row button .icon {
+ opacity: .5;
+}
+
+.tag-row:first-child button {
+ border-top-width: 1px;
+}
+
+.tag-row:first-child button.tag-help-button {
+ border-top-right-radius: 4px;
+}
+
+.tag-row:last-child button.tag-help-button {
+ border-bottom-right-radius: 4px;
+}
+
+.tag-row button.tag-help-button {
+ right: -40px;
+}
+
+/* Adding form fields to tag editor */
+
+.inspector-inner .add-tag {
+ width: -webkit-calc(50% - 20px);
+ width: calc(50% - 20px);
+ height: 30px;
+ border-top: 0;
+ background: rgba(0,0,0,.5);
+ border-radius: 0 0 4px 4px;
+}
+
+.inspector-inner .add-tag:hover {
+ background: rgba(0,0,0,.8);
+}
+
+.inspector-inner .add-tag .label {
+ display: none;
+}
+
+/* Tag reference */
+
+.preset-inspect {
+ position: relative;
+}
+
+.tag-help {
+ overflow: hidden;
+}
+
+.tag-help a {
+ margin-top: 5px;
+ display: block;
+}
+
+.grid-pane .tag-reference-wrap {
+ padding: 10px 0 20px 0;
+}
+
+.tag-pane .tag-reference-wrap {
+ padding-top: 20px;
+}
+
+.additional-tags .tag-reference-wrap {
+ border-bottom: 1px solid #ccc;
+ padding: 20px 0;
+}
+
+.additional-tags div.tag-help {
+ float: left;
+ width: 33.3333%;
+ width: -webkit-calc(100% - 40px);
+ width: calc(100% + 40px);
+}
+
+img.wiki-image {
+ float: left;
+ width: 33.3333%;
+ width: -webkit-calc(33.3333% - 10px);
+ width: calc(33.3333% - 10px);
+ margin-right: 20px;
+ border-radius: 4px;
+ max-height: 200px;
+}
+
+/* Map Controls */
+
+.map-control {
+ z-index: 100;
+ left:0px;
+ position:absolute;
+}
+
+.map-control > button {
+ width: 30px;
+ background: rgba(0,0,0,.5);
+ border-radius: 0;
+}
+
+.map-control > button:hover {
+ background: rgba(0, 0, 0, .8);
+}
+
+.map-control > button.active:hover {
+ background: #7092ff;
+}
+
+.map-overlay {
+ z-index: -1;
+ right: 75%;
+ max-width: 260px;
+ min-width: 210px;
+ position: fixed;
+ left: 30px;
+ display: block;
+ padding: 10px 10px 0 10px;
+}
+
+/* Zoomer */
+
+.zoombuttons {
+ top: 180px;
+ width: 30px;
+}
+
+.zoombuttons button.zoom-in {
+ border-radius:0 4px 0 0;
+}
+
+/* Background Settings */
+
+.background-control {
+ top: 80px;
+}
+
+.background-control button {
+ border-radius:0 4px 0 0;
+}
+
+.background-control button.active {
+ border-radius: 0;
+}
+
+.nudge-container {
+ border-top: 1px solid #CCC;
+ margin: 0 -10px;
+}
+
+.background-control .adjustments button:last-child {
+ border: 0;
+}
+
+.hide-toggle {
+ display: block;
+ padding: 0 0 10px 12px;
+ position: relative;
+}
+
+.hide-toggle:before {
+ content: '';
+ display: block;
+ position: absolute;
+ height: 0;
+ width: 0;
+ left: 0;
+ top: 5px;
+ border-top: 4px solid transparent;
+ border-bottom: 4px solid transparent;
+ border-left: 8px solid #7092ff;
+}
+
+.hide-toggle.expanded:before {
+ border-top: 8px solid #7092ff;
+ border-bottom: 0;
+ border-right: 4px solid transparent;
+ border-left: 4px solid transparent;
+}
+
+.background-control .nudge-container button {
+ float: left;
+ display: block;
+ width:20%;
+ border-radius: 0;
+ border-right: 1px solid #CCC;
+ position: relative;
+}
+
+.background-control .nudge::after {
+ content: '';
+ display: block;
+ position: absolute;
+ margin: auto;
+ left: 0; right: 0; top: 0; bottom: 0;
+ height: 0;
+ width: 0;
+}
+
+.background-control .nudge.left::after {
+ border-top: 5px solid transparent;
+ border-bottom: 5px solid transparent;
+ border-left: 5px solid #222;
+}
+
+.background-control .nudge.right::after {
+ border-top: 5px solid transparent;
+ border-bottom: 5px solid transparent;
+ border-right: 5px solid #222;
+}
+
+.background-control .nudge.top::after {
+ border-right: 5px solid transparent;
+ border-left: 5px solid transparent;
+ border-bottom: 5px solid #222;
+}
+
+.background-control .nudge.bottom::after {
+ border-right: 5px solid transparent;
+ border-left: 5px solid transparent;
+ border-top: 5px solid #222;
+}
+
+.opacity-options {
+ background: url(<%= asset_path("iD/img/background-pattern-opacity.png") %>) 0 0 repeat;
+ height:20px;
+ width:62px;
+ position: absolute;
+ right: 10px;
+ top: 10px;
+ border: 1px solid #ccc;
+}
+
+.opacity-options li {
+ height: 100%;
+ display: block;
+ float: left;
+}
+
+.opacity-options li .select-box{
+ position: absolute;
+ width:20px;
+ height:18px;
+ z-index: 9999;
+}
+
+.background-control li:hover .select-box,
+.background-control li.selected .select-box {
+ border: 2px solid #7092ff;
+ background: rgba(89, 123, 231, .5);
+ opacity: .5;
+}
+
+.background-control li.selected:hover .select-box,
+.background-control li.selected .select-box {
+ opacity: 1;
+}
+
+.background-control .opacity {
+ background:#222;
+ display:inline-block;
+ width:20px;
+ height:18px;
+}
+
+.background-control .layer-toggle-gpx .layer-extent {
+ border-left: 1px solid #CCC;
+}
+
+.background-control .layer-toggle-gpx.selected .layer-extent {
+ display:inline-block;
+}
+
+/* Geocoder */
+
+.geocode-control, .geocode-control form {
+ top:260px;
+}
+
+.geocode-control form {
+ padding: 5px;
+}
+
+.geocode-control input {
+ width: 100%;
+}
+
+.geocode-control div.map-overlay {
+ border-top: 1px solid #CCC;
+ z-index: 100;
+ max-height: 240px;
+ overflow-y: auto;
+ padding: 0;
+ margin-top: 40px;
+}
+
+.geocode-control div.map-overlay span {
+ display: inline-block;
+ border-bottom: 1px solid #CCC;
+ padding: 5px 10px;
+}
+.geocode-control div.map-overlay span.not-found {
+ line-height: 28px;
+ width: 100%;
+}
+
+.geocode-control a:focus {
+ text-decoration: underline;
+}
+
+/* Geolocator */
+
+.geolocate-control {
+ top:300px;
+}
+
+.geolocate-control button {
+ border-radius: 0 0 4px 0;
+}
+
+/* Help */
+
+.help-control {
+ top: 120px;
+}
+
+.help-control button {
+ border-radius: 0 0 4px 0;
+}
+
+.help-wrap {
+ position: absolute;
+ top:60px;
+ bottom: 30px;
+ padding: 20px 20px 20px 50px;
+ left: 0;
+ overflow-y: scroll;
+}
+
+.help-wrap p {
+ font-size: 15px;
+ margin-bottom: 20px;
+}
+
+.help-wrap .left-content .body p code {
+ padding:2px 4px;
+ background:#eee;
+}
+
+.help-wrap .toc {
+ /* This is two columns, 41.66666 x .4 = 16.6666 */
+ width:40%;
+ float:right;
+ margin-left: 20px;
+ margin-bottom: 20px;
+ padding-left: 5px
+}
+
+.help-wrap .toc li a,
+.help-wrap .nav a {
+ display: block;
+ border: 1px solid #CCC;
+ padding: 5px 10px;
+}
+
+.help-wrap .toc li a {
+ border-bottom: 0;
+}
+
+.help-wrap .toc li a:hover,
+.help-wrap .nav a:hover {
+ background: #ececec;
+}
+
+.help-wrap .toc li a.selected {
+ background: #E8EBFF;
+}
+
+.help-wrap .toc li:first-child a {
+ border-radius: 4px 4px 0 0;
+}
+
+.help-wrap .toc li:nth-last-child(2) a {
+ border-bottom: 1px solid #CCC;
+ border-radius: 0 0 4px 4px
+}
+
+.help-wrap .toc li.walkthrough a {
+ overflow: hidden;
+ margin-top: 10px;
+ border-bottom: 1px solid #ccc;
+ border-radius: 4px;
+}
+
+.help-wrap .nav {
+ position: relative;
+}
+
+.help-wrap .nav a {
+ float: left;
+ width: 50%;
+ text-align: center;
+}
+
+.help-wrap .nav a:first-child {
+ border-radius: 4px 0 0 4px;
+}
+
+.help-wrap .nav a:last-child:not(:only-child) {
+ border-radius: 0 4px 4px 0;
+ border-left: 0;
+}
+
+.help-wrap .nav a:only-child {
+ width: 100%;
+ border-radius: 4px;
+}
+
+/* Map
+------------------------------------------------------- */
+
+#map {
+ display:block;
+ position:absolute;
+ overflow:hidden;
+ top:0px;
+ left:0;
+ right:0;
+ bottom:0;
+ background:#000;
+}
+
+#surface, #layer-g, .layer-layer {
+ position:absolute;
+ top:0;
+ left: 0;
+ right: 0;
+ bottom: 0;
+ transform-origin:0 0;
+ -ms-transform-origin:0 0;
+ -webkit-transform-origin:0 0;
+ -moz-transform-origin:0 0;
+ -o-transform-origin:0 0;
+ -moz-user-select: none;
+ -webkit-user-select: none;
+ -ms-user-select: none;
+ user-select: none;
+}
+
+#surface {
+ position: static;
+}
+
+/* About Section
+------------------------------------------------------- */
+
+.about-block {
+ position: absolute;
+ right:0px;
+ bottom:0px;
+ overflow: hidden;
+ white-space: nowrap;
+ text-overflow: ellipsis;
+ border-radius: 0;
+ opacity: .625;
+ -webkit-transition: opacity 200ms;
+ -moz-transition: opacity 200ms;
+ transition: opacity 200ms;
+}
+
+.about-block:hover {
+ opacity: 1;
+}
+
+#about {
+ text-align: right;
+ margin-right: 10px;
+}
+
+.source-switch a {
+ padding: 2px 4px 4px 4px;
+ border-radius: 2px;
+}
+.source-switch a.live {
+ background: #d32232;
+ color:#fff;
+}
+
+/* Attribution overlay */
+.attribution {
+ position: absolute;
+ bottom: 35px;
+ left:10px;
+ color:#888;
+ font-size:10px;
+}
+
+.source-image {
+ height:20px;
+ vertical-align:top;
+}
+
+.user-list a:not(:last-child):after {
+ content: ', ';
+}
+
+/* API Status */
+
+.api-status {
+ float: left;
+}
+
+.api-status.offline,
+.api-status.readonly {
+ background: red;
+ padding: 5px 10px;
+}
+
+/* Account Information */
+
+.account {
+ float: left;
+ padding: 5px 10px;
+}
+
+.account .logout {
+ margin-left:10px;
+ border-left: 1px solid white;
+ padding-left: 10px;
+}
+
+/* Modals
+------------------------------------------------------- */
+
+.modal {
+ display: inline-block;
+ position:absolute;
+ left: 0;
+ right: 0;
+ margin: auto;
+ z-index: 3;
+}
+
+.modal .loader {
+ margin-bottom: 10px;
+}
+
+.modal .description {
+ text-align: center;
+}
+
+.shaded {
+ z-index: 2;
+ position: absolute;
+ height: 100%;
+ width: 100%;
+ overflow: auto;
+}
+
+.shaded:before {
+ content:'';
+ background:rgba(0,0,0,0.5);
+ position:fixed;
+ left:0px; right:0px; top:0px; bottom:0px;
+}
+
+.modal-section {
+ padding: 20px;
+ border-bottom: 1px solid #CCC;
+}
+
+.modal-section:last-child {
+ border-bottom: 0;
+}
+
+.loading-modal {
+ text-align: center;
+}
+
+.modal-actions button,
+.modal-actions a {
+ background-size: white;
+ font-weight: normal;
+ color: #7092FF;
+ border-bottom: 1px solid #CCC;
+ border-radius: 0;
+ height: 180px;
+ text-align: center;
+ display: inline-block;
+}
+
+.modal-actions a {
+ /* `button` elements have box-algin: auto, need
+ compensate this for `a`*/
+ padding-top: 25px;
+}
+
+.modal-actions button:hover,
+.modal-actions a:hover {
+ background-color: #ececec;
+}
+
+.modal-actions a:before,
+.modal-actions button:before,
+.walkthrough a:before {
+ background-size: white;
+ display: block;
+ content: '';
+ height: 100px;
+ width: 100px;
+ margin: auto;
+ margin-bottom: 10px;
+ background:transparent url(<%= asset_path("iD/img/sprite.svg") %>) no-repeat 0 -220px;
+}
+
+.modal-actions :first-child {
+ border-right: 1px solid #CCC;
+}
+
+/* Restore Modal
+------------------------------------------------------- */
+
+.modal-actions .restore:before {
+ background-position: -400px -220px;
+}
+
+.modal-actions .reset:before {
+ background-position: -500px -220px;
+}
+
+/* Success Modal
+------------------------------------------------------- */
+
+.modal-actions .twitter:before {
+ background-position: -100px -220px;
+}
+
+/* Splash Modal
+------------------------------------------------------- */
+
+.modal-actions .walkthrough:before,
+.walkthrough a:before {
+ background-position: -200px -220px;
+}
+
+.modal-actions .start:before {
+ background-position: -300px -220px;
+}
+
+/* Commit Modal
+------------------------------------------------------- */
+
+.commit-modal a.user-info {
+ display: inline-block;
+}
+
+.commit-modal .commit-info {
+ margin-top: 10px;
+ padding-bottom: 20px;
+}
+
+.commit-modal .user-info img {
+ float: left;
+}
+
+.commit-modal h3 small.count {
+ margin-right: 10px;
+ text-align: center;
+ float: left;
+ height: 12px;
+ min-width: 12px;
+ font-size:12px;
+ line-height: 12px;
+ border-radius:24px;
+ padding:5px;
+ background:#7092ff;
+ color:#fff;
+}
+
+.commit-modal .changeset-list {
+ overflow: auto;
+ border:1px solid #ccc;
+ border-radius: 4px;
+ background:#fff;
+ max-height: 160px;
+}
+
+.commit-modal .warning-section .changeset-list button {
+ border-left: 1px solid #CCC;
+}
+
+.commit-modal .changeset-list li {
+ position: relative;
+ border-top:1px solid #ccc;
+ padding:5px 10px;
+}
+
+.changeset-list li span.count {
+ font-size:10px;
+ color:#555;
+}
+
+.changeset-list li span.count:before { content: '('; }
+
+.changeset-list li span.count:after { content: ')'; }
+
+.changeset-list li:first-child { border-top: 0;}
+
+/* Notices
+------------------------------------------------------- */
+
+.notice {
+ float:left;
+ width:25%;
+ padding-right: 10px;
+ text-align:center;
+}
+
+.notice .zoom-to {
+ width:100%;
+ height: 40px;
+ border-radius: 5px;
+ line-height: 40px;
+ background: #fff;
+ color: #000;
+ opacity: 0.9;
+}
+
+.notice .zoom-to:hover {
+ background: #d8e1ff;
+}
+
+.notice .zoom-to .icon {
+ margin-top:10px;
+ margin-right:10px;
+}
+
+.icon.zoom-in-invert {
+ background-position: -240px -40px;
+}
+
+/* Tooltips
+------------------------------------------------------- */
+
+.tooltip {
+ width: 200px;
+ position: absolute;
+ display: none;
+ color:#333;
+ text-align: left;
+ font-size: 12px;
+}
+
+.tooltip.in {
+ opacity: 0.8;
+ z-index: 1030;
+ height: auto;
+ display: block;
+}
+
+.tooltip.top {
+ margin-top: -20px;
+ text-align: center;
+}
+
+.tooltip.right {
+ margin-left: 20px;
+}
+
+.tooltip.bottom {
+ margin-top: 20px;
+ text-align: center;
+}
+
+.tooltip.left {
+ margin-left: -20px;
+}
+
+.tooltip-inner {
+ display: inline-block;
+ padding: 10px;
+ font-weight: normal;
+ background-color: white;
+}
+
+.tail {
+ width: 200px;
+ height: 400px;
+ pointer-events: none;
+ opacity: .8;
+ margin-top: -200px;
+ position: absolute;
+ background: transparent;
+}
+
+.tail::after {
+ content: "";
+ position: absolute;
+ width: 0;
+ height: 0;
+ border-color: transparent;
+ border-style: solid;
+ top: 50%;
+ right: -5px;
+ margin-top: -5px;
+ border-left-color: white;
+ border-width: 5px 0 5px 5px;
+}
+
+.tail div {
+ padding: 10px;
+ background: white;
+ position: absolute;
+ top: 180px;
+ left: 0;
+ right: 0;
+ margin: auto;
+}
+
+.left.tail::after {
+ content: "";
+ position: absolute;
+ width: 0;
+ height: 0;
+ border-color: transparent;
+ border-style: solid;
+ top: 50%;
+ left: -5px;
+ margin-top: -5px;
+ border-right-color: white;
+ border-width: 5px 5px 5px 0;
+}
+
+.tooltip-arrow {
+ position: absolute;
+ width: 0;
+ height: 0;
+ border-color: transparent;
+ border-style: solid;
+}
+
+.tooltip.top .tooltip-arrow {
+ bottom: -5px;
+ left: 50%;
+ margin-left: -5px;
+ border-top-color: white;
+ border-width: 5px 5px 0;
+}
+
+.tooltip.right .tooltip-arrow {
+ top: 50%;
+ left: -5px;
+ margin-top: -5px;
+ border-right-color: white;
+ border-width: 5px 5px 5px 0;
+}
+
+.tooltip.left .tooltip-arrow {
+ top: 50%;
+ right: -5px;
+ margin-top: -5px;
+ border-left-color: white;
+ border-width: 5px 0 5px 5px;
+}
+
+.tooltip.bottom .tooltip-arrow {
+ top: -5px;
+ left: 50%;
+ margin-left: -5px;
+ border-bottom-color: white;
+ border-width: 0 5px 5px;
+}
+
+
+/* Exceptions for tooltips that are up against the edge of the screen */
+.add-point .tooltip {
+ left: -20px !important; }
+
+.curtain-tooltip.intro-points-add .tooltip-arrow,
+.add-point .tooltip .tooltip-arrow {
+ left: 60px;
+}
+
+.tooltip .keyhint-wrap {
+ padding: 5px 0 5px 0;
+}
+
+.tooltip-inner .keyhint {
+ color: #222;
+ font-size: 10px;
+ padding: 0 7px;
+ font-weight: bold;
+ display: inline-block;
+ border-radius: 2px;
+ border: 1px solid #CCC;
+ position: relative;
+ z-index: 1;
+ text-align: left;
+ clear: both;
+}
+
+.tooltip .keyhint .keyhint-label {
+ display: inline-block;
+}
+
+.tooltip-inner .keyhint::after {
+ content: "";
+ position: absolute;
+ border-radius: 2px;
+ height: 10px;
+ width: 100%;
+ z-index: 0;
+ bottom: -4px;
+ left: -1px;
+ border: 1px solid #CCC;
+ border-top: 0;
+}
+
+.radial-menu-tooltip {
+ background-color: rgba(255, 255, 255, 0.8);
+ display: none;
+ position: absolute;
+ width: 200px;
+}
+
+.radial-menu-background {
+ stroke: black;
+ stroke-opacity: 0.5;
+}
+
+.radial-menu-item {
+ fill: white;
+}
+
+.radial-menu-item:hover {
+ fill: #ececec;
+}
+
+.radial-menu-item:active {
+ fill: #ececec;
+}
+
+.radial-menu-item.disabled {
+ cursor: auto;
+ fill: rgba(255,255,255,.5);
+}
+
+.radial-menu .icon {
+ pointer-events: none;
+}
+
+.lasso-box {
+ fill-opacity:0.1;
+ stroke: #fff;
+ stroke-width: 1;
+ stroke-opacity: 1;
+ stroke-dasharray: 5, 5;
+}
+
+/* Media Queries
+------------------------------------------------------- */
+
+@media only screen and (max-width: 840px) {
+ #bar .label {display: none;}
+ #bar .icon.icon-pre-text { margin-right: 0;}
+ /* override hide for save button */
+ #bar .save .label { display: block;}
+}
+
+/* Scrollbars
+ ----------------------------------------------------- */
+
+::-webkit-scrollbar {
+ height: 20px;
+ overflow: visible;
+ width: 10px;
+ background: white;
+ border-left: 1px solid #DDD;
+}
+
+::-webkit-scrollbar-track {
+ background-clip: padding-box;
+ border: solid transparent;
+ border-width: 0;
+}
+
+::-webkit-scrollbar-thumb {
+ background-color: rgba(0,0,0,.2);
+ background-clip: padding-box;
+ border: solid transparent;
+ border-width: 3px 3px 3px 4px;
+ border-radius: 6px;
+}
+::-webkit-scrollbar-track:hover,
+::-webkit-scrollbar-track:active {
+ background-color: rgba(0,0,0,.05);
+}
+
+/* Intro walkthrough
+ ----------------------------------------------------- */
+
+.curtain-darkness {
+ pointer-events: all;
+ fill-opacity: 0.7;
+ fill: #222;
+ fill-rule: evenodd;
+}
+
+.intro-nav-wrap {
+ position: absolute;
+ left: 0;
+ right: 0;
+ bottom: 30px;
+ padding: 10px;
+ z-index: 1001;
+}
+
+.intro-nav-wrap button.step {
+ width: 20%;
+}
+
+.intro-nav-wrap button.step.finished {
+ background: #8cd05f;
+}
+
+.intro-nav-wrap button.step .icon {
+ display: none;
+}
+
+.intro-nav-wrap button.step.finished .icon {
+ display: inline-block;
+}
+
+
+.curtain-tooltip .tooltip-inner {
+ text-align: left;
+ padding: 20px;
+}
+
+.curtain-tooltip .tooltip-inner {
+ font-size: 15px;
+}
+
+.curtain-tooltip .tooltip-inner .bold {
+ font-weight: bold;
+ display: block;
+ border-top: 1px solid #CCC;
+ margin-top: 10px;
+ margin-left: -20px;
+ margin-right: -20px;
+ padding: 10px 20px 0 20px;
+}
+
+.curtain-tooltip .tooltip-inner .bold:only-child {
+ border: 0;
+ padding: 0;
+ margin: 0;
+}
+
+.curtain-tooltip.intro-points-describe {
+ top: 133px !important;
+}
+
+/* Tooltip illustrations */
+
+.intro-points-add .tooltip-inner::before,
+.intro-areas-add .tooltip-inner::before,
+.intro-lines-add .tooltip-inner::before {
+ display: block;
+ content: "";
+ height: 80px;
+ width: 200px;
+ background:transparent url(<%= asset_path("iD/img/sprite.svg") %>) no-repeat 0 -320px;
+}
+
+.intro-areas-add .tooltip-inner::before {
+ background-position: 0 -400px;
+}
+
+.intro-lines-add .tooltip-inner::before {
+ background-position: 0 -480px;
+}
+
+.huge-modal-button {
+ width: 100%;
+ height: auto;
+ padding: 20px;
+}
+
+.huge-modal-button .illustration {
+ height: 100px;
+ width: 100px;
+ background: rgba(0, 0, 0, 0) url(<%= asset_path("iD/img/sprite.svg") %>) no-repeat -301px -220px;
+ margin: auto;
+}
+/* glue: 0.3 hash: 5d99d90788 */
+.feature-waterway-stream,
+.feature-waterway-river,
+.feature-waterway-ditch,
+.feature-waterway-canal,
+.feature-railway-subway,
+.feature-railway-rail,
+.feature-railway-monorail,
+.feature-railway-light_rail,
+.feature-railway-disused,
+.feature-railway-abandoned,
+.feature-power-line,
+.feature-other-line,
+.feature-highway-unclassified,
+.feature-highway-trunk,
+.feature-highway-trunk-link,
+.feature-highway-track,
+.feature-highway-tertiary,
+.feature-highway-tertiary-link,
+.feature-highway-steps,
+.feature-highway-service,
+.feature-highway-secondary,
+.feature-highway-secondary-link,
+.feature-highway-road,
+.feature-highway-residential,
+.feature-highway-primary,
+.feature-highway-primary-link,
+.feature-highway-path,
+.feature-highway-motorway,
+.feature-highway-motorway-link,
+.feature-highway-living_street,
+.feature-highway-footway,
+.feature-highway-cycleway,
+.feature-highway-bridleway,
+.feature-category-water,
+.feature-category-roads,
+.feature-category-rail,
+.feature-category-path,
+.feature-zoo,
+.feature-x,
+.feature-wetland,
+.feature-water,
+.feature-waste-basket,
+.feature-warehouse,
+.feature-triangle,
+.feature-triangle-stroked,
+.feature-town-hall,
+.feature-toilets,
+.feature-theatre,
+.feature-tennis,
+.feature-swimming,
+.feature-star,
+.feature-star-stroked,
+.feature-square,
+.feature-square-stroked,
+.feature-soccer,
+.feature-slaughterhouse,
+.feature-skiing,
+.feature-shop,
+.feature-school,
+.feature-roadblock,
+.feature-restaurant,
+.feature-religious-muslim,
+.feature-religious-jewish,
+.feature-religious-christian,
+.feature-rail,
+.feature-rail-underground,
+.feature-rail-above,
+.feature-prison,
+.feature-post,
+.feature-police,
+.feature-place-of-worship,
+.feature-pitch,
+.feature-pharmacy,
+.feature-parking,
+.feature-parking-garage,
+.feature-park2,
+.feature-park,
+.feature-oil-well,
+.feature-museum,
+.feature-monument,
+.feature-minefield,
+.feature-marker,
+.feature-marker-stroked,
+.feature-london-underground,
+.feature-logging,
+.feature-lodging,
+.feature-library,
+.feature-industrial,
+.feature-hospital,
+.feature-heliport,
+.feature-harbor,
+.feature-grocery,
+.feature-golf,
+.feature-garden,
+.feature-fuel,
+.feature-fire-station,
+.feature-ferry,
+.feature-fast-food,
+.feature-embassy,
+.feature-danger,
+.feature-dam,
+.feature-cross,
+.feature-cricket,
+.feature-commercial,
+.feature-college,
+.feature-circle,
+.feature-circle-stroked,
+.feature-cinema,
+.feature-cemetery,
+.feature-campsite,
+.feature-cafe,
+.feature-bus,
+.feature-bicycle,
+.feature-beer,
+.feature-basketball,
+.feature-baseball,
+.feature-bar,
+.feature-bank,
+.feature-art-gallery,
+.feature-america-football,
+.feature-alcohol-shop,
+.feature-airport,
+.feature-airfield,
+.feature-zoo-18,
+.feature-x-18,
+.feature-wetland-18,
+.feature-water-18,
+.feature-waste-basket-18,
+.feature-warehouse-18,
+.feature-triangle-stroked-18,
+.feature-triangle-18,
+.feature-town-hall-18,
+.feature-toilets-18,
+.feature-theatre-18,
+.feature-tennis-18,
+.feature-swimming-18,
+.feature-star-stroked-18,
+.feature-star-18,
+.feature-square-stroked-18,
+.feature-square-18,
+.feature-soccer-18,
+.feature-slaughterhouse-18,
+.feature-skiing-18,
+.feature-shop-18,
+.feature-school-18,
+.feature-roadblock-18,
+.feature-restaurant-18,
+.feature-religious-muslim-18,
+.feature-religious-jewish-18,
+.feature-religious-christian-18,
+.feature-rail-underground-18,
+.feature-rail-above-18,
+.feature-rail-18,
+.feature-prison-18,
+.feature-post-18,
+.feature-police-18,
+.feature-place-of-worship-18,
+.feature-pitch-18,
+.feature-pharmacy-18,
+.feature-parking-garage-18,
+.feature-parking-18,
+.feature-park2-18,
+.feature-park-18,
+.feature-oil-well-18,
+.feature-museum-18,
+.feature-monument-18,
+.feature-minefield-18,
+.feature-marker-stroked-18,
+.feature-marker-18,
+.feature-london-underground-18,
+.feature-logging-18,
+.feature-lodging-18,
+.feature-library-18,
+.feature-industrial-18,
+.feature-hospital-18,
+.feature-heliport-18,
+.feature-harbor-18,
+.feature-grocery-18,
+.feature-golf-18,
+.feature-garden-18,
+.feature-fuel-18,
+.feature-fire-station-18,
+.feature-ferry-18,
+.feature-fast-food-18,
+.feature-embassy-18,
+.feature-danger-18,
+.feature-dam-18,
+.feature-cross-18,
+.feature-cricket-18,
+.feature-commercial-18,
+.feature-college-18,
+.feature-circle-stroked-18,
+.feature-circle-18,
+.feature-cinema-18,
+.feature-cemetery-18,
+.feature-campsite-18,
+.feature-cafe-18,
+.feature-bus-18,
+.feature-bicycle-18,
+.feature-beer-18,
+.feature-basketball-18,
+.feature-baseball-18,
+.feature-bar-18,
+.feature-bank-18,
+.feature-art-gallery-18,
+.feature-america-football-18,
+.feature-alcohol-shop-18,
+.feature-airport-18,
+.feature-airfield-18,
+.feature-zoo-12,
+.feature-x-12,
+.feature-wetland-12,
+.feature-water-12,
+.feature-waste-basket-12,
+.feature-warehouse-12,
+.feature-triangle-stroked-12,
+.feature-triangle-12,
+.feature-town-hall-12,
+.feature-toilets-12,
+.feature-theatre-12,
+.feature-tennis-12,
+.feature-swimming-12,
+.feature-star-stroked-12,
+.feature-star-12,
+.feature-square-stroked-12,
+.feature-square-12,
+.feature-soccer-12,
+.feature-slaughterhouse-12,
+.feature-skiing-12,
+.feature-shop-12,
+.feature-school-12,
+.feature-roadblock-12,
+.feature-restaurant-12,
+.feature-religious-muslim-12,
+.feature-religious-jewish-12,
+.feature-religious-christian-12,
+.feature-rail-underground-12,
+.feature-rail-above-12,
+.feature-rail-12,
+.feature-prison-12,
+.feature-post-12,
+.feature-police-12,
+.feature-place-of-worship-12,
+.feature-pitch-12,
+.feature-pharmacy-12,
+.feature-parking-garage-12,
+.feature-parking-12,
+.feature-park2-12,
+.feature-park-12,
+.feature-oil-well-12,
+.feature-museum-12,
+.feature-monument-12,
+.feature-minefield-12,
+.feature-marker-stroked-12,
+.feature-marker-12,
+.feature-london-underground-12,
+.feature-logging-12,
+.feature-lodging-12,
+.feature-library-12,
+.feature-industrial-12,
+.feature-hospital-12,
+.feature-heliport-12,
+.feature-harbor-12,
+.feature-grocery-12,
+.feature-golf-12,
+.feature-garden-12,
+.feature-fuel-12,
+.feature-fire-station-12,
+.feature-ferry-12,
+.feature-fast-food-12,
+.feature-embassy-12,
+.feature-danger-12,
+.feature-dam-12,
+.feature-cross-12,
+.feature-cricket-12,
+.feature-commercial-12,
+.feature-college-12,
+.feature-circle-stroked-12,
+.feature-circle-12,
+.feature-cinema-12,
+.feature-cemetery-12,
+.feature-campsite-12,
+.feature-cafe-12,
+.feature-bus-12,
+.feature-bicycle-12,
+.feature-beer-12,
+.feature-basketball-12,
+.feature-baseball-12,
+.feature-bar-12,
+.feature-bank-12,
+.feature-art-gallery-12,
+.feature-america-football-12,
+.feature-alcohol-shop-12,
+.feature-airport-12,
+.feature-airfield-12{background-image:url(<%= asset_path("iD/img/feature-icons.png") %>);background-repeat:no-repeat}
+.feature-waterway-stream{background-position:0px 0px;width:60px;height:60px;}
+.feature-waterway-river{background-position:-60px 0px;width:60px;height:60px;}
+.feature-waterway-ditch{background-position:0px -60px;width:60px;height:60px;}
+.feature-waterway-canal{background-position:-60px -60px;width:60px;height:60px;}
+.feature-railway-subway{background-position:-120px 0px;width:60px;height:60px;}
+.feature-railway-rail{background-position:-120px -60px;width:60px;height:60px;}
+.feature-railway-monorail{background-position:0px -120px;width:60px;height:60px;}
+.feature-railway-light_rail{background-position:-60px -120px;width:60px;height:60px;}
+.feature-railway-disused{background-position:-120px -120px;width:60px;height:60px;}
+.feature-railway-abandoned{background-position:-180px 0px;width:60px;height:60px;}
+.feature-power-line{background-position:-180px -60px;width:60px;height:60px;}
+.feature-other-line{background-position:-180px -120px;width:60px;height:60px;}
+.feature-highway-unclassified{background-position:0px -180px;width:60px;height:60px;}
+.feature-highway-trunk{background-position:-60px -180px;width:60px;height:60px;}
+.feature-highway-trunk-link{background-position:-120px -180px;width:60px;height:60px;}
+.feature-highway-track{background-position:-180px -180px;width:60px;height:60px;}
+.feature-highway-tertiary{background-position:-240px 0px;width:60px;height:60px;}
+.feature-highway-tertiary-link{background-position:-240px -60px;width:60px;height:60px;}
+.feature-highway-steps{background-position:-240px -120px;width:60px;height:60px;}
+.feature-highway-service{background-position:-240px -180px;width:60px;height:60px;}
+.feature-highway-secondary{background-position:0px -240px;width:60px;height:60px;}
+.feature-highway-secondary-link{background-position:-60px -240px;width:60px;height:60px;}
+.feature-highway-road{background-position:-120px -240px;width:60px;height:60px;}
+.feature-highway-residential{background-position:-180px -240px;width:60px;height:60px;}
+.feature-highway-primary{background-position:-240px -240px;width:60px;height:60px;}
+.feature-highway-primary-link{background-position:-300px 0px;width:60px;height:60px;}
+.feature-highway-path{background-position:-300px -60px;width:60px;height:60px;}
+.feature-highway-motorway{background-position:-300px -120px;width:60px;height:60px;}
+.feature-highway-motorway-link{background-position:-300px -180px;width:60px;height:60px;}
+.feature-highway-living_street{background-position:-300px -240px;width:60px;height:60px;}
+.feature-highway-footway{background-position:0px -300px;width:60px;height:60px;}
+.feature-highway-cycleway{background-position:-60px -300px;width:60px;height:60px;}
+.feature-highway-bridleway{background-position:-120px -300px;width:60px;height:60px;}
+.feature-category-water{background-position:-180px -300px;width:60px;height:60px;}
+.feature-category-roads{background-position:-240px -300px;width:60px;height:60px;}
+.feature-category-rail{background-position:-300px -300px;width:60px;height:60px;}
+.feature-category-path{background-position:-360px 0px;width:60px;height:60px;}
+.feature-zoo{background-position:-360px -60px;width:24px;height:24px;}
+.feature-x{background-position:-384px -60px;width:24px;height:24px;}
+.feature-wetland{background-position:-360px -84px;width:24px;height:24px;}
+.feature-water{background-position:-384px -84px;width:24px;height:24px;}
+.feature-waste-basket{background-position:-360px -108px;width:24px;height:24px;}
+.feature-warehouse{background-position:-384px -108px;width:24px;height:24px;}
+.feature-triangle{background-position:-360px -132px;width:24px;height:24px;}
+.feature-triangle-stroked{background-position:-384px -132px;width:24px;height:24px;}
+.feature-town-hall{background-position:-360px -156px;width:24px;height:24px;}
+.feature-toilets{background-position:-384px -156px;width:24px;height:24px;}
+.feature-theatre{background-position:-360px -180px;width:24px;height:24px;}
+.feature-tennis{background-position:-384px -180px;width:24px;height:24px;}
+.feature-swimming{background-position:-360px -204px;width:24px;height:24px;}
+.feature-star{background-position:-384px -204px;width:24px;height:24px;}
+.feature-star-stroked{background-position:-360px -228px;width:24px;height:24px;}
+.feature-square{background-position:-384px -228px;width:24px;height:24px;}
+.feature-square-stroked{background-position:-360px -252px;width:24px;height:24px;}
+.feature-soccer{background-position:-384px -252px;width:24px;height:24px;}
+.feature-slaughterhouse{background-position:-360px -276px;width:24px;height:24px;}
+.feature-skiing{background-position:-384px -276px;width:24px;height:24px;}
+.feature-shop{background-position:-360px -300px;width:24px;height:24px;}
+.feature-school{background-position:-384px -300px;width:24px;height:24px;}
+.feature-roadblock{background-position:-360px -324px;width:24px;height:24px;}
+.feature-restaurant{background-position:-384px -324px;width:24px;height:24px;}
+.feature-religious-muslim{background-position:0px -360px;width:24px;height:24px;}
+.feature-religious-jewish{background-position:-24px -360px;width:24px;height:24px;}
+.feature-religious-christian{background-position:-48px -360px;width:24px;height:24px;}
+.feature-rail{background-position:-72px -360px;width:24px;height:24px;}
+.feature-rail-underground{background-position:-96px -360px;width:24px;height:24px;}
+.feature-rail-above{background-position:-120px -360px;width:24px;height:24px;}
+.feature-prison{background-position:-144px -360px;width:24px;height:24px;}
+.feature-post{background-position:-168px -360px;width:24px;height:24px;}
+.feature-police{background-position:-192px -360px;width:24px;height:24px;}
+.feature-place-of-worship{background-position:-216px -360px;width:24px;height:24px;}
+.feature-pitch{background-position:-240px -360px;width:24px;height:24px;}
+.feature-pharmacy{background-position:-264px -360px;width:24px;height:24px;}
+.feature-parking{background-position:-288px -360px;width:24px;height:24px;}
+.feature-parking-garage{background-position:-312px -360px;width:24px;height:24px;}
+.feature-park2{background-position:-336px -360px;width:24px;height:24px;}
+.feature-park{background-position:-360px -360px;width:24px;height:24px;}
+.feature-oil-well{background-position:-384px -360px;width:24px;height:24px;}
+.feature-museum{background-position:0px -384px;width:24px;height:24px;}
+.feature-monument{background-position:-24px -384px;width:24px;height:24px;}
+.feature-minefield{background-position:-48px -384px;width:24px;height:24px;}
+.feature-marker{background-position:-72px -384px;width:24px;height:24px;}
+.feature-marker-stroked{background-position:-96px -384px;width:24px;height:24px;}
+.feature-london-underground{background-position:-120px -384px;width:24px;height:24px;}
+.feature-logging{background-position:-144px -384px;width:24px;height:24px;}
+.feature-lodging{background-position:-168px -384px;width:24px;height:24px;}
+.feature-library{background-position:-192px -384px;width:24px;height:24px;}
+.feature-industrial{background-position:-216px -384px;width:24px;height:24px;}
+.feature-hospital{background-position:-240px -384px;width:24px;height:24px;}
+.feature-heliport{background-position:-264px -384px;width:24px;height:24px;}
+.feature-harbor{background-position:-288px -384px;width:24px;height:24px;}
+.feature-grocery{background-position:-312px -384px;width:24px;height:24px;}
+.feature-golf{background-position:-336px -384px;width:24px;height:24px;}
+.feature-garden{background-position:-360px -384px;width:24px;height:24px;}
+.feature-fuel{background-position:-384px -384px;width:24px;height:24px;}
+.feature-fire-station{background-position:-420px 0px;width:24px;height:24px;}
+.feature-ferry{background-position:-420px -24px;width:24px;height:24px;}
+.feature-fast-food{background-position:-420px -48px;width:24px;height:24px;}
+.feature-embassy{background-position:-420px -72px;width:24px;height:24px;}
+.feature-danger{background-position:-420px -96px;width:24px;height:24px;}
+.feature-dam{background-position:-420px -120px;width:24px;height:24px;}
+.feature-cross{background-position:-420px -144px;width:24px;height:24px;}
+.feature-cricket{background-position:-420px -168px;width:24px;height:24px;}
+.feature-commercial{background-position:-420px -192px;width:24px;height:24px;}
+.feature-college{background-position:-420px -216px;width:24px;height:24px;}
+.feature-circle{background-position:-420px -240px;width:24px;height:24px;}
+.feature-circle-stroked{background-position:-420px -264px;width:24px;height:24px;}
+.feature-cinema{background-position:-420px -288px;width:24px;height:24px;}
+.feature-cemetery{background-position:-420px -312px;width:24px;height:24px;}
+.feature-campsite{background-position:-420px -336px;width:24px;height:24px;}
+.feature-cafe{background-position:-420px -360px;width:24px;height:24px;}
+.feature-bus{background-position:-420px -384px;width:24px;height:24px;}
+.feature-bicycle{background-position:0px -408px;width:24px;height:24px;}
+.feature-beer{background-position:-24px -408px;width:24px;height:24px;}
+.feature-basketball{background-position:-48px -408px;width:24px;height:24px;}
+.feature-baseball{background-position:-72px -408px;width:24px;height:24px;}
+.feature-bar{background-position:-96px -408px;width:24px;height:24px;}
+.feature-bank{background-position:-120px -408px;width:24px;height:24px;}
+.feature-art-gallery{background-position:-144px -408px;width:24px;height:24px;}
+.feature-america-football{background-position:-168px -408px;width:24px;height:24px;}
+.feature-alcohol-shop{background-position:-192px -408px;width:24px;height:24px;}
+.feature-airport{background-position:-216px -408px;width:24px;height:24px;}
+.feature-airfield{background-position:-240px -408px;width:24px;height:24px;}
+.feature-zoo-18{background-position:-264px -408px;width:18px;height:18px;}
+.feature-x-18{background-position:-282px -408px;width:18px;height:18px;}
+.feature-wetland-18{background-position:-300px -408px;width:18px;height:18px;}
+.feature-water-18{background-position:-318px -408px;width:18px;height:18px;}
+.feature-waste-basket-18{background-position:-336px -408px;width:18px;height:18px;}
+.feature-warehouse-18{background-position:-354px -408px;width:18px;height:18px;}
+.feature-triangle-stroked-18{background-position:-372px -408px;width:18px;height:18px;}
+.feature-triangle-18{background-position:-390px -408px;width:18px;height:18px;}
+.feature-town-hall-18{background-position:-408px -408px;width:18px;height:18px;}
+.feature-toilets-18{background-position:-426px -408px;width:18px;height:18px;}
+.feature-theatre-18{background-position:-444px 0px;width:18px;height:18px;}
+.feature-tennis-18{background-position:-444px -18px;width:18px;height:18px;}
+.feature-swimming-18{background-position:-444px -36px;width:18px;height:18px;}
+.feature-star-stroked-18{background-position:-444px -54px;width:18px;height:18px;}
+.feature-star-18{background-position:-444px -72px;width:18px;height:18px;}
+.feature-square-stroked-18{background-position:-444px -90px;width:18px;height:18px;}
+.feature-square-18{background-position:-444px -108px;width:18px;height:18px;}
+.feature-soccer-18{background-position:-444px -126px;width:18px;height:18px;}
+.feature-slaughterhouse-18{background-position:-444px -144px;width:18px;height:18px;}
+.feature-skiing-18{background-position:-444px -162px;width:18px;height:18px;}
+.feature-shop-18{background-position:-444px -180px;width:18px;height:18px;}
+.feature-school-18{background-position:-444px -198px;width:18px;height:18px;}
+.feature-roadblock-18{background-position:-444px -216px;width:18px;height:18px;}
+.feature-restaurant-18{background-position:-444px -234px;width:18px;height:18px;}
+.feature-religious-muslim-18{background-position:-444px -252px;width:18px;height:18px;}
+.feature-religious-jewish-18{background-position:-444px -270px;width:18px;height:18px;}
+.feature-religious-christian-18{background-position:-444px -288px;width:18px;height:18px;}
+.feature-rail-underground-18{background-position:-444px -306px;width:18px;height:18px;}
+.feature-rail-above-18{background-position:-444px -324px;width:18px;height:18px;}
+.feature-rail-18{background-position:-444px -342px;width:18px;height:18px;}
+.feature-prison-18{background-position:-444px -360px;width:18px;height:18px;}
+.feature-post-18{background-position:-444px -378px;width:18px;height:18px;}
+.feature-police-18{background-position:-444px -396px;width:18px;height:18px;}
+.feature-place-of-worship-18{background-position:-444px -414px;width:18px;height:18px;}
+.feature-pitch-18{background-position:0px -432px;width:18px;height:18px;}
+.feature-pharmacy-18{background-position:-18px -432px;width:18px;height:18px;}
+.feature-parking-garage-18{background-position:-36px -432px;width:18px;height:18px;}
+.feature-parking-18{background-position:-54px -432px;width:18px;height:18px;}
+.feature-park2-18{background-position:-72px -432px;width:18px;height:18px;}
+.feature-park-18{background-position:-90px -432px;width:18px;height:18px;}
+.feature-oil-well-18{background-position:-108px -432px;width:18px;height:18px;}
+.feature-museum-18{background-position:-126px -432px;width:18px;height:18px;}
+.feature-monument-18{background-position:-144px -432px;width:18px;height:18px;}
+.feature-minefield-18{background-position:-162px -432px;width:18px;height:18px;}
+.feature-marker-stroked-18{background-position:-180px -432px;width:18px;height:18px;}
+.feature-marker-18{background-position:-198px -432px;width:18px;height:18px;}
+.feature-london-underground-18{background-position:-216px -432px;width:18px;height:18px;}
+.feature-logging-18{background-position:-234px -432px;width:18px;height:18px;}
+.feature-lodging-18{background-position:-252px -432px;width:18px;height:18px;}
+.feature-library-18{background-position:-270px -432px;width:18px;height:18px;}
+.feature-industrial-18{background-position:-288px -432px;width:18px;height:18px;}
+.feature-hospital-18{background-position:-306px -432px;width:18px;height:18px;}
+.feature-heliport-18{background-position:-324px -432px;width:18px;height:18px;}
+.feature-harbor-18{background-position:-342px -432px;width:18px;height:18px;}
+.feature-grocery-18{background-position:-360px -432px;width:18px;height:18px;}
+.feature-golf-18{background-position:-378px -432px;width:18px;height:18px;}
+.feature-garden-18{background-position:-396px -432px;width:18px;height:18px;}
+.feature-fuel-18{background-position:-414px -432px;width:18px;height:18px;}
+.feature-fire-station-18{background-position:-432px -432px;width:18px;height:18px;}
+.feature-ferry-18{background-position:-462px 0px;width:18px;height:18px;}
+.feature-fast-food-18{background-position:-462px -18px;width:18px;height:18px;}
+.feature-embassy-18{background-position:-462px -36px;width:18px;height:18px;}
+.feature-danger-18{background-position:-462px -54px;width:18px;height:18px;}
+.feature-dam-18{background-position:-462px -72px;width:18px;height:18px;}
+.feature-cross-18{background-position:-462px -90px;width:18px;height:18px;}
+.feature-cricket-18{background-position:-462px -108px;width:18px;height:18px;}
+.feature-commercial-18{background-position:-462px -126px;width:18px;height:18px;}
+.feature-college-18{background-position:-462px -144px;width:18px;height:18px;}
+.feature-circle-stroked-18{background-position:-462px -162px;width:18px;height:18px;}
+.feature-circle-18{background-position:-462px -180px;width:18px;height:18px;}
+.feature-cinema-18{background-position:-462px -198px;width:18px;height:18px;}
+.feature-cemetery-18{background-position:-462px -216px;width:18px;height:18px;}
+.feature-campsite-18{background-position:-462px -234px;width:18px;height:18px;}
+.feature-cafe-18{background-position:-462px -252px;width:18px;height:18px;}
+.feature-bus-18{background-position:-462px -270px;width:18px;height:18px;}
+.feature-bicycle-18{background-position:-462px -288px;width:18px;height:18px;}
+.feature-beer-18{background-position:-462px -306px;width:18px;height:18px;}
+.feature-basketball-18{background-position:-462px -324px;width:18px;height:18px;}
+.feature-baseball-18{background-position:-462px -342px;width:18px;height:18px;}
+.feature-bar-18{background-position:-462px -360px;width:18px;height:18px;}
+.feature-bank-18{background-position:-462px -378px;width:18px;height:18px;}
+.feature-art-gallery-18{background-position:-462px -396px;width:18px;height:18px;}
+.feature-america-football-18{background-position:-462px -414px;width:18px;height:18px;}
+.feature-alcohol-shop-18{background-position:-462px -432px;width:18px;height:18px;}
+.feature-airport-18{background-position:0px -450px;width:18px;height:18px;}
+.feature-airfield-18{background-position:-18px -450px;width:18px;height:18px;}
+.feature-zoo-12{background-position:-408px -60px;width:12px;height:12px;}
+.feature-x-12{background-position:-408px -72px;width:12px;height:12px;}
+.feature-wetland-12{background-position:-408px -84px;width:12px;height:12px;}
+.feature-water-12{background-position:-408px -96px;width:12px;height:12px;}
+.feature-waste-basket-12{background-position:-408px -108px;width:12px;height:12px;}
+.feature-warehouse-12{background-position:-408px -120px;width:12px;height:12px;}
+.feature-triangle-stroked-12{background-position:-408px -132px;width:12px;height:12px;}
+.feature-triangle-12{background-position:-408px -144px;width:12px;height:12px;}
+.feature-town-hall-12{background-position:-408px -156px;width:12px;height:12px;}
+.feature-toilets-12{background-position:-408px -168px;width:12px;height:12px;}
+.feature-theatre-12{background-position:-408px -180px;width:12px;height:12px;}
+.feature-tennis-12{background-position:-408px -192px;width:12px;height:12px;}
+.feature-swimming-12{background-position:-408px -204px;width:12px;height:12px;}
+.feature-star-stroked-12{background-position:-408px -216px;width:12px;height:12px;}
+.feature-star-12{background-position:-408px -228px;width:12px;height:12px;}
+.feature-square-stroked-12{background-position:-408px -240px;width:12px;height:12px;}
+.feature-square-12{background-position:-408px -252px;width:12px;height:12px;}
+.feature-soccer-12{background-position:-408px -264px;width:12px;height:12px;}
+.feature-slaughterhouse-12{background-position:-408px -276px;width:12px;height:12px;}
+.feature-skiing-12{background-position:-408px -288px;width:12px;height:12px;}
+.feature-shop-12{background-position:-408px -300px;width:12px;height:12px;}
+.feature-school-12{background-position:-408px -312px;width:12px;height:12px;}
+.feature-roadblock-12{background-position:-408px -324px;width:12px;height:12px;}
+.feature-restaurant-12{background-position:-408px -336px;width:12px;height:12px;}
+.feature-religious-muslim-12{background-position:-360px -348px;width:12px;height:12px;}
+.feature-religious-jewish-12{background-position:-372px -348px;width:12px;height:12px;}
+.feature-religious-christian-12{background-position:-384px -348px;width:12px;height:12px;}
+.feature-rail-underground-12{background-position:-396px -348px;width:12px;height:12px;}
+.feature-rail-above-12{background-position:-408px -348px;width:12px;height:12px;}
+.feature-rail-12{background-position:-408px -360px;width:12px;height:12px;}
+.feature-prison-12{background-position:-408px -372px;width:12px;height:12px;}
+.feature-post-12{background-position:-408px -384px;width:12px;height:12px;}
+.feature-police-12{background-position:-408px -396px;width:12px;height:12px;}
+.feature-place-of-worship-12{background-position:-450px -432px;width:12px;height:12px;}
+.feature-pitch-12{background-position:-36px -450px;width:12px;height:12px;}
+.feature-pharmacy-12{background-position:-48px -450px;width:12px;height:12px;}
+.feature-parking-garage-12{background-position:-60px -450px;width:12px;height:12px;}
+.feature-parking-12{background-position:-72px -450px;width:12px;height:12px;}
+.feature-park2-12{background-position:-84px -450px;width:12px;height:12px;}
+.feature-park-12{background-position:-96px -450px;width:12px;height:12px;}
+.feature-oil-well-12{background-position:-108px -450px;width:12px;height:12px;}
+.feature-museum-12{background-position:-120px -450px;width:12px;height:12px;}
+.feature-monument-12{background-position:-132px -450px;width:12px;height:12px;}
+.feature-minefield-12{background-position:-144px -450px;width:12px;height:12px;}
+.feature-marker-stroked-12{background-position:-156px -450px;width:12px;height:12px;}
+.feature-marker-12{background-position:-168px -450px;width:12px;height:12px;}
+.feature-london-underground-12{background-position:-180px -450px;width:12px;height:12px;}
+.feature-logging-12{background-position:-192px -450px;width:12px;height:12px;}
+.feature-lodging-12{background-position:-204px -450px;width:12px;height:12px;}
+.feature-library-12{background-position:-216px -450px;width:12px;height:12px;}
+.feature-industrial-12{background-position:-228px -450px;width:12px;height:12px;}
+.feature-hospital-12{background-position:-240px -450px;width:12px;height:12px;}
+.feature-heliport-12{background-position:-252px -450px;width:12px;height:12px;}
+.feature-harbor-12{background-position:-264px -450px;width:12px;height:12px;}
+.feature-grocery-12{background-position:-276px -450px;width:12px;height:12px;}
+.feature-golf-12{background-position:-288px -450px;width:12px;height:12px;}
+.feature-garden-12{background-position:-300px -450px;width:12px;height:12px;}
+.feature-fuel-12{background-position:-312px -450px;width:12px;height:12px;}
+.feature-fire-station-12{background-position:-324px -450px;width:12px;height:12px;}
+.feature-ferry-12{background-position:-336px -450px;width:12px;height:12px;}
+.feature-fast-food-12{background-position:-348px -450px;width:12px;height:12px;}
+.feature-embassy-12{background-position:-360px -450px;width:12px;height:12px;}
+.feature-danger-12{background-position:-372px -450px;width:12px;height:12px;}
+.feature-dam-12{background-position:-384px -450px;width:12px;height:12px;}
+.feature-cross-12{background-position:-396px -450px;width:12px;height:12px;}
+.feature-cricket-12{background-position:-408px -450px;width:12px;height:12px;}
+.feature-commercial-12{background-position:-420px -450px;width:12px;height:12px;}
+.feature-college-12{background-position:-432px -450px;width:12px;height:12px;}
+.feature-circle-stroked-12{background-position:-444px -450px;width:12px;height:12px;}
+.feature-circle-12{background-position:-456px -450px;width:12px;height:12px;}
+.feature-cinema-12{background-position:-468px -450px;width:12px;height:12px;}
+.feature-cemetery-12{background-position:0px -468px;width:12px;height:12px;}
+.feature-campsite-12{background-position:-12px -468px;width:12px;height:12px;}
+.feature-cafe-12{background-position:-24px -468px;width:12px;height:12px;}
+.feature-bus-12{background-position:-36px -468px;width:12px;height:12px;}
+.feature-bicycle-12{background-position:-48px -468px;width:12px;height:12px;}
+.feature-beer-12{background-position:-60px -468px;width:12px;height:12px;}
+.feature-basketball-12{background-position:-72px -468px;width:12px;height:12px;}
+.feature-baseball-12{background-position:-84px -468px;width:12px;height:12px;}
+.feature-bar-12{background-position:-96px -468px;width:12px;height:12px;}
+.feature-bank-12{background-position:-108px -468px;width:12px;height:12px;}
+.feature-art-gallery-12{background-position:-120px -468px;width:12px;height:12px;}
+.feature-america-football-12{background-position:-132px -468px;width:12px;height:12px;}
+.feature-alcohol-shop-12{background-position:-144px -468px;width:12px;height:12px;}
+.feature-airport-12{background-position:-156px -468px;width:12px;height:12px;}
+.feature-airfield-12{background-position:-168px -468px;width:12px;height:12px;}
diff --git a/vendor/assets/iD/iD.js b/vendor/assets/iD/iD.js
new file mode 100644
index 000000000..11dd2f7b0
--- /dev/null
+++ b/vendor/assets/iD/iD.js
@@ -0,0 +1,70963 @@
+(function(exports) {
+
+ var bootstrap = (typeof exports.bootstrap === "object") ?
+ exports.bootstrap :
+ (exports.bootstrap = {});
+
+ bootstrap.tooltip = function() {
+
+ var tooltip = function(selection) {
+ selection.each(setup);
+ },
+ animation = d3.functor(false),
+ html = d3.functor(false),
+ title = function() {
+ var title = this.getAttribute("data-original-title");
+ if (title) {
+ return title;
+ } else {
+ title = this.getAttribute("title");
+ this.removeAttribute("title");
+ this.setAttribute("data-original-title", title);
+ }
+ return title;
+ },
+ over = "mouseenter.tooltip",
+ out = "mouseleave.tooltip",
+ placements = "top left bottom right".split(" "),
+ placement = d3.functor("top");
+
+ tooltip.title = function(_) {
+ if (arguments.length) {
+ title = d3.functor(_);
+ return tooltip;
+ } else {
+ return title;
+ }
+ };
+
+ tooltip.html = function(_) {
+ if (arguments.length) {
+ html = d3.functor(_);
+ return tooltip;
+ } else {
+ return html;
+ }
+ };
+
+ tooltip.placement = function(_) {
+ if (arguments.length) {
+ placement = d3.functor(_);
+ return tooltip;
+ } else {
+ return placement;
+ }
+ };
+
+ tooltip.show = function(selection) {
+ selection.each(show);
+ };
+
+ tooltip.hide = function(selection) {
+ selection.each(hide);
+ };
+
+ tooltip.toggle = function(selection) {
+ selection.each(toggle);
+ };
+
+ tooltip.destroy = function(selection) {
+ selection
+ .on(over, null)
+ .on(out, null)
+ .attr("title", function() {
+ return this.getAttribute("data-original-title") || this.getAttribute("title");
+ })
+ .attr("data-original-title", null)
+ .select(".tooltip")
+ .remove();
+ };
+
+ function setup() {
+ var root = d3.select(this),
+ animate = animation.apply(this, arguments),
+ tip = root.append("div")
+ .attr("class", "tooltip");
+
+ if (animate) {
+ tip.classed("fade", true);
+ }
+
+ // TODO "inside" checks?
+
+ tip.append("div")
+ .attr("class", "tooltip-arrow");
+ tip.append("div")
+ .attr("class", "tooltip-inner");
+
+ var place = placement.apply(this, arguments);
+ tip.classed(place, true);
+
+ root.on(over, show);
+ root.on(out, hide);
+ }
+
+ function show() {
+ var root = d3.select(this),
+ content = title.apply(this, arguments),
+ tip = root.select(".tooltip")
+ .classed("in", true),
+ markup = html.apply(this, arguments),
+ innercontent = tip.select(".tooltip-inner")[markup ? "html" : "text"](content),
+ place = placement.apply(this, arguments),
+ outer = getPosition(root.node()),
+ inner = getPosition(tip.node()),
+ pos;
+
+ switch (place) {
+ case "top":
+ pos = {x: outer.x + (outer.w - inner.w) / 2, y: outer.y - inner.h};
+ break;
+ case "right":
+ pos = {x: outer.x + outer.w, y: outer.y + (outer.h - inner.h) / 2};
+ break;
+ case "left":
+ pos = {x: outer.x - inner.w, y: outer.y + (outer.h - inner.h) / 2};
+ break;
+ case "bottom":
+ pos = {x: Math.max(0, outer.x + (outer.w - inner.w) / 2), y: outer.y + outer.h};
+ break;
+ }
+
+ tip.style(pos ?
+ {left: ~~pos.x + "px", top: ~~pos.y + "px"} :
+ {left: null, top: null});
+
+ this.tooltipVisible = true;
+ }
+
+ function hide() {
+ d3.select(this).select(".tooltip")
+ .classed("in", false);
+
+ this.tooltipVisible = false;
+ }
+
+ function toggle() {
+ if (this.tooltipVisible) {
+ hide.apply(this, arguments);
+ } else {
+ show.apply(this, arguments);
+ }
+ }
+
+ return tooltip;
+ };
+
+ function getPosition(node) {
+ var mode = d3.select(node).style('position');
+ if (mode === 'absolute' || mode === 'static') {
+ return {
+ x: node.offsetLeft,
+ y: node.offsetTop,
+ w: node.offsetWidth,
+ h: node.offsetHeight
+ };
+ } else {
+ return {
+ x: 0,
+ y: 0,
+ w: node.offsetWidth,
+ h: node.offsetHeight
+ };
+ }
+ }
+
+})(this);
+d3 = (function(){
+ var d3 = {version: "3.1.4"}; // semver
+d3.ascending = function(a, b) {
+ return a < b ? -1 : a > b ? 1 : a >= b ? 0 : NaN;
+};
+d3.descending = function(a, b) {
+ return b < a ? -1 : b > a ? 1 : b >= a ? 0 : NaN;
+};
+d3.min = function(array, f) {
+ var i = -1,
+ n = array.length,
+ a,
+ b;
+ if (arguments.length === 1) {
+ while (++i < n && ((a = array[i]) == null || a != a)) a = undefined;
+ while (++i < n) if ((b = array[i]) != null && a > b) a = b;
+ } else {
+ while (++i < n && ((a = f.call(array, array[i], i)) == null || a != a)) a = undefined;
+ while (++i < n) if ((b = f.call(array, array[i], i)) != null && a > b) a = b;
+ }
+ return a;
+};
+d3.max = function(array, f) {
+ var i = -1,
+ n = array.length,
+ a,
+ b;
+ if (arguments.length === 1) {
+ while (++i < n && ((a = array[i]) == null || a != a)) a = undefined;
+ while (++i < n) if ((b = array[i]) != null && b > a) a = b;
+ } else {
+ while (++i < n && ((a = f.call(array, array[i], i)) == null || a != a)) a = undefined;
+ while (++i < n) if ((b = f.call(array, array[i], i)) != null && b > a) a = b;
+ }
+ return a;
+};
+d3.extent = function(array, f) {
+ var i = -1,
+ n = array.length,
+ a,
+ b,
+ c;
+ if (arguments.length === 1) {
+ while (++i < n && ((a = c = array[i]) == null || a != a)) a = c = undefined;
+ while (++i < n) if ((b = array[i]) != null) {
+ if (a > b) a = b;
+ if (c < b) c = b;
+ }
+ } else {
+ while (++i < n && ((a = c = f.call(array, array[i], i)) == null || a != a)) a = undefined;
+ while (++i < n) if ((b = f.call(array, array[i], i)) != null) {
+ if (a > b) a = b;
+ if (c < b) c = b;
+ }
+ }
+ return [a, c];
+};
+d3.sum = function(array, f) {
+ var s = 0,
+ n = array.length,
+ a,
+ i = -1;
+
+ if (arguments.length === 1) {
+ while (++i < n) if (!isNaN(a = +array[i])) s += a;
+ } else {
+ while (++i < n) if (!isNaN(a = +f.call(array, array[i], i))) s += a;
+ }
+
+ return s;
+};
+function d3_number(x) {
+ return x != null && !isNaN(x);
+}
+
+d3.mean = function(array, f) {
+ var n = array.length,
+ a,
+ m = 0,
+ i = -1,
+ j = 0;
+ if (arguments.length === 1) {
+ while (++i < n) if (d3_number(a = array[i])) m += (a - m) / ++j;
+ } else {
+ while (++i < n) if (d3_number(a = f.call(array, array[i], i))) m += (a - m) / ++j;
+ }
+ return j ? m : undefined;
+};
+// R-7 per
+d3.quantile = function(values, p) {
+ var H = (values.length - 1) * p + 1,
+ h = Math.floor(H),
+ v = +values[h - 1],
+ e = H - h;
+ return e ? v + e * (values[h] - v) : v;
+};
+
+d3.median = function(array, f) {
+ if (arguments.length > 1) array = array.map(f);
+ array = array.filter(d3_number);
+ return array.length ? d3.quantile(array.sort(d3.ascending), .5) : undefined;
+};
+d3.bisector = function(f) {
+ return {
+ left: function(a, x, lo, hi) {
+ if (arguments.length < 3) lo = 0;
+ if (arguments.length < 4) hi = a.length;
+ while (lo < hi) {
+ var mid = lo + hi >>> 1;
+ if (f.call(a, a[mid], mid) < x) lo = mid + 1;
+ else hi = mid;
+ }
+ return lo;
+ },
+ right: function(a, x, lo, hi) {
+ if (arguments.length < 3) lo = 0;
+ if (arguments.length < 4) hi = a.length;
+ while (lo < hi) {
+ var mid = lo + hi >>> 1;
+ if (x < f.call(a, a[mid], mid)) hi = mid;
+ else lo = mid + 1;
+ }
+ return lo;
+ }
+ };
+};
+
+var d3_bisector = d3.bisector(function(d) { return d; });
+d3.bisectLeft = d3_bisector.left;
+d3.bisect = d3.bisectRight = d3_bisector.right;
+d3.shuffle = function(array) {
+ var m = array.length, t, i;
+ while (m) {
+ i = Math.random() * m-- | 0;
+ t = array[m], array[m] = array[i], array[i] = t;
+ }
+ return array;
+};
+d3.permute = function(array, indexes) {
+ var permutes = [],
+ i = -1,
+ n = indexes.length;
+ while (++i < n) permutes[i] = array[indexes[i]];
+ return permutes;
+};
+
+d3.zip = function() {
+ if (!(n = arguments.length)) return [];
+ for (var i = -1, m = d3.min(arguments, d3_zipLength), zips = new Array(m); ++i < m;) {
+ for (var j = -1, n, zip = zips[i] = new Array(n); ++j < n;) {
+ zip[j] = arguments[j][i];
+ }
+ }
+ return zips;
+};
+
+function d3_zipLength(d) {
+ return d.length;
+}
+
+d3.transpose = function(matrix) {
+ return d3.zip.apply(d3, matrix);
+};
+d3.keys = function(map) {
+ var keys = [];
+ for (var key in map) keys.push(key);
+ return keys;
+};
+d3.values = function(map) {
+ var values = [];
+ for (var key in map) values.push(map[key]);
+ return values;
+};
+d3.entries = function(map) {
+ var entries = [];
+ for (var key in map) entries.push({key: key, value: map[key]});
+ return entries;
+};
+d3.merge = function(arrays) {
+ return Array.prototype.concat.apply([], arrays);
+};
+d3.range = function(start, stop, step) {
+ if (arguments.length < 3) {
+ step = 1;
+ if (arguments.length < 2) {
+ stop = start;
+ start = 0;
+ }
+ }
+ if ((stop - start) / step === Infinity) throw new Error("infinite range");
+ var range = [],
+ k = d3_range_integerScale(Math.abs(step)),
+ i = -1,
+ j;
+ start *= k, stop *= k, step *= k;
+ if (step < 0) while ((j = start + step * ++i) > stop) range.push(j / k);
+ else while ((j = start + step * ++i) < stop) range.push(j / k);
+ return range;
+};
+
+function d3_range_integerScale(x) {
+ var k = 1;
+ while (x * k % 1) k *= 10;
+ return k;
+}
+function d3_class(ctor, properties) {
+ try {
+ for (var key in properties) {
+ Object.defineProperty(ctor.prototype, key, {
+ value: properties[key],
+ enumerable: false
+ });
+ }
+ } catch (e) {
+ ctor.prototype = properties;
+ }
+}
+
+d3.map = function(object) {
+ var map = new d3_Map;
+ for (var key in object) map.set(key, object[key]);
+ return map;
+};
+
+function d3_Map() {}
+
+d3_class(d3_Map, {
+ has: function(key) {
+ return d3_map_prefix + key in this;
+ },
+ get: function(key) {
+ return this[d3_map_prefix + key];
+ },
+ set: function(key, value) {
+ return this[d3_map_prefix + key] = value;
+ },
+ remove: function(key) {
+ key = d3_map_prefix + key;
+ return key in this && delete this[key];
+ },
+ keys: function() {
+ var keys = [];
+ this.forEach(function(key) { keys.push(key); });
+ return keys;
+ },
+ values: function() {
+ var values = [];
+ this.forEach(function(key, value) { values.push(value); });
+ return values;
+ },
+ entries: function() {
+ var entries = [];
+ this.forEach(function(key, value) { entries.push({key: key, value: value}); });
+ return entries;
+ },
+ forEach: function(f) {
+ for (var key in this) {
+ if (key.charCodeAt(0) === d3_map_prefixCode) {
+ f.call(this, key.substring(1), this[key]);
+ }
+ }
+ }
+});
+
+var d3_map_prefix = "\0", // prevent collision with built-ins
+ d3_map_prefixCode = d3_map_prefix.charCodeAt(0);
+
+d3.nest = function() {
+ var nest = {},
+ keys = [],
+ sortKeys = [],
+ sortValues,
+ rollup;
+
+ function map(mapType, array, depth) {
+ if (depth >= keys.length) return rollup
+ ? rollup.call(nest, array) : (sortValues
+ ? array.sort(sortValues)
+ : array);
+
+ var i = -1,
+ n = array.length,
+ key = keys[depth++],
+ keyValue,
+ object,
+ setter,
+ valuesByKey = new d3_Map,
+ values;
+
+ while (++i < n) {
+ if (values = valuesByKey.get(keyValue = key(object = array[i]))) {
+ values.push(object);
+ } else {
+ valuesByKey.set(keyValue, [object]);
+ }
+ }
+
+ if (mapType) {
+ object = mapType();
+ setter = function(keyValue, values) {
+ object.set(keyValue, map(mapType, values, depth));
+ };
+ } else {
+ object = {};
+ setter = function(keyValue, values) {
+ object[keyValue] = map(mapType, values, depth);
+ };
+ }
+
+ valuesByKey.forEach(setter);
+ return object;
+ }
+
+ function entries(map, depth) {
+ if (depth >= keys.length) return map;
+
+ var array = [],
+ sortKey = sortKeys[depth++];
+
+ map.forEach(function(key, keyMap) {
+ array.push({key: key, values: entries(keyMap, depth)});
+ });
+
+ return sortKey
+ ? array.sort(function(a, b) { return sortKey(a.key, b.key); })
+ : array;
+ }
+
+ nest.map = function(array, mapType) {
+ return map(mapType, array, 0);
+ };
+
+ nest.entries = function(array) {
+ return entries(map(d3.map, array, 0), 0);
+ };
+
+ nest.key = function(d) {
+ keys.push(d);
+ return nest;
+ };
+
+ // Specifies the order for the most-recently specified key.
+ // Note: only applies to entries. Map keys are unordered!
+ nest.sortKeys = function(order) {
+ sortKeys[keys.length - 1] = order;
+ return nest;
+ };
+
+ // Specifies the order for leaf values.
+ // Applies to both maps and entries array.
+ nest.sortValues = function(order) {
+ sortValues = order;
+ return nest;
+ };
+
+ nest.rollup = function(f) {
+ rollup = f;
+ return nest;
+ };
+
+ return nest;
+};
+
+d3.set = function(array) {
+ var set = new d3_Set();
+ if (array) for (var i = 0; i < array.length; i++) set.add(array[i]);
+ return set;
+};
+
+function d3_Set() {}
+
+d3_class(d3_Set, {
+ has: function(value) {
+ return d3_map_prefix + value in this;
+ },
+ add: function(value) {
+ this[d3_map_prefix + value] = true;
+ return value;
+ },
+ remove: function(value) {
+ value = d3_map_prefix + value;
+ return value in this && delete this[value];
+ },
+ values: function() {
+ var values = [];
+ this.forEach(function(value) {
+ values.push(value);
+ });
+ return values;
+ },
+ forEach: function(f) {
+ for (var value in this) {
+ if (value.charCodeAt(0) === d3_map_prefixCode) {
+ f.call(this, value.substring(1));
+ }
+ }
+ }
+});
+d3.behavior = {};
+var d3_document = document,
+ d3_window = window;
+// Copies a variable number of methods from source to target.
+d3.rebind = function(target, source) {
+ var i = 1, n = arguments.length, method;
+ while (++i < n) target[method = arguments[i]] = d3_rebind(target, source, source[method]);
+ return target;
+};
+
+// Method is assumed to be a standard D3 getter-setter:
+// If passed with no arguments, gets the value.
+// If passed with arguments, sets the value and returns the target.
+function d3_rebind(target, source, method) {
+ return function() {
+ var value = method.apply(source, arguments);
+ return value === source ? target : value;
+ };
+}
+
+d3.dispatch = function() {
+ var dispatch = new d3_dispatch,
+ i = -1,
+ n = arguments.length;
+ while (++i < n) dispatch[arguments[i]] = d3_dispatch_event(dispatch);
+ return dispatch;
+};
+
+function d3_dispatch() {}
+
+d3_dispatch.prototype.on = function(type, listener) {
+ var i = type.indexOf("."),
+ name = "";
+
+ // Extract optional namespace, e.g., "click.foo"
+ if (i >= 0) {
+ name = type.substring(i + 1);
+ type = type.substring(0, i);
+ }
+
+ if (type) return arguments.length < 2
+ ? this[type].on(name)
+ : this[type].on(name, listener);
+
+ if (arguments.length === 2) {
+ if (listener == null) for (type in this) {
+ if (this.hasOwnProperty(type)) this[type].on(name, null);
+ }
+ return this;
+ }
+};
+
+function d3_dispatch_event(dispatch) {
+ var listeners = [],
+ listenerByName = new d3_Map;
+
+ function event() {
+ var z = listeners, // defensive reference
+ i = -1,
+ n = z.length,
+ l;
+ while (++i < n) if (l = z[i].on) l.apply(this, arguments);
+ return dispatch;
+ }
+
+ event.on = function(name, listener) {
+ var l = listenerByName.get(name),
+ i;
+
+ // return the current listener, if any
+ if (arguments.length < 2) return l && l.on;
+
+ // remove the old listener, if any (with copy-on-write)
+ if (l) {
+ l.on = null;
+ listeners = listeners.slice(0, i = listeners.indexOf(l)).concat(listeners.slice(i + 1));
+ listenerByName.remove(name);
+ }
+
+ // add the new listener, if any
+ if (listener) listeners.push(listenerByName.set(name, {on: listener}));
+
+ return dispatch;
+ };
+
+ return event;
+}
+
+d3.event = null;
+
+function d3_eventCancel() {
+ d3.event.stopPropagation();
+ d3.event.preventDefault();
+}
+
+function d3_eventSource() {
+ var e = d3.event, s;
+ while (s = e.sourceEvent) e = s;
+ return e;
+}
+
+// Like d3.dispatch, but for custom events abstracting native UI events. These
+// events have a target component (such as a brush), a target element (such as
+// the svg:g element containing the brush) and the standard arguments `d` (the
+// target element's data) and `i` (the selection index of the target element).
+function d3_eventDispatch(target) {
+ var dispatch = new d3_dispatch,
+ i = 0,
+ n = arguments.length;
+
+ while (++i < n) dispatch[arguments[i]] = d3_dispatch_event(dispatch);
+
+ // Creates a dispatch context for the specified `thiz` (typically, the target
+ // DOM element that received the source event) and `argumentz` (typically, the
+ // data `d` and index `i` of the target element). The returned function can be
+ // used to dispatch an event to any registered listeners; the function takes a
+ // single argument as input, being the event to dispatch. The event must have
+ // a "type" attribute which corresponds to a type registered in the
+ // constructor. This context will automatically populate the "sourceEvent" and
+ // "target" attributes of the event, as well as setting the `d3.event` global
+ // for the duration of the notification.
+ dispatch.of = function(thiz, argumentz) {
+ return function(e1) {
+ try {
+ var e0 =
+ e1.sourceEvent = d3.event;
+ e1.target = target;
+ d3.event = e1;
+ dispatch[e1.type].apply(thiz, argumentz);
+ } finally {
+ d3.event = e0;
+ }
+ };
+ };
+
+ return dispatch;
+}
+
+d3.mouse = function(container) {
+ return d3_mousePoint(container, d3_eventSource());
+};
+
+// https://bugs.webkit.org/show_bug.cgi?id=44083
+var d3_mouse_bug44083 = /WebKit/.test(d3_window.navigator.userAgent) ? -1 : 0;
+
+function d3_mousePoint(container, e) {
+ var svg = container.ownerSVGElement || container;
+ if (svg.createSVGPoint) {
+ var point = svg.createSVGPoint();
+ if (d3_mouse_bug44083 < 0 && (d3_window.scrollX || d3_window.scrollY)) {
+ svg = d3.select(d3_document.body).append("svg")
+ .style("position", "absolute")
+ .style("top", 0)
+ .style("left", 0);
+ var ctm = svg[0][0].getScreenCTM();
+ d3_mouse_bug44083 = !(ctm.f || ctm.e);
+ svg.remove();
+ }
+ if (d3_mouse_bug44083) {
+ point.x = e.pageX;
+ point.y = e.pageY;
+ } else {
+ point.x = e.clientX;
+ point.y = e.clientY;
+ }
+ point = point.matrixTransform(container.getScreenCTM().inverse());
+ return [point.x, point.y];
+ }
+ var rect = container.getBoundingClientRect();
+ return [e.clientX - rect.left - container.clientLeft, e.clientY - rect.top - container.clientTop];
+};
+
+var d3_array = d3_arraySlice; // conversion for NodeLists
+
+function d3_arrayCopy(pseudoarray) {
+ var i = -1, n = pseudoarray.length, array = [];
+ while (++i < n) array.push(pseudoarray[i]);
+ return array;
+}
+
+function d3_arraySlice(pseudoarray) {
+ return Array.prototype.slice.call(pseudoarray);
+}
+
+try {
+ d3_array(d3_document.documentElement.childNodes)[0].nodeType;
+} catch(e) {
+ d3_array = d3_arrayCopy;
+}
+
+var d3_arraySubclass = [].__proto__?
+
+// Until ECMAScript supports array subclassing, prototype injection works well.
+function(array, prototype) {
+ array.__proto__ = prototype;
+}:
+
+// And if your browser doesn't support __proto__, we'll use direct extension.
+function(array, prototype) {
+ for (var property in prototype) array[property] = prototype[property];
+};
+
+d3.touches = function(container, touches) {
+ if (arguments.length < 2) touches = d3_eventSource().touches;
+ return touches ? d3_array(touches).map(function(touch) {
+ var point = d3_mousePoint(container, touch);
+ point.identifier = touch.identifier;
+ return point;
+ }) : [];
+};
+
+function d3_selection(groups) {
+ d3_arraySubclass(groups, d3_selectionPrototype);
+ return groups;
+}
+
+var d3_select = function(s, n) { return n.querySelector(s); },
+ d3_selectAll = function(s, n) { return n.querySelectorAll(s); },
+ d3_selectRoot = d3_document.documentElement,
+ d3_selectMatcher = d3_selectRoot.matchesSelector || d3_selectRoot.webkitMatchesSelector || d3_selectRoot.mozMatchesSelector || d3_selectRoot.msMatchesSelector || d3_selectRoot.oMatchesSelector,
+ d3_selectMatches = function(n, s) { return d3_selectMatcher.call(n, s); };
+
+// Prefer Sizzle, if available.
+if (typeof Sizzle === "function") {
+ d3_select = function(s, n) { return Sizzle(s, n)[0] || null; };
+ d3_selectAll = function(s, n) { return Sizzle.uniqueSort(Sizzle(s, n)); };
+ d3_selectMatches = Sizzle.matchesSelector;
+}
+
+var d3_selectionPrototype = [];
+
+d3.selection = function() {
+ return d3_selectionRoot;
+};
+
+d3.selection.prototype = d3_selectionPrototype;
+
+
+d3_selectionPrototype.select = function(selector) {
+ var subgroups = [],
+ subgroup,
+ subnode,
+ group,
+ node;
+
+ if (typeof selector !== "function") selector = d3_selection_selector(selector);
+
+ for (var j = -1, m = this.length; ++j < m;) {
+ subgroups.push(subgroup = []);
+ subgroup.parentNode = (group = this[j]).parentNode;
+ for (var i = -1, n = group.length; ++i < n;) {
+ if (node = group[i]) {
+ subgroup.push(subnode = selector.call(node, node.__data__, i));
+ if (subnode && "__data__" in node) subnode.__data__ = node.__data__;
+ } else {
+ subgroup.push(null);
+ }
+ }
+ }
+
+ return d3_selection(subgroups);
+};
+
+function d3_selection_selector(selector) {
+ return function() {
+ return d3_select(selector, this);
+ };
+}
+
+d3_selectionPrototype.selectAll = function(selector) {
+ var subgroups = [],
+ subgroup,
+ node;
+
+ if (typeof selector !== "function") selector = d3_selection_selectorAll(selector);
+
+ for (var j = -1, m = this.length; ++j < m;) {
+ for (var group = this[j], i = -1, n = group.length; ++i < n;) {
+ if (node = group[i]) {
+ subgroups.push(subgroup = d3_array(selector.call(node, node.__data__, i)));
+ subgroup.parentNode = node;
+ }
+ }
+ }
+
+ return d3_selection(subgroups);
+};
+
+function d3_selection_selectorAll(selector) {
+ return function() {
+ return d3_selectAll(selector, this);
+ };
+}
+var d3_nsPrefix = {
+ svg: "http://www.w3.org/2000/svg",
+ xhtml: "http://www.w3.org/1999/xhtml",
+ xlink: "http://www.w3.org/1999/xlink",
+ xml: "http://www.w3.org/XML/1998/namespace",
+ xmlns: "http://www.w3.org/2000/xmlns/"
+};
+
+d3.ns = {
+ prefix: d3_nsPrefix,
+ qualify: function(name) {
+ var i = name.indexOf(":"),
+ prefix = name;
+ if (i >= 0) {
+ prefix = name.substring(0, i);
+ name = name.substring(i + 1);
+ }
+ return d3_nsPrefix.hasOwnProperty(prefix)
+ ? {space: d3_nsPrefix[prefix], local: name}
+ : name;
+ }
+};
+
+d3_selectionPrototype.attr = function(name, value) {
+ if (arguments.length < 2) {
+
+ // For attr(string), return the attribute value for the first node.
+ if (typeof name === "string") {
+ var node = this.node();
+ name = d3.ns.qualify(name);
+ return name.local
+ ? node.getAttributeNS(name.space, name.local)
+ : node.getAttribute(name);
+ }
+
+ // For attr(object), the object specifies the names and values of the
+ // attributes to set or remove. The values may be functions that are
+ // evaluated for each element.
+ for (value in name) this.each(d3_selection_attr(value, name[value]));
+ return this;
+ }
+
+ return this.each(d3_selection_attr(name, value));
+};
+
+function d3_selection_attr(name, value) {
+ name = d3.ns.qualify(name);
+
+ // For attr(string, null), remove the attribute with the specified name.
+ function attrNull() {
+ this.removeAttribute(name);
+ }
+ function attrNullNS() {
+ this.removeAttributeNS(name.space, name.local);
+ }
+
+ // For attr(string, string), set the attribute with the specified name.
+ function attrConstant() {
+ this.setAttribute(name, value);
+ }
+ function attrConstantNS() {
+ this.setAttributeNS(name.space, name.local, value);
+ }
+
+ // For attr(string, function), evaluate the function for each element, and set
+ // or remove the attribute as appropriate.
+ function attrFunction() {
+ var x = value.apply(this, arguments);
+ if (x == null) this.removeAttribute(name);
+ else this.setAttribute(name, x);
+ }
+ function attrFunctionNS() {
+ var x = value.apply(this, arguments);
+ if (x == null) this.removeAttributeNS(name.space, name.local);
+ else this.setAttributeNS(name.space, name.local, x);
+ }
+
+ return value == null
+ ? (name.local ? attrNullNS : attrNull) : (typeof value === "function"
+ ? (name.local ? attrFunctionNS : attrFunction)
+ : (name.local ? attrConstantNS : attrConstant));
+}
+function d3_collapse(s) {
+ return s.trim().replace(/\s+/g, " ");
+}
+d3.requote = function(s) {
+ return s.replace(d3_requote_re, "\\$&");
+};
+
+var d3_requote_re = /[\\\^\$\*\+\?\|\[\]\(\)\.\{\}]/g;
+
+d3_selectionPrototype.classed = function(name, value) {
+ if (arguments.length < 2) {
+
+ // For classed(string), return true only if the first node has the specified
+ // class or classes. Note that even if the browser supports DOMTokenList, it
+ // probably doesn't support it on SVG elements (which can be animated).
+ if (typeof name === "string") {
+ var node = this.node(),
+ n = (name = name.trim().split(/^|\s+/g)).length,
+ i = -1;
+ if (value = node.classList) {
+ while (++i < n) if (!value.contains(name[i])) return false;
+ } else {
+ value = node.getAttribute("class");
+ while (++i < n) if (!d3_selection_classedRe(name[i]).test(value)) return false;
+ }
+ return true;
+ }
+
+ // For classed(object), the object specifies the names of classes to add or
+ // remove. The values may be functions that are evaluated for each element.
+ for (value in name) this.each(d3_selection_classed(value, name[value]));
+ return this;
+ }
+
+ // Otherwise, both a name and a value are specified, and are handled as below.
+ return this.each(d3_selection_classed(name, value));
+};
+
+function d3_selection_classedRe(name) {
+ return new RegExp("(?:^|\\s+)" + d3.requote(name) + "(?:\\s+|$)", "g");
+}
+
+// Multiple class names are allowed (e.g., "foo bar").
+function d3_selection_classed(name, value) {
+ name = name.trim().split(/\s+/).map(d3_selection_classedName);
+ var n = name.length;
+
+ function classedConstant() {
+ var i = -1;
+ while (++i < n) name[i](this, value);
+ }
+
+ // When the value is a function, the function is still evaluated only once per
+ // element even if there are multiple class names.
+ function classedFunction() {
+ var i = -1, x = value.apply(this, arguments);
+ while (++i < n) name[i](this, x);
+ }
+
+ return typeof value === "function"
+ ? classedFunction
+ : classedConstant;
+}
+
+function d3_selection_classedName(name) {
+ var re = d3_selection_classedRe(name);
+ return function(node, value) {
+ if (c = node.classList) return value ? c.add(name) : c.remove(name);
+ var c = node.getAttribute("class") || "";
+ if (value) {
+ re.lastIndex = 0;
+ if (!re.test(c)) node.setAttribute("class", d3_collapse(c + " " + name));
+ } else {
+ node.setAttribute("class", d3_collapse(c.replace(re, " ")));
+ }
+ };
+}
+
+d3_selectionPrototype.style = function(name, value, priority) {
+ var n = arguments.length;
+ if (n < 3) {
+
+ // For style(object) or style(object, string), the object specifies the
+ // names and values of the attributes to set or remove. The values may be
+ // functions that are evaluated for each element. The optional string
+ // specifies the priority.
+ if (typeof name !== "string") {
+ if (n < 2) value = "";
+ for (priority in name) this.each(d3_selection_style(priority, name[priority], value));
+ return this;
+ }
+
+ // For style(string), return the computed style value for the first node.
+ if (n < 2) return d3_window.getComputedStyle(this.node(), null).getPropertyValue(name);
+
+ // For style(string, string) or style(string, function), use the default
+ // priority. The priority is ignored for style(string, null).
+ priority = "";
+ }
+
+ // Otherwise, a name, value and priority are specified, and handled as below.
+ return this.each(d3_selection_style(name, value, priority));
+};
+
+function d3_selection_style(name, value, priority) {
+
+ // For style(name, null) or style(name, null, priority), remove the style
+ // property with the specified name. The priority is ignored.
+ function styleNull() {
+ this.style.removeProperty(name);
+ }
+
+ // For style(name, string) or style(name, string, priority), set the style
+ // property with the specified name, using the specified priority.
+ function styleConstant() {
+ this.style.setProperty(name, value, priority);
+ }
+
+ // For style(name, function) or style(name, function, priority), evaluate the
+ // function for each element, and set or remove the style property as
+ // appropriate. When setting, use the specified priority.
+ function styleFunction() {
+ var x = value.apply(this, arguments);
+ if (x == null) this.style.removeProperty(name);
+ else this.style.setProperty(name, x, priority);
+ }
+
+ return value == null
+ ? styleNull : (typeof value === "function"
+ ? styleFunction : styleConstant);
+}
+
+d3_selectionPrototype.property = function(name, value) {
+ if (arguments.length < 2) {
+
+ // For property(string), return the property value for the first node.
+ if (typeof name === "string") return this.node()[name];
+
+ // For property(object), the object specifies the names and values of the
+ // properties to set or remove. The values may be functions that are
+ // evaluated for each element.
+ for (value in name) this.each(d3_selection_property(value, name[value]));
+ return this;
+ }
+
+ // Otherwise, both a name and a value are specified, and are handled as below.
+ return this.each(d3_selection_property(name, value));
+};
+
+function d3_selection_property(name, value) {
+
+ // For property(name, null), remove the property with the specified name.
+ function propertyNull() {
+ delete this[name];
+ }
+
+ // For property(name, string), set the property with the specified name.
+ function propertyConstant() {
+ this[name] = value;
+ }
+
+ // For property(name, function), evaluate the function for each element, and
+ // set or remove the property as appropriate.
+ function propertyFunction() {
+ var x = value.apply(this, arguments);
+ if (x == null) delete this[name];
+ else this[name] = x;
+ }
+
+ return value == null
+ ? propertyNull : (typeof value === "function"
+ ? propertyFunction : propertyConstant);
+}
+
+d3_selectionPrototype.text = function(value) {
+ return arguments.length
+ ? this.each(typeof value === "function"
+ ? function() { var v = value.apply(this, arguments); this.textContent = v == null ? "" : v; } : value == null
+ ? function() { this.textContent = ""; }
+ : function() { this.textContent = value; })
+ : this.node().textContent;
+};
+
+d3_selectionPrototype.html = function(value) {
+ return arguments.length
+ ? this.each(typeof value === "function"
+ ? function() { var v = value.apply(this, arguments); this.innerHTML = v == null ? "" : v; } : value == null
+ ? function() { this.innerHTML = ""; }
+ : function() { this.innerHTML = value; })
+ : this.node().innerHTML;
+};
+
+// TODO append(node)?
+// TODO append(function)?
+d3_selectionPrototype.append = function(name) {
+ name = d3.ns.qualify(name);
+
+ function append() {
+ return this.appendChild(d3_document.createElementNS(this.namespaceURI, name));
+ }
+
+ function appendNS() {
+ return this.appendChild(d3_document.createElementNS(name.space, name.local));
+ }
+
+ return this.select(name.local ? appendNS : append);
+};
+
+d3_selectionPrototype.insert = function(name, before) {
+ name = d3.ns.qualify(name);
+
+ if (typeof before !== "function") before = d3_selection_selector(before);
+
+ function insert(d, i) {
+ return this.insertBefore(
+ d3_document.createElementNS(this.namespaceURI, name),
+ before.call(this, d, i));
+ }
+
+ function insertNS(d, i) {
+ return this.insertBefore(
+ d3_document.createElementNS(name.space, name.local),
+ before.call(this, d, i));
+ }
+
+ return this.select(name.local ? insertNS : insert);
+};
+
+// TODO remove(selector)?
+// TODO remove(node)?
+// TODO remove(function)?
+d3_selectionPrototype.remove = function() {
+ return this.each(function() {
+ var parent = this.parentNode;
+ if (parent) parent.removeChild(this);
+ });
+};
+
+d3_selectionPrototype.data = function(value, key) {
+ var i = -1,
+ n = this.length,
+ group,
+ node;
+
+ // If no value is specified, return the first value.
+ if (!arguments.length) {
+ value = new Array(n = (group = this[0]).length);
+ while (++i < n) {
+ if (node = group[i]) {
+ value[i] = node.__data__;
+ }
+ }
+ return value;
+ }
+
+ function bind(group, groupData) {
+ var i,
+ n = group.length,
+ m = groupData.length,
+ n0 = Math.min(n, m),
+ updateNodes = new Array(m),
+ enterNodes = new Array(m),
+ exitNodes = new Array(n),
+ node,
+ nodeData;
+
+ if (key) {
+ var nodeByKeyValue = new d3_Map,
+ dataByKeyValue = new d3_Map,
+ keyValues = [],
+ keyValue;
+
+ for (i = -1; ++i < n;) {
+ keyValue = key.call(node = group[i], node.__data__, i);
+ if (nodeByKeyValue.has(keyValue)) {
+ exitNodes[i] = node; // duplicate selection key
+ } else {
+ nodeByKeyValue.set(keyValue, node);
+ }
+ keyValues.push(keyValue);
+ }
+
+ for (i = -1; ++i < m;) {
+ keyValue = key.call(groupData, nodeData = groupData[i], i);
+ if (node = nodeByKeyValue.get(keyValue)) {
+ updateNodes[i] = node;
+ node.__data__ = nodeData;
+ } else if (!dataByKeyValue.has(keyValue)) { // no duplicate data key
+ enterNodes[i] = d3_selection_dataNode(nodeData);
+ }
+ dataByKeyValue.set(keyValue, nodeData);
+ nodeByKeyValue.remove(keyValue);
+ }
+
+ for (i = -1; ++i < n;) {
+ if (nodeByKeyValue.has(keyValues[i])) {
+ exitNodes[i] = group[i];
+ }
+ }
+ } else {
+ for (i = -1; ++i < n0;) {
+ node = group[i];
+ nodeData = groupData[i];
+ if (node) {
+ node.__data__ = nodeData;
+ updateNodes[i] = node;
+ } else {
+ enterNodes[i] = d3_selection_dataNode(nodeData);
+ }
+ }
+ for (; i < m; ++i) {
+ enterNodes[i] = d3_selection_dataNode(groupData[i]);
+ }
+ for (; i < n; ++i) {
+ exitNodes[i] = group[i];
+ }
+ }
+
+ enterNodes.update
+ = updateNodes;
+
+ enterNodes.parentNode
+ = updateNodes.parentNode
+ = exitNodes.parentNode
+ = group.parentNode;
+
+ enter.push(enterNodes);
+ update.push(updateNodes);
+ exit.push(exitNodes);
+ }
+
+ var enter = d3_selection_enter([]),
+ update = d3_selection([]),
+ exit = d3_selection([]);
+
+ if (typeof value === "function") {
+ while (++i < n) {
+ bind(group = this[i], value.call(group, group.parentNode.__data__, i));
+ }
+ } else {
+ while (++i < n) {
+ bind(group = this[i], value);
+ }
+ }
+
+ update.enter = function() { return enter; };
+ update.exit = function() { return exit; };
+ return update;
+};
+
+function d3_selection_dataNode(data) {
+ return {__data__: data};
+}
+
+d3_selectionPrototype.datum = function(value) {
+ return arguments.length
+ ? this.property("__data__", value)
+ : this.property("__data__");
+};
+
+d3_selectionPrototype.filter = function(filter) {
+ var subgroups = [],
+ subgroup,
+ group,
+ node;
+
+ if (typeof filter !== "function") filter = d3_selection_filter(filter);
+
+ for (var j = 0, m = this.length; j < m; j++) {
+ subgroups.push(subgroup = []);
+ subgroup.parentNode = (group = this[j]).parentNode;
+ for (var i = 0, n = group.length; i < n; i++) {
+ if ((node = group[i]) && filter.call(node, node.__data__, i)) {
+ subgroup.push(node);
+ }
+ }
+ }
+
+ return d3_selection(subgroups);
+};
+
+function d3_selection_filter(selector) {
+ return function() {
+ return d3_selectMatches(this, selector);
+ };
+}
+
+d3_selectionPrototype.order = function() {
+ for (var j = -1, m = this.length; ++j < m;) {
+ for (var group = this[j], i = group.length - 1, next = group[i], node; --i >= 0;) {
+ if (node = group[i]) {
+ if (next && next !== node.nextSibling) next.parentNode.insertBefore(node, next);
+ next = node;
+ }
+ }
+ }
+ return this;
+};
+
+d3_selectionPrototype.sort = function(comparator) {
+ comparator = d3_selection_sortComparator.apply(this, arguments);
+ for (var j = -1, m = this.length; ++j < m;) this[j].sort(comparator);
+ return this.order();
+};
+
+function d3_selection_sortComparator(comparator) {
+ if (!arguments.length) comparator = d3.ascending;
+ return function(a, b) {
+ return (!a - !b) || comparator(a.__data__, b.__data__);
+ };
+}
+function d3_noop() {}
+
+d3_selectionPrototype.on = function(type, listener, capture) {
+ var n = arguments.length;
+ if (n < 3) {
+
+ // For on(object) or on(object, boolean), the object specifies the event
+ // types and listeners to add or remove. The optional boolean specifies
+ // whether the listener captures events.
+ if (typeof type !== "string") {
+ if (n < 2) listener = false;
+ for (capture in type) this.each(d3_selection_on(capture, type[capture], listener));
+ return this;
+ }
+
+ // For on(string), return the listener for the first node.
+ if (n < 2) return (n = this.node()["__on" + type]) && n._;
+
+ // For on(string, function), use the default capture.
+ capture = false;
+ }
+
+ // Otherwise, a type, listener and capture are specified, and handled as below.
+ return this.each(d3_selection_on(type, listener, capture));
+};
+
+function d3_selection_on(type, listener, capture) {
+ var name = "__on" + type,
+ i = type.indexOf("."),
+ wrap = d3_selection_onListener;
+
+ if (i > 0) type = type.substring(0, i);
+ var filter = d3_selection_onFilters.get(type);
+ if (filter) type = filter, wrap = d3_selection_onFilter;
+
+ function onRemove() {
+ var l = this[name];
+ if (l) {
+ this.removeEventListener(type, l, l.$);
+ delete this[name];
+ }
+ }
+
+ function onAdd() {
+ var l = wrap(listener, d3_array(arguments));
+ if (typeof Raven !== 'undefined') l = Raven.wrap(l);
+ onRemove.call(this);
+ this.addEventListener(type, this[name] = l, l.$ = capture);
+ l._ = listener;
+ }
+
+ function removeAll() {
+ var re = new RegExp("^__on([^.]+)" + d3.requote(type) + "$"),
+ match;
+ for (var name in this) {
+ if (match = name.match(re)) {
+ var l = this[name];
+ this.removeEventListener(match[1], l, l.$);
+ delete this[name];
+ }
+ }
+ }
+
+ return i
+ ? listener ? onAdd : onRemove
+ : listener ? d3_noop : removeAll;
+}
+
+var d3_selection_onFilters = d3.map({
+ mouseenter: "mouseover",
+ mouseleave: "mouseout"
+});
+
+d3_selection_onFilters.forEach(function(k) {
+ if ("on" + k in d3_document) d3_selection_onFilters.remove(k);
+});
+
+function d3_selection_onListener(listener, argumentz) {
+ return function(e) {
+ var o = d3.event; // Events can be reentrant (e.g., focus).
+ d3.event = e;
+ argumentz[0] = this.__data__;
+ try {
+ listener.apply(this, argumentz);
+ } finally {
+ d3.event = o;
+ }
+ };
+}
+
+function d3_selection_onFilter(listener, argumentz) {
+ var l = d3_selection_onListener(listener, argumentz);
+ return function(e) {
+ var target = this, related = e.relatedTarget;
+ if (!related || (related !== target && !(related.compareDocumentPosition(target) & 8))) {
+ l.call(target, e);
+ }
+ };
+}
+
+d3_selectionPrototype.each = function(callback) {
+ return d3_selection_each(this, function(node, i, j) {
+ callback.call(node, node.__data__, i, j);
+ });
+};
+
+function d3_selection_each(groups, callback) {
+ for (var j = 0, m = groups.length; j < m; j++) {
+ for (var group = groups[j], i = 0, n = group.length, node; i < n; i++) {
+ if (node = group[i]) callback(node, i, j);
+ }
+ }
+ return groups;
+}
+
+d3_selectionPrototype.call = function(callback) {
+ var args = d3_array(arguments);
+ callback.apply(args[0] = this, args);
+ return this;
+};
+
+d3_selectionPrototype.empty = function() {
+ return !this.node();
+};
+
+d3_selectionPrototype.node = function() {
+ for (var j = 0, m = this.length; j < m; j++) {
+ for (var group = this[j], i = 0, n = group.length; i < n; i++) {
+ var node = group[i];
+ if (node) return node;
+ }
+ }
+ return null;
+};
+
+function d3_selection_enter(selection) {
+ d3_arraySubclass(selection, d3_selection_enterPrototype);
+ return selection;
+}
+
+var d3_selection_enterPrototype = [];
+
+d3.selection.enter = d3_selection_enter;
+d3.selection.enter.prototype = d3_selection_enterPrototype;
+
+d3_selection_enterPrototype.append = d3_selectionPrototype.append;
+d3_selection_enterPrototype.insert = d3_selectionPrototype.insert;
+d3_selection_enterPrototype.empty = d3_selectionPrototype.empty;
+d3_selection_enterPrototype.node = d3_selectionPrototype.node;
+
+
+d3_selection_enterPrototype.select = function(selector) {
+ var subgroups = [],
+ subgroup,
+ subnode,
+ upgroup,
+ group,
+ node;
+
+ for (var j = -1, m = this.length; ++j < m;) {
+ upgroup = (group = this[j]).update;
+ subgroups.push(subgroup = []);
+ subgroup.parentNode = group.parentNode;
+ for (var i = -1, n = group.length; ++i < n;) {
+ if (node = group[i]) {
+ subgroup.push(upgroup[i] = subnode = selector.call(group.parentNode, node.__data__, i));
+ subnode.__data__ = node.__data__;
+ } else {
+ subgroup.push(null);
+ }
+ }
+ }
+
+ return d3_selection(subgroups);
+};
+
+d3_selectionPrototype.transition = function() {
+ var id = d3_transitionInheritId || ++d3_transitionId,
+ subgroups = [],
+ subgroup,
+ node,
+ transition = Object.create(d3_transitionInherit);
+
+ transition.time = Date.now();
+
+ for (var j = -1, m = this.length; ++j < m;) {
+ subgroups.push(subgroup = []);
+ for (var group = this[j], i = -1, n = group.length; ++i < n;) {
+ if (node = group[i]) d3_transitionNode(node, i, id, transition);
+ subgroup.push(node);
+ }
+ }
+
+ return d3_transition(subgroups, id);
+};
+
+var d3_selectionRoot = d3_selection([[d3_document]]);
+
+d3_selectionRoot[0].parentNode = d3_selectRoot;
+
+// TODO fast singleton implementation!
+// TODO select(function)
+d3.select = function(selector) {
+ return typeof selector === "string"
+ ? d3_selectionRoot.select(selector)
+ : d3_selection([[selector]]); // assume node
+};
+
+// TODO selectAll(function)
+d3.selectAll = function(selector) {
+ return typeof selector === "string"
+ ? d3_selectionRoot.selectAll(selector)
+ : d3_selection([d3_array(selector)]); // assume node[]
+};
+
+d3.behavior.zoom = function() {
+ var translate = [0, 0],
+ translate0, // translate when we started zooming (to avoid drift)
+ scale = 1,
+ scale0, // scale when we started touching
+ scaleExtent = d3_behavior_zoomInfinity,
+ event = d3_eventDispatch(zoom, "zoom"),
+ x0,
+ x1,
+ y0,
+ y1,
+ touchtime; // time of last touchstart (to detect double-tap)
+
+ function zoom() {
+ this.on("mousedown.zoom", mousedown)
+ .on("mousemove.zoom", mousemove)
+ .on(d3_behavior_zoomWheel + ".zoom", mousewheel)
+ .on("dblclick.zoom", dblclick)
+ .on("touchstart.zoom", touchstart)
+ .on("touchmove.zoom", touchmove)
+ .on("touchend.zoom", touchstart);
+ }
+
+ zoom.translate = function(x) {
+ if (!arguments.length) return translate;
+ translate = x.map(Number);
+ rescale();
+ return zoom;
+ };
+
+ zoom.scale = function(x) {
+ if (!arguments.length) return scale;
+ scale = +x;
+ rescale();
+ return zoom;
+ };
+
+ zoom.scaleExtent = function(x) {
+ if (!arguments.length) return scaleExtent;
+ scaleExtent = x == null ? d3_behavior_zoomInfinity : x.map(Number);
+ return zoom;
+ };
+
+ zoom.x = function(z) {
+ if (!arguments.length) return x1;
+ x1 = z;
+ x0 = z.copy();
+ translate = [0, 0];
+ scale = 1;
+ return zoom;
+ };
+
+ zoom.y = function(z) {
+ if (!arguments.length) return y1;
+ y1 = z;
+ y0 = z.copy();
+ translate = [0, 0];
+ scale = 1;
+ return zoom;
+ };
+
+ function location(p) {
+ return [(p[0] - translate[0]) / scale, (p[1] - translate[1]) / scale];
+ }
+
+ function point(l) {
+ return [l[0] * scale + translate[0], l[1] * scale + translate[1]];
+ }
+
+ function scaleTo(s) {
+ scale = Math.max(scaleExtent[0], Math.min(scaleExtent[1], s));
+ }
+
+ function translateTo(p, l) {
+ l = point(l);
+ translate[0] += p[0] - l[0];
+ translate[1] += p[1] - l[1];
+ }
+
+ function rescale() {
+ if (x1) x1.domain(x0.range().map(function(x) { return (x - translate[0]) / scale; }).map(x0.invert));
+ if (y1) y1.domain(y0.range().map(function(y) { return (y - translate[1]) / scale; }).map(y0.invert));
+ }
+
+ function dispatch(event) {
+ rescale();
+ d3.event.preventDefault();
+ event({type: "zoom", scale: scale, translate: translate});
+ }
+
+ function mousedown() {
+ var target = this,
+ event_ = event.of(target, arguments),
+ eventTarget = d3.event.target,
+ moved = 0,
+ w = d3.select(d3_window).on("mousemove.zoom", mousemove).on("mouseup.zoom", mouseup),
+ l = location(d3.mouse(target));
+
+ d3_window.focus();
+ d3_eventCancel();
+
+ function mousemove() {
+ moved = 1;
+ translateTo(d3.mouse(target), l);
+ dispatch(event_);
+ }
+
+ function mouseup() {
+ if (moved) d3_eventCancel();
+ w.on("mousemove.zoom", null).on("mouseup.zoom", null);
+ if (moved && d3.event.target === eventTarget) {
+ w.on("click.zoom", click, true);
+ window.setTimeout(function() {
+ // Remove click block if click didn't fire
+ w.on("click.zoom", null);
+ }, 0);
+ }
+ }
+
+ function click() {
+ d3_eventCancel();
+ w.on("click.zoom", null);
+ }
+ }
+
+ function mousewheel() {
+ if (!translate0) translate0 = location(d3.mouse(this));
+ scaleTo(Math.pow(2, d3_behavior_zoomDelta() * .002) * scale);
+ translateTo(d3.mouse(this), translate0);
+ dispatch(event.of(this, arguments));
+ }
+
+ function mousemove() {
+ translate0 = null;
+ }
+
+ function dblclick() {
+ var p = d3.mouse(this), l = location(p), k = Math.log(scale) / Math.LN2;
+ scaleTo(Math.pow(2, d3.event.shiftKey ? Math.ceil(k) - 1 : Math.floor(k) + 1));
+ translateTo(p, l);
+ dispatch(event.of(this, arguments));
+ }
+
+ function touchstart() {
+ var touches = d3.touches(this),
+ now = Date.now();
+
+ scale0 = scale;
+ translate0 = {};
+ touches.forEach(function(t) { translate0[t.identifier] = location(t); });
+ d3_eventCancel();
+
+ if (touches.length === 1) {
+ if (now - touchtime < 500) { // dbltap
+ var p = touches[0], l = location(touches[0]);
+ scaleTo(scale * 2);
+ translateTo(p, l);
+ dispatch(event.of(this, arguments));
+ }
+ touchtime = now;
+ }
+ }
+
+ function touchmove() {
+ var touches = d3.touches(this),
+ p0 = touches[0],
+ l0 = translate0[p0.identifier];
+ if (p1 = touches[1]) {
+ var p1, l1 = translate0[p1.identifier];
+ p0 = [(p0[0] + p1[0]) / 2, (p0[1] + p1[1]) / 2];
+ l0 = [(l0[0] + l1[0]) / 2, (l0[1] + l1[1]) / 2];
+ scaleTo(d3.event.scale * scale0);
+ }
+ translateTo(p0, l0);
+ touchtime = null;
+ dispatch(event.of(this, arguments));
+ }
+
+ return d3.rebind(zoom, event, "on");
+};
+
+var d3_behavior_zoomInfinity = [0, Infinity]; // default scale extent
+
+// https://developer.mozilla.org/en-US/docs/Mozilla_event_reference/wheel
+var d3_behavior_zoomDelta, d3_behavior_zoomWheel
+ = "onwheel" in d3_document ? (d3_behavior_zoomDelta = function() { return -d3.event.deltaY * (d3.event.deltaMode ? 120 : 1); }, "wheel")
+ : "onmousewheel" in d3_document ? (d3_behavior_zoomDelta = function() { return d3.event.wheelDelta; }, "mousewheel")
+ : (d3_behavior_zoomDelta = function() { return -d3.event.detail; }, "MozMousePixelScroll");
+function d3_functor(v) {
+ return typeof v === "function" ? v : function() { return v; };
+}
+
+d3.functor = d3_functor;
+
+var d3_timer_id = 0,
+ d3_timer_byId = {},
+ d3_timer_queue = null,
+ d3_timer_interval, // is an interval (or frame) active?
+ d3_timer_timeout; // is a timeout active?
+
+// The timer will continue to fire until callback returns true.
+d3.timer = function(callback, delay, then) {
+ if (arguments.length < 3) {
+ if (arguments.length < 2) delay = 0;
+ else if (!isFinite(delay)) return;
+ then = Date.now();
+ }
+
+ // If the callback's already in the queue, update it.
+ var timer = d3_timer_byId[callback.id];
+ if (timer && timer.callback === callback) {
+ timer.then = then;
+ timer.delay = delay;
+ }
+
+ // Otherwise, add the callback to the queue.
+ else d3_timer_byId[callback.id = ++d3_timer_id] = d3_timer_queue = {
+ callback: callback,
+ then: then,
+ delay: delay,
+ next: d3_timer_queue
+ };
+
+ // Start animatin'!
+ if (!d3_timer_interval) {
+ d3_timer_timeout = clearTimeout(d3_timer_timeout);
+ d3_timer_interval = 1;
+ d3_timer_frame(d3_timer_step);
+ }
+};
+
+function d3_timer_step() {
+ var elapsed,
+ now = Date.now(),
+ t1 = d3_timer_queue;
+
+ while (t1) {
+ elapsed = now - t1.then;
+ if (elapsed >= t1.delay) t1.flush = t1.callback(elapsed);
+ t1 = t1.next;
+ }
+
+ var delay = d3_timer_flush() - now;
+ if (delay > 24) {
+ if (isFinite(delay)) {
+ clearTimeout(d3_timer_timeout);
+ d3_timer_timeout = setTimeout(d3_timer_step, delay);
+ }
+ d3_timer_interval = 0;
+ } else {
+ d3_timer_interval = 1;
+ d3_timer_frame(d3_timer_step);
+ }
+}
+
+d3.timer.flush = function() {
+ var elapsed,
+ now = Date.now(),
+ t1 = d3_timer_queue;
+
+ while (t1) {
+ elapsed = now - t1.then;
+ if (!t1.delay) t1.flush = t1.callback(elapsed);
+ t1 = t1.next;
+ }
+
+ d3_timer_flush();
+};
+
+// Flush after callbacks to avoid concurrent queue modification.
+function d3_timer_flush() {
+ var t0 = null,
+ t1 = d3_timer_queue,
+ then = Infinity;
+ while (t1) {
+ if (t1.flush) {
+ delete d3_timer_byId[t1.callback.id];
+ t1 = t0 ? t0.next = t1.next : d3_timer_queue = t1.next;
+ } else {
+ then = Math.min(then, t1.then + t1.delay);
+ t1 = (t0 = t1).next;
+ }
+ }
+ return then;
+}
+
+var d3_timer_frame = d3_window.requestAnimationFrame
+ || d3_window.webkitRequestAnimationFrame
+ || d3_window.mozRequestAnimationFrame
+ || d3_window.oRequestAnimationFrame
+ || d3_window.msRequestAnimationFrame
+ || function(callback) { setTimeout(callback, 17); };
+var π = Math.PI,
+ ε = 1e-6,
+ d3_radians = π / 180,
+ d3_degrees = 180 / π;
+
+function d3_sgn(x) {
+ return x > 0 ? 1 : x < 0 ? -1 : 0;
+}
+
+function d3_acos(x) {
+ return Math.acos(Math.max(-1, Math.min(1, x)));
+}
+
+function d3_asin(x) {
+ return x > 1 ? π / 2 : x < -1 ? -π / 2 : Math.asin(x);
+}
+
+function d3_sinh(x) {
+ return (Math.exp(x) - Math.exp(-x)) / 2;
+}
+
+function d3_cosh(x) {
+ return (Math.exp(x) + Math.exp(-x)) / 2;
+}
+
+function d3_haversin(x) {
+ return (x = Math.sin(x / 2)) * x;
+}
+d3.geo = {};
+function d3_identity(d) {
+ return d;
+}
+function d3_true() {
+ return true;
+}
+
+function d3_geo_spherical(cartesian) {
+ return [
+ Math.atan2(cartesian[1], cartesian[0]),
+ Math.asin(Math.max(-1, Math.min(1, cartesian[2])))
+ ];
+}
+
+function d3_geo_sphericalEqual(a, b) {
+ return Math.abs(a[0] - b[0]) < ε && Math.abs(a[1] - b[1]) < ε;
+}
+
+// General spherical polygon clipping algorithm: takes a polygon, cuts it into
+// visible line segments and rejoins the segments by interpolating along the
+// clip edge.
+function d3_geo_clipPolygon(segments, compare, inside, interpolate, listener) {
+ var subject = [],
+ clip = [];
+
+ segments.forEach(function(segment) {
+ if ((n = segment.length - 1) <= 0) return;
+ var n, p0 = segment[0], p1 = segment[n];
+
+ // If the first and last points of a segment are coincident, then treat as
+ // a closed ring.
+ // TODO if all rings are closed, then the winding order of the exterior
+ // ring should be checked.
+ if (d3_geo_sphericalEqual(p0, p1)) {
+ listener.lineStart();
+ for (var i = 0; i < n; ++i) listener.point((p0 = segment[i])[0], p0[1]);
+ listener.lineEnd();
+ return;
+ }
+
+ var a = {point: p0, points: segment, other: null, visited: false, entry: true, subject: true},
+ b = {point: p0, points: [p0], other: a, visited: false, entry: false, subject: false};
+ a.other = b;
+ subject.push(a);
+ clip.push(b);
+ a = {point: p1, points: [p1], other: null, visited: false, entry: false, subject: true};
+ b = {point: p1, points: [p1], other: a, visited: false, entry: true, subject: false};
+ a.other = b;
+ subject.push(a);
+ clip.push(b);
+ });
+ clip.sort(compare);
+ d3_geo_clipPolygonLinkCircular(subject);
+ d3_geo_clipPolygonLinkCircular(clip);
+ if (!subject.length) return;
+
+ if (inside) for (var i = 1, e = !inside(clip[0].point), n = clip.length; i < n; ++i) {
+ clip[i].entry = (e = !e);
+ }
+
+ var start = subject[0],
+ current,
+ points,
+ point;
+ while (1) {
+ // Find first unvisited intersection.
+ current = start;
+ while (current.visited) if ((current = current.next) === start) return;
+ points = current.points;
+ listener.lineStart();
+ do {
+ current.visited = current.other.visited = true;
+ if (current.entry) {
+ if (current.subject) {
+ for (var i = 0; i < points.length; i++) listener.point((point = points[i])[0], point[1]);
+ } else {
+ interpolate(current.point, current.next.point, 1, listener);
+ }
+ current = current.next;
+ } else {
+ if (current.subject) {
+ points = current.prev.points;
+ for (var i = points.length; --i >= 0;) listener.point((point = points[i])[0], point[1]);
+ } else {
+ interpolate(current.point, current.prev.point, -1, listener);
+ }
+ current = current.prev;
+ }
+ current = current.other;
+ points = current.points;
+ } while (!current.visited);
+ listener.lineEnd();
+ }
+}
+
+function d3_geo_clipPolygonLinkCircular(array) {
+ if (!(n = array.length)) return;
+ var n,
+ i = 0,
+ a = array[0],
+ b;
+ while (++i < n) {
+ a.next = b = array[i];
+ b.prev = a;
+ a = b;
+ }
+ a.next = b = array[0];
+ b.prev = a;
+}
+
+function d3_geo_clip(pointVisible, clipLine, interpolate) {
+ return function(listener) {
+ var line = clipLine(listener);
+
+ var clip = {
+ point: point,
+ lineStart: lineStart,
+ lineEnd: lineEnd,
+ polygonStart: function() {
+ clip.point = pointRing;
+ clip.lineStart = ringStart;
+ clip.lineEnd = ringEnd;
+ invisible = false;
+ invisibleArea = visibleArea = 0;
+ segments = [];
+ listener.polygonStart();
+ },
+ polygonEnd: function() {
+ clip.point = point;
+ clip.lineStart = lineStart;
+ clip.lineEnd = lineEnd;
+
+ segments = d3.merge(segments);
+ if (segments.length) {
+ d3_geo_clipPolygon(segments, d3_geo_clipSort, null, interpolate, listener);
+ } else if (visibleArea < -ε || invisible && invisibleArea < -ε) {
+ listener.lineStart();
+ interpolate(null, null, 1, listener);
+ listener.lineEnd();
+ }
+ listener.polygonEnd();
+ segments = null;
+ },
+ sphere: function() {
+ listener.polygonStart();
+ listener.lineStart();
+ interpolate(null, null, 1, listener);
+ listener.lineEnd();
+ listener.polygonEnd();
+ }
+ };
+
+ function point(λ, φ) { if (pointVisible(λ, φ)) listener.point(λ, φ); }
+ function pointLine(λ, φ) { line.point(λ, φ); }
+ function lineStart() { clip.point = pointLine; line.lineStart(); }
+ function lineEnd() { clip.point = point; line.lineEnd(); }
+
+ var segments,
+ visibleArea,
+ invisibleArea,
+ invisible;
+
+ var buffer = d3_geo_clipBufferListener(),
+ ringListener = clipLine(buffer),
+ ring;
+
+ function pointRing(λ, φ) {
+ ringListener.point(λ, φ);
+ ring.push([λ, φ]);
+ }
+
+ function ringStart() {
+ ringListener.lineStart();
+ ring = [];
+ }
+
+ function ringEnd() {
+ pointRing(ring[0][0], ring[0][1]);
+ ringListener.lineEnd();
+
+ var clean = ringListener.clean(),
+ ringSegments = buffer.buffer(),
+ segment,
+ n = ringSegments.length;
+
+ // TODO compute on-the-fly?
+ if (!n) {
+ invisible = true;
+ invisibleArea += d3_geo_clipAreaRing(ring, -1);
+ ring = null;
+ return;
+ }
+ ring = null;
+
+ // No intersections.
+ // TODO compute on-the-fly?
+ if (clean & 1) {
+ segment = ringSegments[0];
+ visibleArea += d3_geo_clipAreaRing(segment, 1);
+ var n = segment.length - 1,
+ i = -1,
+ point;
+ listener.lineStart();
+ while (++i < n) listener.point((point = segment[i])[0], point[1]);
+ listener.lineEnd();
+ return;
+ }
+
+ // Rejoin connected segments.
+ // TODO reuse bufferListener.rejoin()?
+ if (n > 1 && clean & 2) ringSegments.push(ringSegments.pop().concat(ringSegments.shift()));
+
+ segments.push(ringSegments.filter(d3_geo_clipSegmentLength1));
+ }
+
+ return clip;
+ };
+}
+
+function d3_geo_clipSegmentLength1(segment) {
+ return segment.length > 1;
+}
+
+function d3_geo_clipBufferListener() {
+ var lines = [],
+ line;
+ return {
+ lineStart: function() { lines.push(line = []); },
+ point: function(λ, φ) { line.push([λ, φ]); },
+ lineEnd: d3_noop,
+ buffer: function() {
+ var buffer = lines;
+ lines = [];
+ line = null;
+ return buffer;
+ },
+ rejoin: function() {
+ if (lines.length > 1) lines.push(lines.pop().concat(lines.shift()));
+ }
+ };
+}
+
+// Approximate polygon ring area (×2, since we only need the sign).
+// For an invisible polygon ring, we rotate longitudinally by 180°.
+// The invisible parameter should be 1, or -1 to rotate longitudinally.
+// Based on Robert. G. Chamberlain and William H. Duquette,
+// “Some Algorithms for Polygons on a Sphere”,
+// http://trs-new.jpl.nasa.gov/dspace/handle/2014/40409
+function d3_geo_clipAreaRing(ring, invisible) {
+ if (!(n = ring.length)) return 0;
+ var n,
+ i = 0,
+ area = 0,
+ p = ring[0],
+ λ = p[0],
+ φ = p[1],
+ cosφ = Math.cos(φ),
+ x0 = Math.atan2(invisible * Math.sin(λ) * cosφ, Math.sin(φ)),
+ y0 = 1 - invisible * Math.cos(λ) * cosφ,
+ x1 = x0,
+ x, // λ'; λ rotated to south pole.
+ y; // φ' = 1 + sin(φ); φ rotated to south pole.
+ while (++i < n) {
+ p = ring[i];
+ cosφ = Math.cos(φ = p[1]);
+ x = Math.atan2(invisible * Math.sin(λ = p[0]) * cosφ, Math.sin(φ));
+ y = 1 - invisible * Math.cos(λ) * cosφ;
+
+ // If both the current point and the previous point are at the north pole,
+ // skip this point.
+ if (Math.abs(y0 - 2) < ε && Math.abs(y - 2) < ε) continue;
+
+ // If this or the previous point is at the south pole, or if this segment
+ // goes through the south pole, the area is 0.
+ if (Math.abs(y) < ε || Math.abs(y0) < ε) {}
+
+ // If this segment goes through either pole…
+ else if (Math.abs(Math.abs(x - x0) - π) < ε) {
+ // For the north pole, compute lune area.
+ if (y + y0 > 2) area += 4 * (x - x0);
+ // For the south pole, the area is zero.
+ }
+
+ // If the previous point is at the north pole, then compute lune area.
+ else if (Math.abs(y0 - 2) < ε) area += 4 * (x - x1);
+
+ // Otherwise, the spherical triangle area is approximately
+ // δλ * (1 + sinφ0 + 1 + sinφ) / 2.
+ else area += ((3 * π + x - x0) % (2 * π) - π) * (y0 + y);
+
+ x1 = x0, x0 = x, y0 = y;
+ }
+ return area;
+}
+
+// Intersection points are sorted along the clip edge. For both antimeridian
+// cutting and circle clipping, the same comparison is used.
+function d3_geo_clipSort(a, b) {
+ return ((a = a.point)[0] < 0 ? a[1] - π / 2 - ε : π / 2 - a[1])
+ - ((b = b.point)[0] < 0 ? b[1] - π / 2 - ε : π / 2 - b[1]);
+}
+
+var d3_geo_clipAntimeridian = d3_geo_clip(d3_true, d3_geo_clipAntimeridianLine, d3_geo_clipAntimeridianInterpolate);
+
+// Takes a line and cuts into visible segments. Return values:
+// 0: there were intersections or the line was empty.
+// 1: no intersections.
+// 2: there were intersections, and the first and last segments should be
+// rejoined.
+function d3_geo_clipAntimeridianLine(listener) {
+ var λ0 = NaN,
+ φ0 = NaN,
+ sλ0 = NaN,
+ clean; // no intersections
+
+ return {
+ lineStart: function() {
+ listener.lineStart();
+ clean = 1;
+ },
+ point: function(λ1, φ1) {
+ var sλ1 = λ1 > 0 ? π : -π,
+ dλ = Math.abs(λ1 - λ0);
+ if (Math.abs(dλ - π) < ε) { // line crosses a pole
+ listener.point(λ0, φ0 = (φ0 + φ1) / 2 > 0 ? π / 2 : -π / 2);
+ listener.point(sλ0, φ0);
+ listener.lineEnd();
+ listener.lineStart();
+ listener.point(sλ1, φ0);
+ listener.point( λ1, φ0);
+ clean = 0;
+ } else if (sλ0 !== sλ1 && dλ >= π) { // line crosses antimeridian
+ // handle degeneracies
+ if (Math.abs(λ0 - sλ0) < ε) λ0 -= sλ0 * ε;
+ if (Math.abs(λ1 - sλ1) < ε) λ1 -= sλ1 * ε;
+ φ0 = d3_geo_clipAntimeridianIntersect(λ0, φ0, λ1, φ1);
+ listener.point(sλ0, φ0);
+ listener.lineEnd();
+ listener.lineStart();
+ listener.point(sλ1, φ0);
+ clean = 0;
+ }
+ listener.point(λ0 = λ1, φ0 = φ1);
+ sλ0 = sλ1;
+ },
+ lineEnd: function() {
+ listener.lineEnd();
+ λ0 = φ0 = NaN;
+ },
+ // if there are intersections, we always rejoin the first and last segments.
+ clean: function() { return 2 - clean; }
+ };
+}
+
+function d3_geo_clipAntimeridianIntersect(λ0, φ0, λ1, φ1) {
+ var cosφ0,
+ cosφ1,
+ sinλ0_λ1 = Math.sin(λ0 - λ1);
+ return Math.abs(sinλ0_λ1) > ε
+ ? Math.atan((Math.sin(φ0) * (cosφ1 = Math.cos(φ1)) * Math.sin(λ1)
+ - Math.sin(φ1) * (cosφ0 = Math.cos(φ0)) * Math.sin(λ0))
+ / (cosφ0 * cosφ1 * sinλ0_λ1))
+ : (φ0 + φ1) / 2;
+}
+
+function d3_geo_clipAntimeridianInterpolate(from, to, direction, listener) {
+ var φ;
+ if (from == null) {
+ φ = direction * π / 2;
+ listener.point(-π, φ);
+ listener.point( 0, φ);
+ listener.point( π, φ);
+ listener.point( π, 0);
+ listener.point( π, -φ);
+ listener.point( 0, -φ);
+ listener.point(-π, -φ);
+ listener.point(-π, 0);
+ listener.point(-π, φ);
+ } else if (Math.abs(from[0] - to[0]) > ε) {
+ var s = (from[0] < to[0] ? 1 : -1) * π;
+ φ = direction * s / 2;
+ listener.point(-s, φ);
+ listener.point( 0, φ);
+ listener.point( s, φ);
+ } else {
+ listener.point(to[0], to[1]);
+ }
+}
+// TODO
+// cross and scale return new vectors,
+// whereas add and normalize operate in-place
+
+function d3_geo_cartesian(spherical) {
+ var λ = spherical[0],
+ φ = spherical[1],
+ cosφ = Math.cos(φ);
+ return [
+ cosφ * Math.cos(λ),
+ cosφ * Math.sin(λ),
+ Math.sin(φ)
+ ];
+}
+
+function d3_geo_cartesianDot(a, b) {
+ return a[0] * b[0] + a[1] * b[1] + a[2] * b[2];
+}
+
+function d3_geo_cartesianCross(a, b) {
+ return [
+ a[1] * b[2] - a[2] * b[1],
+ a[2] * b[0] - a[0] * b[2],
+ a[0] * b[1] - a[1] * b[0]
+ ];
+}
+
+function d3_geo_cartesianAdd(a, b) {
+ a[0] += b[0];
+ a[1] += b[1];
+ a[2] += b[2];
+}
+
+function d3_geo_cartesianScale(vector, k) {
+ return [
+ vector[0] * k,
+ vector[1] * k,
+ vector[2] * k
+ ];
+}
+
+function d3_geo_cartesianNormalize(d) {
+ var l = Math.sqrt(d[0] * d[0] + d[1] * d[1] + d[2] * d[2]);
+ d[0] /= l;
+ d[1] /= l;
+ d[2] /= l;
+}
+
+function d3_geo_equirectangular(λ, φ) {
+ return [λ, φ];
+}
+
+(d3.geo.equirectangular = function() {
+ return d3_geo_projection(d3_geo_equirectangular);
+}).raw = d3_geo_equirectangular.invert = d3_geo_equirectangular;
+
+d3.geo.rotation = function(rotate) {
+ rotate = d3_geo_rotation(rotate[0] % 360 * d3_radians, rotate[1] * d3_radians, rotate.length > 2 ? rotate[2] * d3_radians : 0);
+
+ function forward(coordinates) {
+ coordinates = rotate(coordinates[0] * d3_radians, coordinates[1] * d3_radians);
+ return coordinates[0] *= d3_degrees, coordinates[1] *= d3_degrees, coordinates;
+ }
+
+ forward.invert = function(coordinates) {
+ coordinates = rotate.invert(coordinates[0] * d3_radians, coordinates[1] * d3_radians);
+ return coordinates[0] *= d3_degrees, coordinates[1] *= d3_degrees, coordinates;
+ };
+
+ return forward;
+};
+
+// Note: |δλ| must be < 2π
+function d3_geo_rotation(δλ, δφ, δγ) {
+ return δλ ? (δφ || δγ ? d3_geo_compose(d3_geo_rotationλ(δλ), d3_geo_rotationφγ(δφ, δγ))
+ : d3_geo_rotationλ(δλ))
+ : (δφ || δγ ? d3_geo_rotationφγ(δφ, δγ)
+ : d3_geo_equirectangular);
+}
+
+function d3_geo_forwardRotationλ(δλ) {
+ return function(λ, φ) {
+ return λ += δλ, [λ > π ? λ - 2 * π : λ < -π ? λ + 2 * π : λ, φ];
+ };
+}
+
+function d3_geo_rotationλ(δλ) {
+ var rotation = d3_geo_forwardRotationλ(δλ);
+ rotation.invert = d3_geo_forwardRotationλ(-δλ);
+ return rotation;
+}
+
+function d3_geo_rotationφγ(δφ, δγ) {
+ var cosδφ = Math.cos(δφ),
+ sinδφ = Math.sin(δφ),
+ cosδγ = Math.cos(δγ),
+ sinδγ = Math.sin(δγ);
+
+ function rotation(λ, φ) {
+ var cosφ = Math.cos(φ),
+ x = Math.cos(λ) * cosφ,
+ y = Math.sin(λ) * cosφ,
+ z = Math.sin(φ),
+ k = z * cosδφ + x * sinδφ;
+ return [
+ Math.atan2(y * cosδγ - k * sinδγ, x * cosδφ - z * sinδφ),
+ Math.asin(Math.max(-1, Math.min(1, k * cosδγ + y * sinδγ)))
+ ];
+ }
+
+ rotation.invert = function(λ, φ) {
+ var cosφ = Math.cos(φ),
+ x = Math.cos(λ) * cosφ,
+ y = Math.sin(λ) * cosφ,
+ z = Math.sin(φ),
+ k = z * cosδγ - y * sinδγ;
+ return [
+ Math.atan2(y * cosδγ + z * sinδγ, x * cosδφ + k * sinδφ),
+ Math.asin(Math.max(-1, Math.min(1, k * cosδφ - x * sinδφ)))
+ ];
+ };
+
+ return rotation;
+}
+
+d3.geo.circle = function() {
+ var origin = [0, 0],
+ angle,
+ precision = 6,
+ interpolate;
+
+ function circle() {
+ var center = typeof origin === "function" ? origin.apply(this, arguments) : origin,
+ rotate = d3_geo_rotation(-center[0] * d3_radians, -center[1] * d3_radians, 0).invert,
+ ring = [];
+
+ interpolate(null, null, 1, {
+ point: function(x, y) {
+ ring.push(x = rotate(x, y));
+ x[0] *= d3_degrees, x[1] *= d3_degrees;
+ }
+ });
+
+ return {type: "Polygon", coordinates: [ring]};
+ }
+
+ circle.origin = function(x) {
+ if (!arguments.length) return origin;
+ origin = x;
+ return circle;
+ };
+
+ circle.angle = function(x) {
+ if (!arguments.length) return angle;
+ interpolate = d3_geo_circleInterpolate((angle = +x) * d3_radians, precision * d3_radians);
+ return circle;
+ };
+
+ circle.precision = function(_) {
+ if (!arguments.length) return precision;
+ interpolate = d3_geo_circleInterpolate(angle * d3_radians, (precision = +_) * d3_radians);
+ return circle;
+ };
+
+ return circle.angle(90);
+};
+
+// Interpolates along a circle centered at [0°, 0°], with a given radius and
+// precision.
+function d3_geo_circleInterpolate(radius, precision) {
+ var cr = Math.cos(radius),
+ sr = Math.sin(radius);
+ return function(from, to, direction, listener) {
+ if (from != null) {
+ from = d3_geo_circleAngle(cr, from);
+ to = d3_geo_circleAngle(cr, to);
+ if (direction > 0 ? from < to: from > to) from += direction * 2 * π;
+ } else {
+ from = radius + direction * 2 * π;
+ to = radius;
+ }
+ var point;
+ for (var step = direction * precision, t = from; direction > 0 ? t > to : t < to; t -= step) {
+ listener.point((point = d3_geo_spherical([
+ cr,
+ -sr * Math.cos(t),
+ -sr * Math.sin(t)
+ ]))[0], point[1]);
+ }
+ };
+}
+
+// Signed angle of a cartesian point relative to [cr, 0, 0].
+function d3_geo_circleAngle(cr, point) {
+ var a = d3_geo_cartesian(point);
+ a[0] -= cr;
+ d3_geo_cartesianNormalize(a);
+ var angle = d3_acos(-a[1]);
+ return ((-a[2] < 0 ? -angle : angle) + 2 * Math.PI - ε) % (2 * Math.PI);
+}
+
+// Clip features against a small circle centered at [0°, 0°].
+function d3_geo_clipCircle(radius) {
+ var cr = Math.cos(radius),
+ smallRadius = cr > 0,
+ notHemisphere = Math.abs(cr) > ε, // TODO optimise for this common case
+ interpolate = d3_geo_circleInterpolate(radius, 6 * d3_radians);
+
+ return d3_geo_clip(visible, clipLine, interpolate);
+
+ function visible(λ, φ) {
+ return Math.cos(λ) * Math.cos(φ) > cr;
+ }
+
+ // Takes a line and cuts into visible segments. Return values used for
+ // polygon clipping:
+ // 0: there were intersections or the line was empty.
+ // 1: no intersections.
+ // 2: there were intersections, and the first and last segments should be
+ // rejoined.
+ function clipLine(listener) {
+ var point0, // previous point
+ c0, // code for previous point
+ v0, // visibility of previous point
+ v00, // visibility of first point
+ clean; // no intersections
+ return {
+ lineStart: function() {
+ v00 = v0 = false;
+ clean = 1;
+ },
+ point: function(λ, φ) {
+ var point1 = [λ, φ],
+ point2,
+ v = visible(λ, φ),
+ c = smallRadius
+ ? v ? 0 : code(λ, φ)
+ : v ? code(λ + (λ < 0 ? π : -π), φ) : 0;
+ if (!point0 && (v00 = v0 = v)) listener.lineStart();
+ // Handle degeneracies.
+ // TODO ignore if not clipping polygons.
+ if (v !== v0) {
+ point2 = intersect(point0, point1);
+ if (d3_geo_sphericalEqual(point0, point2) || d3_geo_sphericalEqual(point1, point2)) {
+ point1[0] += ε;
+ point1[1] += ε;
+ v = visible(point1[0], point1[1]);
+ }
+ }
+ if (v !== v0) {
+ clean = 0;
+ if (v) {
+ // outside going in
+ listener.lineStart();
+ point2 = intersect(point1, point0);
+ listener.point(point2[0], point2[1]);
+ } else {
+ // inside going out
+ point2 = intersect(point0, point1);
+ listener.point(point2[0], point2[1]);
+ listener.lineEnd();
+ }
+ point0 = point2;
+ } else if (notHemisphere && point0 && smallRadius ^ v) {
+ var t;
+ // If the codes for two points are different, or are both zero,
+ // and there this segment intersects with the small circle.
+ if (!(c & c0) && (t = intersect(point1, point0, true))) {
+ clean = 0;
+ if (smallRadius) {
+ listener.lineStart();
+ listener.point(t[0][0], t[0][1]);
+ listener.point(t[1][0], t[1][1]);
+ listener.lineEnd();
+ } else {
+ listener.point(t[1][0], t[1][1]);
+ listener.lineEnd();
+ listener.lineStart();
+ listener.point(t[0][0], t[0][1]);
+ }
+ }
+ }
+ if (v && (!point0 || !d3_geo_sphericalEqual(point0, point1))) {
+ listener.point(point1[0], point1[1]);
+ }
+ point0 = point1, v0 = v, c0 = c;
+ },
+ lineEnd: function() {
+ if (v0) listener.lineEnd();
+ point0 = null;
+ },
+ // Rejoin first and last segments if there were intersections and the first
+ // and last points were visible.
+ clean: function() { return clean | ((v00 && v0) << 1); }
+ };
+ }
+
+ // Intersects the great circle between a and b with the clip circle.
+ function intersect(a, b, two) {
+ var pa = d3_geo_cartesian(a),
+ pb = d3_geo_cartesian(b);
+
+ // We have two planes, n1.p = d1 and n2.p = d2.
+ // Find intersection line p(t) = c1 n1 + c2 n2 + t (n1 ⨯ n2).
+ var n1 = [1, 0, 0], // normal
+ n2 = d3_geo_cartesianCross(pa, pb),
+ n2n2 = d3_geo_cartesianDot(n2, n2),
+ n1n2 = n2[0], // d3_geo_cartesianDot(n1, n2),
+ determinant = n2n2 - n1n2 * n1n2;
+
+ // Two polar points.
+ if (!determinant) return !two && a;
+
+ var c1 = cr * n2n2 / determinant,
+ c2 = -cr * n1n2 / determinant,
+ n1xn2 = d3_geo_cartesianCross(n1, n2),
+ A = d3_geo_cartesianScale(n1, c1),
+ B = d3_geo_cartesianScale(n2, c2);
+ d3_geo_cartesianAdd(A, B);
+
+ // Solve |p(t)|^2 = 1.
+ var u = n1xn2,
+ w = d3_geo_cartesianDot(A, u),
+ uu = d3_geo_cartesianDot(u, u),
+ t2 = w * w - uu * (d3_geo_cartesianDot(A, A) - 1);
+
+ if (t2 < 0) return;
+
+ var t = Math.sqrt(t2),
+ q = d3_geo_cartesianScale(u, (-w - t) / uu);
+ d3_geo_cartesianAdd(q, A);
+ q = d3_geo_spherical(q);
+ if (!two) return q;
+
+ // Two intersection points.
+ var λ0 = a[0],
+ λ1 = b[0],
+ φ0 = a[1],
+ φ1 = b[1],
+ z;
+ if (λ1 < λ0) z = λ0, λ0 = λ1, λ1 = z;
+ var δλ = λ1 - λ0,
+ polar = Math.abs(δλ - π) < ε,
+ meridian = polar || δλ < ε;
+
+ if (!polar && φ1 < φ0) z = φ0, φ0 = φ1, φ1 = z;
+
+ // Check that the first point is between a and b.
+ if (meridian
+ ? polar
+ ? φ0 + φ1 > 0 ^ q[1] < (Math.abs(q[0] - λ0) < ε ? φ0 : φ1)
+ : φ0 <= q[1] && q[1] <= φ1
+ : δλ > π ^ (λ0 <= q[0] && q[0] <= λ1)) {
+ var q1 = d3_geo_cartesianScale(u, (-w + t) / uu);
+ d3_geo_cartesianAdd(q1, A);
+ return [q, d3_geo_spherical(q1)];
+ }
+ }
+
+ // Generates a 4-bit vector representing the location of a point relative to
+ // the small circle's bounding box.
+ function code(λ, φ) {
+ var r = smallRadius ? radius : π - radius,
+ code = 0;
+ if (λ < -r) code |= 1; // left
+ else if (λ > r) code |= 2; // right
+ if (φ < -r) code |= 4; // below
+ else if (φ > r) code |= 8; // above
+ return code;
+ }
+}
+
+var d3_geo_clipViewMAX = 1e9;
+
+function d3_geo_clipView(x0, y0, x1, y1) {
+ return function(listener) {
+ var listener_ = listener,
+ bufferListener = d3_geo_clipBufferListener(),
+ segments,
+ polygon,
+ ring;
+
+ var clip = {
+ point: point,
+ lineStart: lineStart,
+ lineEnd: lineEnd,
+ polygonStart: function() {
+ listener = bufferListener;
+ segments = [];
+ polygon = [];
+ },
+ polygonEnd: function() {
+ listener = listener_;
+ if ((segments = d3.merge(segments)).length) {
+ listener.polygonStart();
+ d3_geo_clipPolygon(segments, compare, inside, interpolate, listener);
+ listener.polygonEnd();
+ } else if (insidePolygon([x0, y0])) {
+ listener.polygonStart(), listener.lineStart();
+ interpolate(null, null, 1, listener);
+ listener.lineEnd(), listener.polygonEnd();
+ }
+ segments = polygon = ring = null;
+ }
+ };
+
+ function inside(point) {
+ var a = corner(point, -1),
+ i = insidePolygon([a === 0 || a === 3 ? x0 : x1, a > 1 ? y1 : y0]);
+ return i;
+ }
+
+ function insidePolygon(p) {
+ var wn = 0, // the winding number counter
+ n = polygon.length,
+ y = p[1];
+
+ for (var i = 0; i < n; ++i) {
+ for (var j = 1, v = polygon[i], m = v.length, a = v[0]; j < m; ++j) {
+ b = v[j];
+ if (a[1] <= y) {
+ if (b[1] > y && isLeft(a, b, p) > 0) ++wn;
+ } else {
+ if (b[1] <= y && isLeft(a, b, p) < 0) --wn;
+ }
+ a = b;
+ }
+ }
+ return wn !== 0;
+ }
+
+ function isLeft(a, b, c) {
+ return (b[0] - a[0]) * (c[1] - a[1]) - (c[0] - a[0]) * (b[1] - a[1]);
+ }
+
+ function interpolate(from, to, direction, listener) {
+ var a = 0, a1 = 0;
+ if (from == null ||
+ (a = corner(from, direction)) !== (a1 = corner(to, direction)) ||
+ comparePoints(from, to) < 0 ^ direction > 0) {
+ do {
+ listener.point(a === 0 || a === 3 ? x0 : x1, a > 1 ? y1 : y0);
+ } while ((a = (a + direction + 4) % 4) !== a1);
+ } else {
+ listener.point(to[0], to[1]);
+ }
+ }
+
+ function visible(x, y) {
+ return x0 <= x && x <= x1 && y0 <= y && y <= y1;
+ }
+
+ function point(x, y) {
+ if (visible(x, y)) listener.point(x, y);
+ }
+
+ var x__, y__, v__, // first point
+ x_, y_, v_, // previous point
+ first;
+
+ function lineStart() {
+ clip.point = linePoint;
+ if (polygon) polygon.push(ring = []);
+ first = true;
+ v_ = false;
+ x_ = y_ = NaN;
+ }
+
+ function lineEnd() {
+ // TODO rather than special-case polygons, simply handle them separately.
+ // Ideally, coincident intersection points should be jittered to avoid
+ // clipping issues.
+ if (segments) {
+ linePoint(x__, y__);
+ if (v__ && v_) bufferListener.rejoin();
+ segments.push(bufferListener.buffer());
+ }
+ clip.point = point;
+ if (v_) listener.lineEnd();
+ }
+
+ function linePoint(x, y) {
+ x = Math.max(-d3_geo_clipViewMAX, Math.min(d3_geo_clipViewMAX, x));
+ y = Math.max(-d3_geo_clipViewMAX, Math.min(d3_geo_clipViewMAX, y));
+ var v = visible(x, y);
+ if (polygon) ring.push([x, y]);
+ if (first) {
+ x__ = x, y__ = y, v__ = v;
+ first = false;
+ if (v) {
+ listener.lineStart();
+ listener.point(x, y);
+ }
+ } else {
+ if (v && v_) listener.point(x, y);
+ else {
+ var a = [x_, y_],
+ b = [x, y];
+ if (clipLine(a, b)) {
+ if (!v_) {
+ listener.lineStart();
+ listener.point(a[0], a[1]);
+ }
+ listener.point(b[0], b[1]);
+ if (!v) listener.lineEnd();
+ } else {
+ listener.lineStart();
+ listener.point(x, y);
+ }
+ }
+ }
+ x_ = x, y_ = y, v_ = v;
+ }
+
+ return clip;
+ };
+
+ function corner(p, direction) {
+ return Math.abs(p[0] - x0) < ε ? direction > 0 ? 0 : 3
+ : Math.abs(p[0] - x1) < ε ? direction > 0 ? 2 : 1
+ : Math.abs(p[1] - y0) < ε ? direction > 0 ? 1 : 0
+ : direction > 0 ? 3 : 2; // Math.abs(p[1] - y1) < ε
+ }
+
+ function compare(a, b) {
+ return comparePoints(a.point, b.point);
+ }
+
+ function comparePoints(a, b) {
+ var ca = corner(a, 1),
+ cb = corner(b, 1);
+ return ca !== cb ? ca - cb
+ : ca === 0 ? b[1] - a[1]
+ : ca === 1 ? a[0] - b[0]
+ : ca === 2 ? a[1] - b[1]
+ : b[0] - a[0];
+ }
+
+ // Liang–Barsky line clipping.
+ function clipLine(a, b) {
+ var dx = b[0] - a[0],
+ dy = b[1] - a[1],
+ t = [0, 1];
+
+ if (Math.abs(dx) < ε && Math.abs(dy) < ε) return x0 <= a[0] && a[0] <= x1 && y0 <= a[1] && a[1] <= y1;
+
+ if (d3_geo_clipViewT(x0 - a[0], dx, t) &&
+ d3_geo_clipViewT(a[0] - x1, -dx, t) &&
+ d3_geo_clipViewT(y0 - a[1], dy, t) &&
+ d3_geo_clipViewT(a[1] - y1, -dy, t)) {
+ if (t[1] < 1) {
+ b[0] = a[0] + t[1] * dx;
+ b[1] = a[1] + t[1] * dy;
+ }
+ if (t[0] > 0) {
+ a[0] += t[0] * dx;
+ a[1] += t[0] * dy;
+ }
+ return true;
+ }
+
+ return false;
+ }
+}
+
+function d3_geo_clipViewT(num, denominator, t) {
+ if (Math.abs(denominator) < ε) return num <= 0;
+
+ var u = num / denominator;
+
+ if (denominator > 0) {
+ if (u > t[1]) return false;
+ if (u > t[0]) t[0] = u;
+ } else {
+ if (u < t[0]) return false;
+ if (u < t[1]) t[1] = u;
+ }
+ return true;
+}
+function d3_geo_compose(a, b) {
+
+ function compose(x, y) {
+ return x = a(x, y), b(x[0], x[1]);
+ }
+
+ if (a.invert && b.invert) compose.invert = function(x, y) {
+ return x = b.invert(x, y), x && a.invert(x[0], x[1]);
+ };
+
+ return compose;
+}
+
+d3.geo.stream = function(object, listener) {
+ if (d3_geo_streamObjectType.hasOwnProperty(object.type)) {
+ d3_geo_streamObjectType[object.type](object, listener);
+ } else {
+ d3_geo_streamGeometry(object, listener);
+ }
+};
+
+function d3_geo_streamGeometry(geometry, listener) {
+ if (d3_geo_streamGeometryType.hasOwnProperty(geometry.type)) {
+ d3_geo_streamGeometryType[geometry.type](geometry, listener);
+ }
+}
+
+var d3_geo_streamObjectType = {
+ Feature: function(feature, listener) {
+ d3_geo_streamGeometry(feature.geometry, listener);
+ },
+ FeatureCollection: function(object, listener) {
+ var features = object.features, i = -1, n = features.length;
+ while (++i < n) d3_geo_streamGeometry(features[i].geometry, listener);
+ }
+};
+
+var d3_geo_streamGeometryType = {
+ Sphere: function(object, listener) {
+ listener.sphere();
+ },
+ Point: function(object, listener) {
+ var coordinate = object.coordinates;
+ listener.point(coordinate[0], coordinate[1]);
+ },
+ MultiPoint: function(object, listener) {
+ var coordinates = object.coordinates, i = -1, n = coordinates.length, coordinate;
+ while (++i < n) coordinate = coordinates[i], listener.point(coordinate[0], coordinate[1]);
+ },
+ LineString: function(object, listener) {
+ d3_geo_streamLine(object.coordinates, listener, 0);
+ },
+ MultiLineString: function(object, listener) {
+ var coordinates = object.coordinates, i = -1, n = coordinates.length;
+ while (++i < n) d3_geo_streamLine(coordinates[i], listener, 0);
+ },
+ Polygon: function(object, listener) {
+ d3_geo_streamPolygon(object.coordinates, listener);
+ },
+ MultiPolygon: function(object, listener) {
+ var coordinates = object.coordinates, i = -1, n = coordinates.length;
+ while (++i < n) d3_geo_streamPolygon(coordinates[i], listener);
+ },
+ GeometryCollection: function(object, listener) {
+ var geometries = object.geometries, i = -1, n = geometries.length;
+ while (++i < n) d3_geo_streamGeometry(geometries[i], listener);
+ }
+};
+
+function d3_geo_streamLine(coordinates, listener, closed) {
+ var i = -1, n = coordinates.length - closed, coordinate;
+ listener.lineStart();
+ while (++i < n) coordinate = coordinates[i], listener.point(coordinate[0], coordinate[1]);
+ listener.lineEnd();
+}
+
+function d3_geo_streamPolygon(coordinates, listener) {
+ var i = -1, n = coordinates.length;
+ listener.polygonStart();
+ while (++i < n) d3_geo_streamLine(coordinates[i], listener, 1);
+ listener.polygonEnd();
+}
+
+function d3_geo_resample(project) {
+ var δ2 = .5, // precision, px²
+ maxDepth = 16;
+
+ function resample(stream) {
+ var λ0, x0, y0, a0, b0, c0; // previous point
+
+ var resample = {
+ point: point,
+ lineStart: lineStart,
+ lineEnd: lineEnd,
+ polygonStart: function() { stream.polygonStart(); resample.lineStart = polygonLineStart; },
+ polygonEnd: function() { stream.polygonEnd(); resample.lineStart = lineStart; }
+ };
+
+ function point(x, y) {
+ x = project(x, y);
+ stream.point(x[0], x[1]);
+ }
+
+ function lineStart() {
+ x0 = NaN;
+ resample.point = linePoint;
+ stream.lineStart();
+ }
+
+ function linePoint(λ, φ) {
+ var c = d3_geo_cartesian([λ, φ]), p = project(λ, φ);
+ resampleLineTo(x0, y0, λ0, a0, b0, c0, x0 = p[0], y0 = p[1], λ0 = λ, a0 = c[0], b0 = c[1], c0 = c[2], maxDepth, stream);
+ stream.point(x0, y0);
+ }
+
+ function lineEnd() {
+ resample.point = point;
+ stream.lineEnd();
+ }
+
+ function polygonLineStart() {
+ var λ00, φ00, x00, y00, a00, b00, c00; // first point
+
+ lineStart();
+
+ resample.point = function(λ, φ) {
+ linePoint(λ00 = λ, φ00 = φ), x00 = x0, y00 = y0, a00 = a0, b00 = b0, c00 = c0;
+ resample.point = linePoint;
+ };
+
+ resample.lineEnd = function() {
+ resampleLineTo(x0, y0, λ0, a0, b0, c0, x00, y00, λ00, a00, b00, c00, maxDepth, stream);
+ resample.lineEnd = lineEnd;
+ lineEnd();
+ };
+ }
+
+ return resample;
+ }
+
+ function resampleLineTo(x0, y0, λ0, a0, b0, c0, x1, y1, λ1, a1, b1, c1, depth, stream) {
+ var dx = x1 - x0,
+ dy = y1 - y0,
+ d2 = dx * dx + dy * dy;
+ if (d2 > 4 * δ2 && depth--) {
+ var a = a0 + a1,
+ b = b0 + b1,
+ c = c0 + c1,
+ m = Math.sqrt(a * a + b * b + c * c),
+ φ2 = Math.asin(c /= m),
+ λ2 = Math.abs(Math.abs(c) - 1) < ε ? (λ0 + λ1) / 2 : Math.atan2(b, a),
+ p = project(λ2, φ2),
+ x2 = p[0],
+ y2 = p[1],
+ dx2 = x2 - x0,
+ dy2 = y2 - y0,
+ dz = dy * dx2 - dx * dy2;
+ if (dz * dz / d2 > δ2 || Math.abs((dx * dx2 + dy * dy2) / d2 - .5) > .3) {
+ resampleLineTo(x0, y0, λ0, a0, b0, c0, x2, y2, λ2, a /= m, b /= m, c, depth, stream);
+ stream.point(x2, y2);
+ resampleLineTo(x2, y2, λ2, a, b, c, x1, y1, λ1, a1, b1, c1, depth, stream);
+ }
+ }
+ }
+
+ resample.precision = function(_) {
+ if (!arguments.length) return Math.sqrt(δ2);
+ maxDepth = (δ2 = _ * _) > 0 && 16;
+ return resample;
+ };
+
+ return resample;
+}
+
+d3.geo.projection = d3_geo_projection;
+d3.geo.projectionMutator = d3_geo_projectionMutator;
+
+function d3_geo_projection(project) {
+ return d3_geo_projectionMutator(function() { return project; })();
+}
+
+function d3_geo_projectionMutator(projectAt) {
+ var project,
+ rotate,
+ projectRotate,
+ projectResample = d3_geo_resample(function(x, y) { x = project(x, y); return [x[0] * k + δx, δy - x[1] * k]; }),
+ k = 150, // scale
+ x = 480, y = 250, // translate
+ λ = 0, φ = 0, // center
+ δλ = 0, δφ = 0, δγ = 0, // rotate
+ δx, δy, // center
+ preclip = d3_geo_clipAntimeridian,
+ postclip = d3_identity,
+ clipAngle = null,
+ clipExtent = null;
+
+ function projection(point) {
+ point = projectRotate(point[0] * d3_radians, point[1] * d3_radians);
+ return [point[0] * k + δx, δy - point[1] * k];
+ }
+
+ function invert(point) {
+ point = projectRotate.invert((point[0] - δx) / k, (δy - point[1]) / k);
+ return point && [point[0] * d3_degrees, point[1] * d3_degrees];
+ }
+
+ projection.stream = function(stream) {
+ return d3_geo_projectionRadiansRotate(rotate, preclip(projectResample(postclip(stream))));
+ };
+
+ projection.clipAngle = function(_) {
+ if (!arguments.length) return clipAngle;
+ preclip = _ == null ? (clipAngle = _, d3_geo_clipAntimeridian) : d3_geo_clipCircle((clipAngle = +_) * d3_radians);
+ return projection;
+ };
+
+ projection.clipExtent = function(_) {
+ if (!arguments.length) return clipExtent;
+ clipExtent = _;
+ postclip = _ == null ? d3_identity : d3_geo_clipView(_[0][0], _[0][1], _[1][0], _[1][1]);
+ return projection;
+ };
+
+ projection.scale = function(_) {
+ if (!arguments.length) return k;
+ k = +_;
+ return reset();
+ };
+
+ projection.translate = function(_) {
+ if (!arguments.length) return [x, y];
+ x = +_[0];
+ y = +_[1];
+ return reset();
+ };
+
+ projection.center = function(_) {
+ if (!arguments.length) return [λ * d3_degrees, φ * d3_degrees];
+ λ = _[0] % 360 * d3_radians;
+ φ = _[1] % 360 * d3_radians;
+ return reset();
+ };
+
+ projection.rotate = function(_) {
+ if (!arguments.length) return [δλ * d3_degrees, δφ * d3_degrees, δγ * d3_degrees];
+ δλ = _[0] % 360 * d3_radians;
+ δφ = _[1] % 360 * d3_radians;
+ δγ = _.length > 2 ? _[2] % 360 * d3_radians : 0;
+ return reset();
+ };
+
+ d3.rebind(projection, projectResample, "precision");
+
+ function reset() {
+ projectRotate = d3_geo_compose(rotate = d3_geo_rotation(δλ, δφ, δγ), project);
+ var center = project(λ, φ);
+ δx = x - center[0] * k;
+ δy = y + center[1] * k;
+ return projection;
+ }
+
+ return function() {
+ project = projectAt.apply(this, arguments);
+ projection.invert = project.invert && invert;
+ return reset();
+ };
+}
+
+function d3_geo_projectionRadiansRotate(rotate, stream) {
+ return {
+ point: function(x, y) {
+ y = rotate(x * d3_radians, y * d3_radians), x = y[0];
+ stream.point(x > π ? x - 2 * π : x < -π ? x + 2 * π : x, y[1]);
+ },
+ sphere: function() { stream.sphere(); },
+ lineStart: function() { stream.lineStart(); },
+ lineEnd: function() { stream.lineEnd(); },
+ polygonStart: function() { stream.polygonStart(); },
+ polygonEnd: function() { stream.polygonEnd(); }
+ };
+}
+
+function d3_geo_mercator(λ, φ) {
+ return [λ, Math.log(Math.tan(π / 4 + φ / 2))];
+}
+
+d3_geo_mercator.invert = function(x, y) {
+ return [x, 2 * Math.atan(Math.exp(y)) - π / 2];
+};
+
+function d3_geo_mercatorProjection(project) {
+ var m = d3_geo_projection(project),
+ scale = m.scale,
+ translate = m.translate,
+ clipExtent = m.clipExtent,
+ clipAuto;
+
+ m.scale = function() {
+ var v = scale.apply(m, arguments);
+ return v === m ? (clipAuto ? m.clipExtent(null) : m) : v;
+ };
+
+ m.translate = function() {
+ var v = translate.apply(m, arguments);
+ return v === m ? (clipAuto ? m.clipExtent(null) : m) : v;
+ };
+
+ m.clipExtent = function(_) {
+ var v = clipExtent.apply(m, arguments);
+ if (v === m) {
+ if (clipAuto = _ == null) {
+ var k = π * scale(), t = translate();
+ clipExtent([[t[0] - k, t[1] - k], [t[0] + k, t[1] + k]]);
+ }
+ } else if (clipAuto) {
+ v = null;
+ }
+ return v;
+ };
+
+ return m.clipExtent(null);
+}
+
+(d3.geo.mercator = function() {
+ return d3_geo_mercatorProjection(d3_geo_mercator);
+}).raw = d3_geo_mercator;
+
+function d3_geo_conic(projectAt) {
+ var φ0 = 0,
+ φ1 = π / 3,
+ m = d3_geo_projectionMutator(projectAt),
+ p = m(φ0, φ1);
+
+ p.parallels = function(_) {
+ if (!arguments.length) return [φ0 / π * 180, φ1 / π * 180];
+ return m(φ0 = _[0] * π / 180, φ1 = _[1] * π / 180);
+ };
+
+ return p;
+}
+
+function d3_geo_conicEqualArea(φ0, φ1) {
+ var sinφ0 = Math.sin(φ0),
+ n = (sinφ0 + Math.sin(φ1)) / 2,
+ C = 1 + sinφ0 * (2 * n - sinφ0),
+ ρ0 = Math.sqrt(C) / n;
+
+ function forward(λ, φ) {
+ var ρ = Math.sqrt(C - 2 * n * Math.sin(φ)) / n;
+ return [
+ ρ * Math.sin(λ *= n),
+ ρ0 - ρ * Math.cos(λ)
+ ];
+ }
+
+ forward.invert = function(x, y) {
+ var ρ0_y = ρ0 - y;
+ return [
+ Math.atan2(x, ρ0_y) / n,
+ Math.asin((C - (x * x + ρ0_y * ρ0_y) * n * n) / (2 * n))
+ ];
+ };
+
+ return forward;
+}
+
+(d3.geo.conicEqualArea = function() {
+ return d3_geo_conic(d3_geo_conicEqualArea);
+}).raw = d3_geo_conicEqualArea;
+
+// A composite projection for the United States, 960×500. The set of standard
+// parallels for each region comes from USGS, which is published here:
+// http://egsc.usgs.gov/isb/pubs/MapProjections/projections.html#albers
+d3.geo.albersUsa = function() {
+ var lower48 = d3.geo.conicEqualArea()
+ .rotate([98, 0])
+ .center([0, 38])
+ .parallels([29.5, 45.5]);
+
+ var alaska = d3.geo.conicEqualArea()
+ .rotate([160, 0])
+ .center([0, 60])
+ .parallels([55, 65]);
+
+ var hawaii = d3.geo.conicEqualArea()
+ .rotate([160, 0])
+ .center([0, 20])
+ .parallels([8, 18]);
+
+ var puertoRico = d3.geo.conicEqualArea()
+ .rotate([60, 0])
+ .center([0, 10])
+ .parallels([8, 18]);
+
+ var alaskaInvert,
+ hawaiiInvert,
+ puertoRicoInvert;
+
+ function albersUsa(coordinates) {
+ return projection(coordinates)(coordinates);
+ }
+
+ function projection(point) {
+ var lon = point[0],
+ lat = point[1];
+ return lat > 50 ? alaska
+ : lon < -140 ? hawaii
+ : lat < 21 ? puertoRico
+ : lower48;
+ }
+
+ albersUsa.invert = function(coordinates) {
+ return alaskaInvert(coordinates) || hawaiiInvert(coordinates) || puertoRicoInvert(coordinates) || lower48.invert(coordinates);
+ };
+
+ albersUsa.scale = function(x) {
+ if (!arguments.length) return lower48.scale();
+ lower48.scale(x);
+ alaska.scale(x * .6);
+ hawaii.scale(x);
+ puertoRico.scale(x * 1.5);
+ return albersUsa.translate(lower48.translate());
+ };
+
+ albersUsa.translate = function(x) {
+ if (!arguments.length) return lower48.translate();
+ var dz = lower48.scale(),
+ dx = x[0],
+ dy = x[1];
+ lower48.translate(x);
+ alaska.translate([dx - .40 * dz, dy + .17 * dz]);
+ hawaii.translate([dx - .19 * dz, dy + .20 * dz]);
+ puertoRico.translate([dx + .58 * dz, dy + .43 * dz]);
+
+ alaskaInvert = d3_geo_albersUsaInvert(alaska, [[-180, 50], [-130, 72]]);
+ hawaiiInvert = d3_geo_albersUsaInvert(hawaii, [[-164, 18], [-154, 24]]);
+ puertoRicoInvert = d3_geo_albersUsaInvert(puertoRico, [[-67.5, 17.5], [-65, 19]]);
+
+ return albersUsa;
+ };
+
+ return albersUsa.scale(1000);
+};
+
+function d3_geo_albersUsaInvert(projection, extent) {
+ var a = projection(extent[0]),
+ b = projection([.5 * (extent[0][0] + extent[1][0]), extent[0][1]]),
+ c = projection([extent[1][0], extent[0][1]]),
+ d = projection(extent[1]);
+
+ var dya = b[1]- a[1],
+ dxa = b[0]- a[0],
+ dyb = c[1]- b[1],
+ dxb = c[0]- b[0];
+
+ var ma = dya / dxa,
+ mb = dyb / dxb;
+
+ // Find center of circle going through points [a, b, c].
+ var cx = .5 * (ma * mb * (a[1] - c[1]) + mb * (a[0] + b[0]) - ma * (b[0] + c[0])) / (mb - ma),
+ cy = (.5 * (a[0] + b[0]) - cx) / ma + .5 * (a[1] + b[1]);
+
+ // Radial distance² from center.
+ var dx0 = d[0] - cx,
+ dy0 = d[1] - cy,
+ dx1 = a[0] - cx,
+ dy1 = a[1] - cy,
+ r0 = dx0 * dx0 + dy0 * dy0,
+ r1 = dx1 * dx1 + dy1 * dy1;
+
+ // Angular extent.
+ var a0 = Math.atan2(dy0, dx0),
+ a1 = Math.atan2(dy1, dx1);
+
+ return function(coordinates) {
+ var dx = coordinates[0] - cx,
+ dy = coordinates[1] - cy,
+ r = dx * dx + dy * dy,
+ a = Math.atan2(dy, dx);
+ if (r0 < r && r < r1 && a0 < a && a < a1) return projection.invert(coordinates);
+ };
+}
+
+d3.geo.area = function(object) {
+ d3_geo_areaSum = 0;
+ d3.geo.stream(object, d3_geo_area);
+ return d3_geo_areaSum;
+};
+
+var d3_geo_areaSum,
+ d3_geo_areaRingU,
+ d3_geo_areaRingV;
+
+var d3_geo_area = {
+ sphere: function() { d3_geo_areaSum += 4 * π; },
+ point: d3_noop,
+ lineStart: d3_noop,
+ lineEnd: d3_noop,
+
+ // Only count area for polygon rings.
+ polygonStart: function() {
+ d3_geo_areaRingU = 1, d3_geo_areaRingV = 0;
+ d3_geo_area.lineStart = d3_geo_areaRingStart;
+ },
+ polygonEnd: function() {
+ var area = 2 * Math.atan2(d3_geo_areaRingV, d3_geo_areaRingU);
+ d3_geo_areaSum += area < 0 ? 4 * π + area : area;
+ d3_geo_area.lineStart = d3_geo_area.lineEnd = d3_geo_area.point = d3_noop;
+ }
+};
+
+function d3_geo_areaRingStart() {
+ var λ00, φ00, λ0, cosφ0, sinφ0; // start point and two previous points
+
+ // For the first point, …
+ d3_geo_area.point = function(λ, φ) {
+ d3_geo_area.point = nextPoint;
+ λ0 = (λ00 = λ) * d3_radians, cosφ0 = Math.cos(φ = (φ00 = φ) * d3_radians / 2 + π / 4), sinφ0 = Math.sin(φ);
+ };
+
+ // For subsequent points, …
+ function nextPoint(λ, φ) {
+ λ *= d3_radians;
+ φ = φ * d3_radians / 2 + π / 4; // half the angular distance from south pole
+
+ // Spherical excess E for a spherical triangle with vertices: south pole,
+ // previous point, current point. Uses a formula derived from Cagnoli’s
+ // theorem. See Todhunter, Spherical Trig. (1871), Sec. 103, Eq. (2).
+ var dλ = λ - λ0,
+ cosφ = Math.cos(φ),
+ sinφ = Math.sin(φ),
+ k = sinφ0 * sinφ,
+ u0 = d3_geo_areaRingU,
+ v0 = d3_geo_areaRingV,
+ u = cosφ0 * cosφ + k * Math.cos(dλ),
+ v = k * Math.sin(dλ);
+ // ∑ arg(z) = arg(∏ z), where z = u + iv.
+ d3_geo_areaRingU = u0 * u - v0 * v;
+ d3_geo_areaRingV = v0 * u + u0 * v;
+
+ // Advance the previous points.
+ λ0 = λ, cosφ0 = cosφ, sinφ0 = sinφ;
+ }
+
+ // For the last point, return to the start.
+ d3_geo_area.lineEnd = function() {
+ nextPoint(λ00, φ00);
+ };
+}
+
+d3.geo.bounds = d3_geo_bounds(d3_identity);
+
+function d3_geo_bounds(projectStream) {
+ var x0, y0, x1, y1;
+
+ var bound = {
+ point: boundPoint,
+ lineStart: d3_noop,
+ lineEnd: d3_noop,
+
+ // While inside a polygon, ignore points in holes.
+ polygonStart: function() { bound.lineEnd = boundPolygonLineEnd; },
+ polygonEnd: function() { bound.point = boundPoint; }
+ };
+
+ function boundPoint(x, y) {
+ if (x < x0) x0 = x;
+ if (x > x1) x1 = x;
+ if (y < y0) y0 = y;
+ if (y > y1) y1 = y;
+ }
+
+ function boundPolygonLineEnd() {
+ bound.point = bound.lineEnd = d3_noop;
+ }
+
+ return function(feature) {
+ y1 = x1 = -(x0 = y0 = Infinity);
+ d3.geo.stream(feature, projectStream(bound));
+ return [[x0, y0], [x1, y1]];
+ };
+}
+
+d3.geo.centroid = function(object) {
+ d3_geo_centroidDimension = d3_geo_centroidW = d3_geo_centroidX = d3_geo_centroidY = d3_geo_centroidZ = 0;
+ d3.geo.stream(object, d3_geo_centroid);
+ var m;
+ if (d3_geo_centroidW &&
+ Math.abs(m = Math.sqrt(d3_geo_centroidX * d3_geo_centroidX + d3_geo_centroidY * d3_geo_centroidY + d3_geo_centroidZ * d3_geo_centroidZ)) > ε) {
+ return [
+ Math.atan2(d3_geo_centroidY, d3_geo_centroidX) * d3_degrees,
+ Math.asin(Math.max(-1, Math.min(1, d3_geo_centroidZ / m))) * d3_degrees
+ ];
+ }
+};
+
+var d3_geo_centroidDimension,
+ d3_geo_centroidW,
+ d3_geo_centroidX,
+ d3_geo_centroidY,
+ d3_geo_centroidZ;
+
+var d3_geo_centroid = {
+ sphere: function() {
+ if (d3_geo_centroidDimension < 2) {
+ d3_geo_centroidDimension = 2;
+ d3_geo_centroidW = d3_geo_centroidX = d3_geo_centroidY = d3_geo_centroidZ = 0;
+ }
+ },
+ point: d3_geo_centroidPoint,
+ lineStart: d3_geo_centroidLineStart,
+ lineEnd: d3_geo_centroidLineEnd,
+ polygonStart: function() {
+ if (d3_geo_centroidDimension < 2) {
+ d3_geo_centroidDimension = 2;
+ d3_geo_centroidW = d3_geo_centroidX = d3_geo_centroidY = d3_geo_centroidZ = 0;
+ }
+ d3_geo_centroid.lineStart = d3_geo_centroidRingStart;
+ },
+ polygonEnd: function() {
+ d3_geo_centroid.lineStart = d3_geo_centroidLineStart;
+ }
+};
+
+// Arithmetic mean of Cartesian vectors.
+function d3_geo_centroidPoint(λ, φ) {
+ if (d3_geo_centroidDimension) return;
+ ++d3_geo_centroidW;
+ λ *= d3_radians;
+ var cosφ = Math.cos(φ *= d3_radians);
+ d3_geo_centroidX += (cosφ * Math.cos(λ) - d3_geo_centroidX) / d3_geo_centroidW;
+ d3_geo_centroidY += (cosφ * Math.sin(λ) - d3_geo_centroidY) / d3_geo_centroidW;
+ d3_geo_centroidZ += (Math.sin(φ) - d3_geo_centroidZ) / d3_geo_centroidW;
+}
+
+function d3_geo_centroidRingStart() {
+ var λ00, φ00; // first point
+
+ d3_geo_centroidDimension = 1;
+ d3_geo_centroidLineStart();
+ d3_geo_centroidDimension = 2;
+
+ var linePoint = d3_geo_centroid.point;
+ d3_geo_centroid.point = function(λ, φ) {
+ linePoint(λ00 = λ, φ00 = φ);
+ };
+ d3_geo_centroid.lineEnd = function() {
+ d3_geo_centroid.point(λ00, φ00);
+ d3_geo_centroidLineEnd();
+ d3_geo_centroid.lineEnd = d3_geo_centroidLineEnd;
+ };
+}
+
+function d3_geo_centroidLineStart() {
+ var x0, y0, z0; // previous point
+
+ if (d3_geo_centroidDimension > 1) return;
+ if (d3_geo_centroidDimension < 1) {
+ d3_geo_centroidDimension = 1;
+ d3_geo_centroidW = d3_geo_centroidX = d3_geo_centroidY = d3_geo_centroidZ = 0;
+ }
+
+ d3_geo_centroid.point = function(λ, φ) {
+ λ *= d3_radians;
+ var cosφ = Math.cos(φ *= d3_radians);
+ x0 = cosφ * Math.cos(λ);
+ y0 = cosφ * Math.sin(λ);
+ z0 = Math.sin(φ);
+ d3_geo_centroid.point = nextPoint;
+ };
+
+ function nextPoint(λ, φ) {
+ λ *= d3_radians;
+ var cosφ = Math.cos(φ *= d3_radians),
+ x = cosφ * Math.cos(λ),
+ y = cosφ * Math.sin(λ),
+ z = Math.sin(φ),
+ w = Math.atan2(
+ Math.sqrt((w = y0 * z - z0 * y) * w + (w = z0 * x - x0 * z) * w + (w = x0 * y - y0 * x) * w),
+ x0 * x + y0 * y + z0 * z);
+ d3_geo_centroidW += w;
+ d3_geo_centroidX += w * (x0 + (x0 = x));
+ d3_geo_centroidY += w * (y0 + (y0 = y));
+ d3_geo_centroidZ += w * (z0 + (z0 = z));
+ }
+}
+
+function d3_geo_centroidLineEnd() {
+ d3_geo_centroid.point = d3_geo_centroidPoint;
+}
+
+// TODO Unify this code with d3.geom.polygon area?
+
+var d3_geo_pathAreaSum, d3_geo_pathAreaPolygon, d3_geo_pathArea = {
+ point: d3_noop,
+ lineStart: d3_noop,
+ lineEnd: d3_noop,
+
+ // Only count area for polygon rings.
+ polygonStart: function() {
+ d3_geo_pathAreaPolygon = 0;
+ d3_geo_pathArea.lineStart = d3_geo_pathAreaRingStart;
+ },
+ polygonEnd: function() {
+ d3_geo_pathArea.lineStart = d3_geo_pathArea.lineEnd = d3_geo_pathArea.point = d3_noop;
+ d3_geo_pathAreaSum += Math.abs(d3_geo_pathAreaPolygon / 2);
+ }
+};
+
+function d3_geo_pathAreaRingStart() {
+ var x00, y00, x0, y0;
+
+ // For the first point, …
+ d3_geo_pathArea.point = function(x, y) {
+ d3_geo_pathArea.point = nextPoint;
+ x00 = x0 = x, y00 = y0 = y;
+ };
+
+ // For subsequent points, …
+ function nextPoint(x, y) {
+ d3_geo_pathAreaPolygon += y0 * x - x0 * y;
+ x0 = x, y0 = y;
+ }
+
+ // For the last point, return to the start.
+ d3_geo_pathArea.lineEnd = function() {
+ nextPoint(x00, y00);
+ };
+}
+function d3_geo_pathBuffer() {
+ var pointCircle = d3_geo_pathCircle(4.5),
+ buffer = [];
+
+ var stream = {
+ point: point,
+
+ // While inside a line, override point to moveTo then lineTo.
+ lineStart: function() { stream.point = pointLineStart; },
+ lineEnd: lineEnd,
+
+ // While inside a polygon, override lineEnd to closePath.
+ polygonStart: function() { stream.lineEnd = lineEndPolygon; },
+ polygonEnd: function() { stream.lineEnd = lineEnd; stream.point = point; },
+
+ pointRadius: function(_) {
+ pointCircle = d3_geo_pathCircle(_);
+ return stream;
+ },
+
+ result: function() {
+ if (buffer.length) {
+ var result = buffer.join("");
+ buffer = [];
+ return result;
+ }
+ }
+ };
+
+ function point(x, y) {
+ buffer.push("M", x, ",", y, pointCircle);
+ }
+
+ function pointLineStart(x, y) {
+ buffer.push("M", x, ",", y);
+ stream.point = pointLine;
+ }
+
+ function pointLine(x, y) {
+ buffer.push("L", x, ",", y);
+ }
+
+ function lineEnd() {
+ stream.point = point;
+ }
+
+ function lineEndPolygon() {
+ buffer.push("Z");
+ }
+
+ return stream;
+}
+
+// TODO Unify this code with d3.geom.polygon centroid?
+// TODO Enforce positive area for exterior, negative area for interior?
+
+var d3_geo_pathCentroid = {
+ point: d3_geo_pathCentroidPoint,
+
+ // For lines, weight by length.
+ lineStart: d3_geo_pathCentroidLineStart,
+ lineEnd: d3_geo_pathCentroidLineEnd,
+
+ // For polygons, weight by area.
+ polygonStart: function() {
+ d3_geo_pathCentroid.lineStart = d3_geo_pathCentroidRingStart;
+ },
+ polygonEnd: function() {
+ d3_geo_pathCentroid.point = d3_geo_pathCentroidPoint;
+ d3_geo_pathCentroid.lineStart = d3_geo_pathCentroidLineStart;
+ d3_geo_pathCentroid.lineEnd = d3_geo_pathCentroidLineEnd;
+ }
+};
+
+function d3_geo_pathCentroidPoint(x, y) {
+ if (d3_geo_centroidDimension) return;
+ d3_geo_centroidX += x;
+ d3_geo_centroidY += y;
+ ++d3_geo_centroidZ;
+}
+
+function d3_geo_pathCentroidLineStart() {
+ var x0, y0;
+
+ if (d3_geo_centroidDimension !== 1) {
+ if (d3_geo_centroidDimension < 1) {
+ d3_geo_centroidDimension = 1;
+ d3_geo_centroidX = d3_geo_centroidY = d3_geo_centroidZ = 0;
+ } else return;
+ }
+
+ d3_geo_pathCentroid.point = function(x, y) {
+ d3_geo_pathCentroid.point = nextPoint;
+ x0 = x, y0 = y;
+ };
+
+ function nextPoint(x, y) {
+ var dx = x - x0, dy = y - y0, z = Math.sqrt(dx * dx + dy * dy);
+ d3_geo_centroidX += z * (x0 + x) / 2;
+ d3_geo_centroidY += z * (y0 + y) / 2;
+ d3_geo_centroidZ += z;
+ x0 = x, y0 = y;
+ }
+}
+
+function d3_geo_pathCentroidLineEnd() {
+ d3_geo_pathCentroid.point = d3_geo_pathCentroidPoint;
+}
+
+function d3_geo_pathCentroidRingStart() {
+ var x00, y00, x0, y0;
+
+ if (d3_geo_centroidDimension < 2) {
+ d3_geo_centroidDimension = 2;
+ d3_geo_centroidX = d3_geo_centroidY = d3_geo_centroidZ = 0;
+ }
+
+ // For the first point, …
+ d3_geo_pathCentroid.point = function(x, y) {
+ d3_geo_pathCentroid.point = nextPoint;
+ x00 = x0 = x, y00 = y0 = y;
+ };
+
+ // For subsequent points, …
+ function nextPoint(x, y) {
+ var z = y0 * x - x0 * y;
+ d3_geo_centroidX += z * (x0 + x);
+ d3_geo_centroidY += z * (y0 + y);
+ d3_geo_centroidZ += z * 3;
+ x0 = x, y0 = y;
+ }
+
+ // For the last point, return to the start.
+ d3_geo_pathCentroid.lineEnd = function() {
+ nextPoint(x00, y00);
+ };
+}
+
+function d3_geo_pathContext(context) {
+ var pointRadius = 4.5;
+
+ var stream = {
+ point: point,
+
+ // While inside a line, override point to moveTo then lineTo.
+ lineStart: function() { stream.point = pointLineStart; },
+ lineEnd: lineEnd,
+
+ // While inside a polygon, override lineEnd to closePath.
+ polygonStart: function() { stream.lineEnd = lineEndPolygon; },
+ polygonEnd: function() { stream.lineEnd = lineEnd; stream.point = point; },
+
+ pointRadius: function(_) {
+ pointRadius = _;
+ return stream;
+ },
+
+ result: d3_noop
+ };
+
+ function point(x, y) {
+ context.moveTo(x, y);
+ context.arc(x, y, pointRadius, 0, 2 * π);
+ }
+
+ function pointLineStart(x, y) {
+ context.moveTo(x, y);
+ stream.point = pointLine;
+ }
+
+ function pointLine(x, y) {
+ context.lineTo(x, y);
+ }
+
+ function lineEnd() {
+ stream.point = point;
+ }
+
+ function lineEndPolygon() {
+ context.closePath();
+ }
+
+ return stream;
+}
+
+d3.geo.path = function() {
+ var pointRadius = 4.5,
+ projection,
+ context,
+ projectStream,
+ contextStream;
+
+ function path(object) {
+ if (object) d3.geo.stream(object, projectStream(
+ contextStream.pointRadius(typeof pointRadius === "function"
+ ? +pointRadius.apply(this, arguments)
+ : pointRadius)));
+ return contextStream.result();
+ }
+
+ path.area = function(object) {
+ d3_geo_pathAreaSum = 0;
+ d3.geo.stream(object, projectStream(d3_geo_pathArea));
+ return d3_geo_pathAreaSum;
+ };
+
+ path.centroid = function(object) {
+ d3_geo_centroidDimension = d3_geo_centroidX = d3_geo_centroidY = d3_geo_centroidZ = 0;
+ d3.geo.stream(object, projectStream(d3_geo_pathCentroid));
+ return d3_geo_centroidZ ? [d3_geo_centroidX / d3_geo_centroidZ, d3_geo_centroidY / d3_geo_centroidZ] : undefined;
+ };
+
+ path.bounds = function(object) {
+ return d3_geo_bounds(projectStream)(object);
+ };
+
+ path.projection = function(_) {
+ if (!arguments.length) return projection;
+ projectStream = (projection = _) ? _.stream || d3_geo_pathProjectStream(_) : d3_identity;
+ return path;
+ };
+
+ path.context = function(_) {
+ if (!arguments.length) return context;
+ contextStream = (context = _) == null ? new d3_geo_pathBuffer : new d3_geo_pathContext(_);
+ return path;
+ };
+
+ path.pointRadius = function(_) {
+ if (!arguments.length) return pointRadius;
+ pointRadius = typeof _ === "function" ? _ : +_;
+ return path;
+ };
+
+ return path.projection(d3.geo.albersUsa()).context(null);
+};
+
+function d3_geo_pathCircle(radius) {
+ return "m0," + radius
+ + "a" + radius + "," + radius + " 0 1,1 0," + (-2 * radius)
+ + "a" + radius + "," + radius + " 0 1,1 0," + (+2 * radius)
+ + "z";
+}
+
+function d3_geo_pathProjectStream(project) {
+ var resample = d3_geo_resample(function(λ, φ) { return project([λ * d3_degrees, φ * d3_degrees]); });
+ return function(stream) {
+ stream = resample(stream);
+ return {
+ point: function(λ, φ) { stream.point(λ * d3_radians, φ * d3_radians); },
+ sphere: function() { stream.sphere(); },
+ lineStart: function() { stream.lineStart(); },
+ lineEnd: function() { stream.lineEnd(); },
+ polygonStart: function() { stream.polygonStart(); },
+ polygonEnd: function() { stream.polygonEnd(); }
+ };
+ };
+}
+d3.geom = {};
+
+d3.geom.polygon = function(coordinates) {
+
+ coordinates.area = function() {
+ var i = 0,
+ n = coordinates.length,
+ area = coordinates[n - 1][1] * coordinates[0][0] - coordinates[n - 1][0] * coordinates[0][1];
+ while (++i < n) {
+ area += coordinates[i - 1][1] * coordinates[i][0] - coordinates[i - 1][0] * coordinates[i][1];
+ }
+ return area * .5;
+ };
+
+ coordinates.centroid = function(k) {
+ var i = -1,
+ n = coordinates.length,
+ x = 0,
+ y = 0,
+ a,
+ b = coordinates[n - 1],
+ c;
+ if (!arguments.length) k = -1 / (6 * coordinates.area());
+ while (++i < n) {
+ a = b;
+ b = coordinates[i];
+ c = a[0] * b[1] - b[0] * a[1];
+ x += (a[0] + b[0]) * c;
+ y += (a[1] + b[1]) * c;
+ }
+ return [x * k, y * k];
+ };
+
+ // The Sutherland-Hodgman clipping algorithm.
+ // Note: requires the clip polygon to be counterclockwise and convex.
+ coordinates.clip = function(subject) {
+ var input,
+ i = -1,
+ n = coordinates.length,
+ j,
+ m,
+ a = coordinates[n - 1],
+ b,
+ c,
+ d;
+ while (++i < n) {
+ input = subject.slice();
+ subject.length = 0;
+ b = coordinates[i];
+ c = input[(m = input.length) - 1];
+ j = -1;
+ while (++j < m) {
+ d = input[j];
+ if (d3_geom_polygonInside(d, a, b)) {
+ if (!d3_geom_polygonInside(c, a, b)) {
+ subject.push(d3_geom_polygonIntersect(c, d, a, b));
+ }
+ subject.push(d);
+ } else if (d3_geom_polygonInside(c, a, b)) {
+ subject.push(d3_geom_polygonIntersect(c, d, a, b));
+ }
+ c = d;
+ }
+ a = b;
+ }
+ return subject;
+ };
+
+ return coordinates;
+};
+
+function d3_geom_polygonInside(p, a, b) {
+ return (b[0] - a[0]) * (p[1] - a[1]) < (b[1] - a[1]) * (p[0] - a[0]);
+}
+
+// Intersect two infinite lines cd and ab.
+function d3_geom_polygonIntersect(c, d, a, b) {
+ var x1 = c[0], x3 = a[0], x21 = d[0] - x1, x43 = b[0] - x3,
+ y1 = c[1], y3 = a[1], y21 = d[1] - y1, y43 = b[1] - y3,
+ ua = (x43 * (y1 - y3) - y43 * (x1 - x3)) / (y43 * x21 - x43 * y21);
+ return [x1 + ua * x21, y1 + ua * y21];
+}
+
+var d3_ease_default = function() { return d3_identity; };
+
+var d3_ease = d3.map({
+ linear: d3_ease_default,
+ poly: d3_ease_poly,
+ quad: function() { return d3_ease_quad; },
+ cubic: function() { return d3_ease_cubic; },
+ sin: function() { return d3_ease_sin; },
+ exp: function() { return d3_ease_exp; },
+ circle: function() { return d3_ease_circle; },
+ elastic: d3_ease_elastic,
+ back: d3_ease_back,
+ bounce: function() { return d3_ease_bounce; }
+});
+
+var d3_ease_mode = d3.map({
+ "in": d3_identity,
+ "out": d3_ease_reverse,
+ "in-out": d3_ease_reflect,
+ "out-in": function(f) { return d3_ease_reflect(d3_ease_reverse(f)); }
+});
+
+d3.ease = function(name) {
+ var i = name.indexOf("-"),
+ t = i >= 0 ? name.substring(0, i) : name,
+ m = i >= 0 ? name.substring(i + 1) : "in";
+ t = d3_ease.get(t) || d3_ease_default;
+ m = d3_ease_mode.get(m) || d3_identity;
+ return d3_ease_clamp(m(t.apply(null, Array.prototype.slice.call(arguments, 1))));
+};
+
+function d3_ease_clamp(f) {
+ return function(t) {
+ return t <= 0 ? 0 : t >= 1 ? 1 : f(t);
+ };
+}
+
+function d3_ease_reverse(f) {
+ return function(t) {
+ return 1 - f(1 - t);
+ };
+}
+
+function d3_ease_reflect(f) {
+ return function(t) {
+ return .5 * (t < .5 ? f(2 * t) : (2 - f(2 - 2 * t)));
+ };
+}
+
+function d3_ease_quad(t) {
+ return t * t;
+}
+
+function d3_ease_cubic(t) {
+ return t * t * t;
+}
+
+// Optimized clamp(reflect(poly(3))).
+function d3_ease_cubicInOut(t) {
+ if (t <= 0) return 0;
+ if (t >= 1) return 1;
+ var t2 = t * t, t3 = t2 * t;
+ return 4 * (t < .5 ? t3 : 3 * (t - t2) + t3 - .75);
+}
+
+function d3_ease_poly(e) {
+ return function(t) {
+ return Math.pow(t, e);
+ };
+}
+
+function d3_ease_sin(t) {
+ return 1 - Math.cos(t * π / 2);
+}
+
+function d3_ease_exp(t) {
+ return Math.pow(2, 10 * (t - 1));
+}
+
+function d3_ease_circle(t) {
+ return 1 - Math.sqrt(1 - t * t);
+}
+
+function d3_ease_elastic(a, p) {
+ var s;
+ if (arguments.length < 2) p = 0.45;
+ if (arguments.length) s = p / (2 * π) * Math.asin(1 / a);
+ else a = 1, s = p / 4;
+ return function(t) {
+ return 1 + a * Math.pow(2, 10 * -t) * Math.sin((t - s) * 2 * π / p);
+ };
+}
+
+function d3_ease_back(s) {
+ if (!s) s = 1.70158;
+ return function(t) {
+ return t * t * ((s + 1) * t - s);
+ };
+}
+
+function d3_ease_bounce(t) {
+ return t < 1 / 2.75 ? 7.5625 * t * t
+ : t < 2 / 2.75 ? 7.5625 * (t -= 1.5 / 2.75) * t + .75
+ : t < 2.5 / 2.75 ? 7.5625 * (t -= 2.25 / 2.75) * t + .9375
+ : 7.5625 * (t -= 2.625 / 2.75) * t + .984375;
+}
+
+function d3_transition(groups, id) {
+ d3_arraySubclass(groups, d3_transitionPrototype);
+
+ groups.id = id; // Note: read-only!
+
+ return groups;
+}
+
+var d3_transitionPrototype = [],
+ d3_transitionId = 0,
+ d3_transitionInheritId,
+ d3_transitionInherit = {ease: d3_ease_cubicInOut, delay: 0, duration: 250};
+
+d3_transitionPrototype.call = d3_selectionPrototype.call;
+d3_transitionPrototype.empty = d3_selectionPrototype.empty;
+d3_transitionPrototype.node = d3_selectionPrototype.node;
+
+d3.transition = function(selection) {
+ return arguments.length
+ ? (d3_transitionInheritId ? selection.transition() : selection)
+ : d3_selectionRoot.transition();
+};
+
+d3.transition.prototype = d3_transitionPrototype;
+
+
+d3_transitionPrototype.select = function(selector) {
+ var id = this.id,
+ subgroups = [],
+ subgroup,
+ subnode,
+ node;
+
+ if (typeof selector !== "function") selector = d3_selection_selector(selector);
+
+ for (var j = -1, m = this.length; ++j < m;) {
+ subgroups.push(subgroup = []);
+ for (var group = this[j], i = -1, n = group.length; ++i < n;) {
+ if ((node = group[i]) && (subnode = selector.call(node, node.__data__, i))) {
+ if ("__data__" in node) subnode.__data__ = node.__data__;
+ d3_transitionNode(subnode, i, id, node.__transition__[id]);
+ subgroup.push(subnode);
+ } else {
+ subgroup.push(null);
+ }
+ }
+ }
+
+ return d3_transition(subgroups, id);
+};
+
+d3_transitionPrototype.selectAll = function(selector) {
+ var id = this.id,
+ subgroups = [],
+ subgroup,
+ subnodes,
+ node,
+ subnode,
+ transition;
+
+ if (typeof selector !== "function") selector = d3_selection_selectorAll(selector);
+
+ for (var j = -1, m = this.length; ++j < m;) {
+ for (var group = this[j], i = -1, n = group.length; ++i < n;) {
+ if (node = group[i]) {
+ transition = node.__transition__[id];
+ subnodes = selector.call(node, node.__data__, i);
+ subgroups.push(subgroup = []);
+ for (var k = -1, o = subnodes.length; ++k < o;) {
+ d3_transitionNode(subnode = subnodes[k], k, id, transition);
+ subgroup.push(subnode);
+ }
+ }
+ }
+ }
+
+ return d3_transition(subgroups, id);
+};
+
+d3_transitionPrototype.filter = function(filter) {
+ var subgroups = [],
+ subgroup,
+ group,
+ node;
+
+ if (typeof filter !== "function") filter = d3_selection_filter(filter);
+
+ for (var j = 0, m = this.length; j < m; j++) {
+ subgroups.push(subgroup = []);
+ for (var group = this[j], i = 0, n = group.length; i < n; i++) {
+ if ((node = group[i]) && filter.call(node, node.__data__, i)) {
+ subgroup.push(node);
+ }
+ }
+ }
+
+ return d3_transition(subgroups, this.id, this.time).ease(this.ease());
+};
+function d3_Color() {}
+
+d3_Color.prototype.toString = function() {
+ return this.rgb() + "";
+};
+
+d3.hsl = function(h, s, l) {
+ return arguments.length === 1
+ ? (h instanceof d3_Hsl ? d3_hsl(h.h, h.s, h.l)
+ : d3_rgb_parse("" + h, d3_rgb_hsl, d3_hsl))
+ : d3_hsl(+h, +s, +l);
+};
+
+function d3_hsl(h, s, l) {
+ return new d3_Hsl(h, s, l);
+}
+
+function d3_Hsl(h, s, l) {
+ this.h = h;
+ this.s = s;
+ this.l = l;
+}
+
+var d3_hslPrototype = d3_Hsl.prototype = new d3_Color;
+
+d3_hslPrototype.brighter = function(k) {
+ k = Math.pow(0.7, arguments.length ? k : 1);
+ return d3_hsl(this.h, this.s, this.l / k);
+};
+
+d3_hslPrototype.darker = function(k) {
+ k = Math.pow(0.7, arguments.length ? k : 1);
+ return d3_hsl(this.h, this.s, k * this.l);
+};
+
+d3_hslPrototype.rgb = function() {
+ return d3_hsl_rgb(this.h, this.s, this.l);
+};
+
+function d3_hsl_rgb(h, s, l) {
+ var m1,
+ m2;
+
+ /* Some simple corrections for h, s and l. */
+ h = h % 360; if (h < 0) h += 360;
+ s = s < 0 ? 0 : s > 1 ? 1 : s;
+ l = l < 0 ? 0 : l > 1 ? 1 : l;
+
+ /* From FvD 13.37, CSS Color Module Level 3 */
+ m2 = l <= .5 ? l * (1 + s) : l + s - l * s;
+ m1 = 2 * l - m2;
+
+ function v(h) {
+ if (h > 360) h -= 360;
+ else if (h < 0) h += 360;
+ if (h < 60) return m1 + (m2 - m1) * h / 60;
+ if (h < 180) return m2;
+ if (h < 240) return m1 + (m2 - m1) * (240 - h) / 60;
+ return m1;
+ }
+
+ function vv(h) {
+ return Math.round(v(h) * 255);
+ }
+
+ return d3_rgb(vv(h + 120), vv(h), vv(h - 120));
+}
+
+d3.hcl = function(h, c, l) {
+ return arguments.length === 1
+ ? (h instanceof d3_Hcl ? d3_hcl(h.h, h.c, h.l)
+ : (h instanceof d3_Lab ? d3_lab_hcl(h.l, h.a, h.b)
+ : d3_lab_hcl((h = d3_rgb_lab((h = d3.rgb(h)).r, h.g, h.b)).l, h.a, h.b)))
+ : d3_hcl(+h, +c, +l);
+};
+
+function d3_hcl(h, c, l) {
+ return new d3_Hcl(h, c, l);
+}
+
+function d3_Hcl(h, c, l) {
+ this.h = h;
+ this.c = c;
+ this.l = l;
+}
+
+var d3_hclPrototype = d3_Hcl.prototype = new d3_Color;
+
+d3_hclPrototype.brighter = function(k) {
+ return d3_hcl(this.h, this.c, Math.min(100, this.l + d3_lab_K * (arguments.length ? k : 1)));
+};
+
+d3_hclPrototype.darker = function(k) {
+ return d3_hcl(this.h, this.c, Math.max(0, this.l - d3_lab_K * (arguments.length ? k : 1)));
+};
+
+d3_hclPrototype.rgb = function() {
+ return d3_hcl_lab(this.h, this.c, this.l).rgb();
+};
+
+function d3_hcl_lab(h, c, l) {
+ return d3_lab(l, Math.cos(h *= d3_radians) * c, Math.sin(h) * c);
+}
+
+d3.lab = function(l, a, b) {
+ return arguments.length === 1
+ ? (l instanceof d3_Lab ? d3_lab(l.l, l.a, l.b)
+ : (l instanceof d3_Hcl ? d3_hcl_lab(l.l, l.c, l.h)
+ : d3_rgb_lab((l = d3.rgb(l)).r, l.g, l.b)))
+ : d3_lab(+l, +a, +b);
+};
+
+function d3_lab(l, a, b) {
+ return new d3_Lab(l, a, b);
+}
+
+function d3_Lab(l, a, b) {
+ this.l = l;
+ this.a = a;
+ this.b = b;
+}
+
+// Corresponds roughly to RGB brighter/darker
+var d3_lab_K = 18;
+
+// D65 standard referent
+var d3_lab_X = 0.950470,
+ d3_lab_Y = 1,
+ d3_lab_Z = 1.088830;
+
+var d3_labPrototype = d3_Lab.prototype = new d3_Color;
+
+d3_labPrototype.brighter = function(k) {
+ return d3_lab(Math.min(100, this.l + d3_lab_K * (arguments.length ? k : 1)), this.a, this.b);
+};
+
+d3_labPrototype.darker = function(k) {
+ return d3_lab(Math.max(0, this.l - d3_lab_K * (arguments.length ? k : 1)), this.a, this.b);
+};
+
+d3_labPrototype.rgb = function() {
+ return d3_lab_rgb(this.l, this.a, this.b);
+};
+
+function d3_lab_rgb(l, a, b) {
+ var y = (l + 16) / 116,
+ x = y + a / 500,
+ z = y - b / 200;
+ x = d3_lab_xyz(x) * d3_lab_X;
+ y = d3_lab_xyz(y) * d3_lab_Y;
+ z = d3_lab_xyz(z) * d3_lab_Z;
+ return d3_rgb(
+ d3_xyz_rgb( 3.2404542 * x - 1.5371385 * y - 0.4985314 * z),
+ d3_xyz_rgb(-0.9692660 * x + 1.8760108 * y + 0.0415560 * z),
+ d3_xyz_rgb( 0.0556434 * x - 0.2040259 * y + 1.0572252 * z)
+ );
+}
+
+function d3_lab_hcl(l, a, b) {
+ return d3_hcl(Math.atan2(b, a) * d3_degrees, Math.sqrt(a * a + b * b), l);
+}
+
+function d3_lab_xyz(x) {
+ return x > 0.206893034 ? x * x * x : (x - 4 / 29) / 7.787037;
+}
+function d3_xyz_lab(x) {
+ return x > 0.008856 ? Math.pow(x, 1 / 3) : 7.787037 * x + 4 / 29;
+}
+
+function d3_xyz_rgb(r) {
+ return Math.round(255 * (r <= 0.00304 ? 12.92 * r : 1.055 * Math.pow(r, 1 / 2.4) - 0.055));
+}
+
+d3.rgb = function(r, g, b) {
+ return arguments.length === 1
+ ? (r instanceof d3_Rgb ? d3_rgb(r.r, r.g, r.b)
+ : d3_rgb_parse("" + r, d3_rgb, d3_hsl_rgb))
+ : d3_rgb(~~r, ~~g, ~~b);
+};
+
+function d3_rgb(r, g, b) {
+ return new d3_Rgb(r, g, b);
+}
+
+function d3_Rgb(r, g, b) {
+ this.r = r;
+ this.g = g;
+ this.b = b;
+}
+
+var d3_rgbPrototype = d3_Rgb.prototype = new d3_Color;
+
+d3_rgbPrototype.brighter = function(k) {
+ k = Math.pow(0.7, arguments.length ? k : 1);
+ var r = this.r,
+ g = this.g,
+ b = this.b,
+ i = 30;
+ if (!r && !g && !b) return d3_rgb(i, i, i);
+ if (r && r < i) r = i;
+ if (g && g < i) g = i;
+ if (b && b < i) b = i;
+ return d3_rgb(
+ Math.min(255, Math.floor(r / k)),
+ Math.min(255, Math.floor(g / k)),
+ Math.min(255, Math.floor(b / k)));
+};
+
+d3_rgbPrototype.darker = function(k) {
+ k = Math.pow(0.7, arguments.length ? k : 1);
+ return d3_rgb(
+ Math.floor(k * this.r),
+ Math.floor(k * this.g),
+ Math.floor(k * this.b));
+};
+
+d3_rgbPrototype.hsl = function() {
+ return d3_rgb_hsl(this.r, this.g, this.b);
+};
+
+d3_rgbPrototype.toString = function() {
+ return "#" + d3_rgb_hex(this.r) + d3_rgb_hex(this.g) + d3_rgb_hex(this.b);
+};
+
+function d3_rgb_hex(v) {
+ return v < 0x10
+ ? "0" + Math.max(0, v).toString(16)
+ : Math.min(255, v).toString(16);
+}
+
+function d3_rgb_parse(format, rgb, hsl) {
+ var r = 0, // red channel; int in [0, 255]
+ g = 0, // green channel; int in [0, 255]
+ b = 0, // blue channel; int in [0, 255]
+ m1, // CSS color specification match
+ m2, // CSS color specification type (e.g., rgb)
+ name;
+
+ /* Handle hsl, rgb. */
+ m1 = /([a-z]+)\((.*)\)/i.exec(format);
+ if (m1) {
+ m2 = m1[2].split(",");
+ switch (m1[1]) {
+ case "hsl": {
+ return hsl(
+ parseFloat(m2[0]), // degrees
+ parseFloat(m2[1]) / 100, // percentage
+ parseFloat(m2[2]) / 100 // percentage
+ );
+ }
+ case "rgb": {
+ return rgb(
+ d3_rgb_parseNumber(m2[0]),
+ d3_rgb_parseNumber(m2[1]),
+ d3_rgb_parseNumber(m2[2])
+ );
+ }
+ }
+ }
+
+ /* Named colors. */
+ if (name = d3_rgb_names.get(format)) return rgb(name.r, name.g, name.b);
+
+ /* Hexadecimal colors: #rgb and #rrggbb. */
+ if (format != null && format.charAt(0) === "#") {
+ if (format.length === 4) {
+ r = format.charAt(1); r += r;
+ g = format.charAt(2); g += g;
+ b = format.charAt(3); b += b;
+ } else if (format.length === 7) {
+ r = format.substring(1, 3);
+ g = format.substring(3, 5);
+ b = format.substring(5, 7);
+ }
+ r = parseInt(r, 16);
+ g = parseInt(g, 16);
+ b = parseInt(b, 16);
+ }
+
+ return rgb(r, g, b);
+}
+
+function d3_rgb_hsl(r, g, b) {
+ var min = Math.min(r /= 255, g /= 255, b /= 255),
+ max = Math.max(r, g, b),
+ d = max - min,
+ h,
+ s,
+ l = (max + min) / 2;
+ if (d) {
+ s = l < .5 ? d / (max + min) : d / (2 - max - min);
+ if (r == max) h = (g - b) / d + (g < b ? 6 : 0);
+ else if (g == max) h = (b - r) / d + 2;
+ else h = (r - g) / d + 4;
+ h *= 60;
+ } else {
+ s = h = 0;
+ }
+ return d3_hsl(h, s, l);
+}
+
+function d3_rgb_lab(r, g, b) {
+ r = d3_rgb_xyz(r);
+ g = d3_rgb_xyz(g);
+ b = d3_rgb_xyz(b);
+ var x = d3_xyz_lab((0.4124564 * r + 0.3575761 * g + 0.1804375 * b) / d3_lab_X),
+ y = d3_xyz_lab((0.2126729 * r + 0.7151522 * g + 0.0721750 * b) / d3_lab_Y),
+ z = d3_xyz_lab((0.0193339 * r + 0.1191920 * g + 0.9503041 * b) / d3_lab_Z);
+ return d3_lab(116 * y - 16, 500 * (x - y), 200 * (y - z));
+}
+
+function d3_rgb_xyz(r) {
+ return (r /= 255) <= 0.04045 ? r / 12.92 : Math.pow((r + 0.055) / 1.055, 2.4);
+}
+
+function d3_rgb_parseNumber(c) { // either integer or percentage
+ var f = parseFloat(c);
+ return c.charAt(c.length - 1) === "%" ? Math.round(f * 2.55) : f;
+}
+
+var d3_rgb_names = d3.map({
+ aliceblue: "#f0f8ff",
+ antiquewhite: "#faebd7",
+ aqua: "#00ffff",
+ aquamarine: "#7fffd4",
+ azure: "#f0ffff",
+ beige: "#f5f5dc",
+ bisque: "#ffe4c4",
+ black: "#000000",
+ blanchedalmond: "#ffebcd",
+ blue: "#0000ff",
+ blueviolet: "#8a2be2",
+ brown: "#a52a2a",
+ burlywood: "#deb887",
+ cadetblue: "#5f9ea0",
+ chartreuse: "#7fff00",
+ chocolate: "#d2691e",
+ coral: "#ff7f50",
+ cornflowerblue: "#6495ed",
+ cornsilk: "#fff8dc",
+ crimson: "#dc143c",
+ cyan: "#00ffff",
+ darkblue: "#00008b",
+ darkcyan: "#008b8b",
+ darkgoldenrod: "#b8860b",
+ darkgray: "#a9a9a9",
+ darkgreen: "#006400",
+ darkgrey: "#a9a9a9",
+ darkkhaki: "#bdb76b",
+ darkmagenta: "#8b008b",
+ darkolivegreen: "#556b2f",
+ darkorange: "#ff8c00",
+ darkorchid: "#9932cc",
+ darkred: "#8b0000",
+ darksalmon: "#e9967a",
+ darkseagreen: "#8fbc8f",
+ darkslateblue: "#483d8b",
+ darkslategray: "#2f4f4f",
+ darkslategrey: "#2f4f4f",
+ darkturquoise: "#00ced1",
+ darkviolet: "#9400d3",
+ deeppink: "#ff1493",
+ deepskyblue: "#00bfff",
+ dimgray: "#696969",
+ dimgrey: "#696969",
+ dodgerblue: "#1e90ff",
+ firebrick: "#b22222",
+ floralwhite: "#fffaf0",
+ forestgreen: "#228b22",
+ fuchsia: "#ff00ff",
+ gainsboro: "#dcdcdc",
+ ghostwhite: "#f8f8ff",
+ gold: "#ffd700",
+ goldenrod: "#daa520",
+ gray: "#808080",
+ green: "#008000",
+ greenyellow: "#adff2f",
+ grey: "#808080",
+ honeydew: "#f0fff0",
+ hotpink: "#ff69b4",
+ indianred: "#cd5c5c",
+ indigo: "#4b0082",
+ ivory: "#fffff0",
+ khaki: "#f0e68c",
+ lavender: "#e6e6fa",
+ lavenderblush: "#fff0f5",
+ lawngreen: "#7cfc00",
+ lemonchiffon: "#fffacd",
+ lightblue: "#add8e6",
+ lightcoral: "#f08080",
+ lightcyan: "#e0ffff",
+ lightgoldenrodyellow: "#fafad2",
+ lightgray: "#d3d3d3",
+ lightgreen: "#90ee90",
+ lightgrey: "#d3d3d3",
+ lightpink: "#ffb6c1",
+ lightsalmon: "#ffa07a",
+ lightseagreen: "#20b2aa",
+ lightskyblue: "#87cefa",
+ lightslategray: "#778899",
+ lightslategrey: "#778899",
+ lightsteelblue: "#b0c4de",
+ lightyellow: "#ffffe0",
+ lime: "#00ff00",
+ limegreen: "#32cd32",
+ linen: "#faf0e6",
+ magenta: "#ff00ff",
+ maroon: "#800000",
+ mediumaquamarine: "#66cdaa",
+ mediumblue: "#0000cd",
+ mediumorchid: "#ba55d3",
+ mediumpurple: "#9370db",
+ mediumseagreen: "#3cb371",
+ mediumslateblue: "#7b68ee",
+ mediumspringgreen: "#00fa9a",
+ mediumturquoise: "#48d1cc",
+ mediumvioletred: "#c71585",
+ midnightblue: "#191970",
+ mintcream: "#f5fffa",
+ mistyrose: "#ffe4e1",
+ moccasin: "#ffe4b5",
+ navajowhite: "#ffdead",
+ navy: "#000080",
+ oldlace: "#fdf5e6",
+ olive: "#808000",
+ olivedrab: "#6b8e23",
+ orange: "#ffa500",
+ orangered: "#ff4500",
+ orchid: "#da70d6",
+ palegoldenrod: "#eee8aa",
+ palegreen: "#98fb98",
+ paleturquoise: "#afeeee",
+ palevioletred: "#db7093",
+ papayawhip: "#ffefd5",
+ peachpuff: "#ffdab9",
+ peru: "#cd853f",
+ pink: "#ffc0cb",
+ plum: "#dda0dd",
+ powderblue: "#b0e0e6",
+ purple: "#800080",
+ red: "#ff0000",
+ rosybrown: "#bc8f8f",
+ royalblue: "#4169e1",
+ saddlebrown: "#8b4513",
+ salmon: "#fa8072",
+ sandybrown: "#f4a460",
+ seagreen: "#2e8b57",
+ seashell: "#fff5ee",
+ sienna: "#a0522d",
+ silver: "#c0c0c0",
+ skyblue: "#87ceeb",
+ slateblue: "#6a5acd",
+ slategray: "#708090",
+ slategrey: "#708090",
+ snow: "#fffafa",
+ springgreen: "#00ff7f",
+ steelblue: "#4682b4",
+ tan: "#d2b48c",
+ teal: "#008080",
+ thistle: "#d8bfd8",
+ tomato: "#ff6347",
+ turquoise: "#40e0d0",
+ violet: "#ee82ee",
+ wheat: "#f5deb3",
+ white: "#ffffff",
+ whitesmoke: "#f5f5f5",
+ yellow: "#ffff00",
+ yellowgreen: "#9acd32"
+});
+
+d3_rgb_names.forEach(function(key, value) {
+ d3_rgb_names.set(key, d3_rgb_parse(value, d3_rgb, d3_hsl_rgb));
+});
+
+d3.interpolateRgb = d3_interpolateRgb;
+
+function d3_interpolateRgb(a, b) {
+ a = d3.rgb(a);
+ b = d3.rgb(b);
+ var ar = a.r,
+ ag = a.g,
+ ab = a.b,
+ br = b.r - ar,
+ bg = b.g - ag,
+ bb = b.b - ab;
+ return function(t) {
+ return "#"
+ + d3_rgb_hex(Math.round(ar + br * t))
+ + d3_rgb_hex(Math.round(ag + bg * t))
+ + d3_rgb_hex(Math.round(ab + bb * t));
+ };
+}
+
+d3.transform = function(string) {
+ var g = d3_document.createElementNS(d3.ns.prefix.svg, "g");
+ return (d3.transform = function(string) {
+ g.setAttribute("transform", string);
+ var t = g.transform.baseVal.consolidate();
+ return new d3_transform(t ? t.matrix : d3_transformIdentity);
+ })(string);
+};
+
+// Compute x-scale and normalize the first row.
+// Compute shear and make second row orthogonal to first.
+// Compute y-scale and normalize the second row.
+// Finally, compute the rotation.
+function d3_transform(m) {
+ var r0 = [m.a, m.b],
+ r1 = [m.c, m.d],
+ kx = d3_transformNormalize(r0),
+ kz = d3_transformDot(r0, r1),
+ ky = d3_transformNormalize(d3_transformCombine(r1, r0, -kz)) || 0;
+ if (r0[0] * r1[1] < r1[0] * r0[1]) {
+ r0[0] *= -1;
+ r0[1] *= -1;
+ kx *= -1;
+ kz *= -1;
+ }
+ this.rotate = (kx ? Math.atan2(r0[1], r0[0]) : Math.atan2(-r1[0], r1[1])) * d3_degrees;
+ this.translate = [m.e, m.f];
+ this.scale = [kx, ky];
+ this.skew = ky ? Math.atan2(kz, ky) * d3_degrees : 0;
+};
+
+d3_transform.prototype.toString = function() {
+ return "translate(" + this.translate
+ + ")rotate(" + this.rotate
+ + ")skewX(" + this.skew
+ + ")scale(" + this.scale
+ + ")";
+};
+
+function d3_transformDot(a, b) {
+ return a[0] * b[0] + a[1] * b[1];
+}
+
+function d3_transformNormalize(a) {
+ var k = Math.sqrt(d3_transformDot(a, a));
+ if (k) {
+ a[0] /= k;
+ a[1] /= k;
+ }
+ return k;
+}
+
+function d3_transformCombine(a, b, k) {
+ a[0] += k * b[0];
+ a[1] += k * b[1];
+ return a;
+}
+
+var d3_transformIdentity = {a: 1, b: 0, c: 0, d: 1, e: 0, f: 0};
+d3.interpolateNumber = d3_interpolateNumber;
+
+function d3_interpolateNumber(a, b) {
+ b -= a;
+ return function(t) { return a + b * t; };
+}
+
+d3.interpolateTransform = d3_interpolateTransform;
+
+function d3_interpolateTransform(a, b) {
+ var s = [], // string constants and placeholders
+ q = [], // number interpolators
+ n,
+ A = d3.transform(a),
+ B = d3.transform(b),
+ ta = A.translate,
+ tb = B.translate,
+ ra = A.rotate,
+ rb = B.rotate,
+ wa = A.skew,
+ wb = B.skew,
+ ka = A.scale,
+ kb = B.scale;
+
+ if (ta[0] != tb[0] || ta[1] != tb[1]) {
+ s.push("translate(", null, ",", null, ")");
+ q.push({i: 1, x: d3_interpolateNumber(ta[0], tb[0])}, {i: 3, x: d3_interpolateNumber(ta[1], tb[1])});
+ } else if (tb[0] || tb[1]) {
+ s.push("translate(" + tb + ")");
+ } else {
+ s.push("");
+ }
+
+ if (ra != rb) {
+ if (ra - rb > 180) rb += 360; else if (rb - ra > 180) ra += 360; // shortest path
+ q.push({i: s.push(s.pop() + "rotate(", null, ")") - 2, x: d3_interpolateNumber(ra, rb)});
+ } else if (rb) {
+ s.push(s.pop() + "rotate(" + rb + ")");
+ }
+
+ if (wa != wb) {
+ q.push({i: s.push(s.pop() + "skewX(", null, ")") - 2, x: d3_interpolateNumber(wa, wb)});
+ } else if (wb) {
+ s.push(s.pop() + "skewX(" + wb + ")");
+ }
+
+ if (ka[0] != kb[0] || ka[1] != kb[1]) {
+ n = s.push(s.pop() + "scale(", null, ",", null, ")");
+ q.push({i: n - 4, x: d3_interpolateNumber(ka[0], kb[0])}, {i: n - 2, x: d3_interpolateNumber(ka[1], kb[1])});
+ } else if (kb[0] != 1 || kb[1] != 1) {
+ s.push(s.pop() + "scale(" + kb + ")");
+ }
+
+ n = q.length;
+ return function(t) {
+ var i = -1, o;
+ while (++i < n) s[(o = q[i]).i] = o.x(t);
+ return s.join("");
+ };
+}
+
+d3.interpolateObject = d3_interpolateObject;
+
+function d3_interpolateObject(a, b) {
+ var i = {},
+ c = {},
+ k;
+ for (k in a) {
+ if (k in b) {
+ i[k] = d3_interpolateByName(k)(a[k], b[k]);
+ } else {
+ c[k] = a[k];
+ }
+ }
+ for (k in b) {
+ if (!(k in a)) {
+ c[k] = b[k];
+ }
+ }
+ return function(t) {
+ for (k in i) c[k] = i[k](t);
+ return c;
+ };
+}
+
+d3.interpolateArray = d3_interpolateArray;
+
+function d3_interpolateArray(a, b) {
+ var x = [],
+ c = [],
+ na = a.length,
+ nb = b.length,
+ n0 = Math.min(a.length, b.length),
+ i;
+ for (i = 0; i < n0; ++i) x.push(d3_interpolate(a[i], b[i]));
+ for (; i < na; ++i) c[i] = a[i];
+ for (; i < nb; ++i) c[i] = b[i];
+ return function(t) {
+ for (i = 0; i < n0; ++i) c[i] = x[i](t);
+ return c;
+ };
+}
+
+d3.interpolateString = d3_interpolateString;
+
+function d3_interpolateString(a, b) {
+ var m, // current match
+ i, // current index
+ j, // current index (for coalescing)
+ s0 = 0, // start index of current string prefix
+ s1 = 0, // end index of current string prefix
+ s = [], // string constants and placeholders
+ q = [], // number interpolators
+ n, // q.length
+ o;
+
+ // Reset our regular expression!
+ d3_interpolate_number.lastIndex = 0;
+
+ // Find all numbers in b.
+ for (i = 0; m = d3_interpolate_number.exec(b); ++i) {
+ if (m.index) s.push(b.substring(s0, s1 = m.index));
+ q.push({i: s.length, x: m[0]});
+ s.push(null);
+ s0 = d3_interpolate_number.lastIndex;
+ }
+ if (s0 < b.length) s.push(b.substring(s0));
+
+ // Find all numbers in a.
+ for (i = 0, n = q.length; (m = d3_interpolate_number.exec(a)) && i < n; ++i) {
+ o = q[i];
+ if (o.x == m[0]) { // The numbers match, so coalesce.
+ if (o.i) {
+ if (s[o.i + 1] == null) { // This match is followed by another number.
+ s[o.i - 1] += o.x;
+ s.splice(o.i, 1);
+ for (j = i + 1; j < n; ++j) q[j].i--;
+ } else { // This match is followed by a string, so coalesce twice.
+ s[o.i - 1] += o.x + s[o.i + 1];
+ s.splice(o.i, 2);
+ for (j = i + 1; j < n; ++j) q[j].i -= 2;
+ }
+ } else {
+ if (s[o.i + 1] == null) { // This match is followed by another number.
+ s[o.i] = o.x;
+ } else { // This match is followed by a string, so coalesce twice.
+ s[o.i] = o.x + s[o.i + 1];
+ s.splice(o.i + 1, 1);
+ for (j = i + 1; j < n; ++j) q[j].i--;
+ }
+ }
+ q.splice(i, 1);
+ n--;
+ i--;
+ } else {
+ o.x = d3_interpolateNumber(parseFloat(m[0]), parseFloat(o.x));
+ }
+ }
+
+ // Remove any numbers in b not found in a.
+ while (i < n) {
+ o = q.pop();
+ if (s[o.i + 1] == null) { // This match is followed by another number.
+ s[o.i] = o.x;
+ } else { // This match is followed by a string, so coalesce twice.
+ s[o.i] = o.x + s[o.i + 1];
+ s.splice(o.i + 1, 1);
+ }
+ n--;
+ }
+
+ // Special optimization for only a single match.
+ if (s.length === 1) {
+ return s[0] == null ? q[0].x : function() { return b; };
+ }
+
+ // Otherwise, interpolate each of the numbers and rejoin the string.
+ return function(t) {
+ for (i = 0; i < n; ++i) s[(o = q[i]).i] = o.x(t);
+ return s.join("");
+ };
+}
+
+var d3_interpolate_number = /[-+]?(?:\d+\.?\d*|\.?\d+)(?:[eE][-+]?\d+)?/g;
+
+d3.interpolate = d3_interpolate;
+
+function d3_interpolate(a, b) {
+ var i = d3.interpolators.length, f;
+ while (--i >= 0 && !(f = d3.interpolators[i](a, b)));
+ return f;
+}
+
+function d3_interpolateByName(name) {
+ return name == "transform"
+ ? d3_interpolateTransform
+ : d3_interpolate;
+}
+
+d3.interpolators = [
+ d3_interpolateObject,
+ function(a, b) { return Array.isArray(b) && d3_interpolateArray(a, b); },
+ function(a, b) { return (typeof a === "string" || typeof b === "string") && d3_interpolateString(a + "", b + ""); },
+ function(a, b) { return (typeof b === "string" ? d3_rgb_names.has(b) || /^(#|rgb\(|hsl\()/.test(b) : b instanceof d3_Color) && d3_interpolateRgb(a, b); },
+ function(a, b) { return !isNaN(a = +a) && !isNaN(b = +b) && d3_interpolateNumber(a, b); }
+];
+
+d3_transitionPrototype.tween = function(name, tween) {
+ var id = this.id;
+ if (arguments.length < 2) return this.node().__transition__[id].tween.get(name);
+ return d3_selection_each(this, tween == null
+ ? function(node) { node.__transition__[id].tween.remove(name); }
+ : function(node) { node.__transition__[id].tween.set(name, tween); });
+};
+
+function d3_transition_tween(groups, name, value, tween) {
+ var id = groups.id;
+ return d3_selection_each(groups, typeof value === "function"
+ ? function(node, i, j) { node.__transition__[id].tween.set(name, tween(value.call(node, node.__data__, i, j))); }
+ : (value = tween(value), function(node) { node.__transition__[id].tween.set(name, value); }));
+}
+
+d3_transitionPrototype.attr = function(nameNS, value) {
+ if (arguments.length < 2) {
+
+ // For attr(object), the object specifies the names and values of the
+ // attributes to transition. The values may be functions that are
+ // evaluated for each element.
+ for (value in nameNS) this.attr(value, nameNS[value]);
+ return this;
+ }
+
+ var interpolate = d3_interpolateByName(nameNS),
+ name = d3.ns.qualify(nameNS);
+
+ // For attr(string, null), remove the attribute with the specified name.
+ function attrNull() {
+ this.removeAttribute(name);
+ }
+ function attrNullNS() {
+ this.removeAttributeNS(name.space, name.local);
+ }
+
+ return d3_transition_tween(this, "attr." + nameNS, value, function(b) {
+
+ // For attr(string, string), set the attribute with the specified name.
+ function attrString() {
+ var a = this.getAttribute(name), i;
+ return a !== b && (i = interpolate(a, b), function(t) { this.setAttribute(name, i(t)); });
+ }
+ function attrStringNS() {
+ var a = this.getAttributeNS(name.space, name.local), i;
+ return a !== b && (i = interpolate(a, b), function(t) { this.setAttributeNS(name.space, name.local, i(t)); });
+ }
+
+ return b == null ? (name.local ? attrNullNS : attrNull)
+ : (b += "", name.local ? attrStringNS : attrString);
+ });
+};
+
+d3_transitionPrototype.attrTween = function(nameNS, tween) {
+ var name = d3.ns.qualify(nameNS);
+
+ function attrTween(d, i) {
+ var f = tween.call(this, d, i, this.getAttribute(name));
+ return f && function(t) { this.setAttribute(name, f(t)); };
+ }
+
+ function attrTweenNS(d, i) {
+ var f = tween.call(this, d, i, this.getAttributeNS(name.space, name.local));
+ return f && function(t) { this.setAttributeNS(name.space, name.local, f(t)); };
+ }
+
+ return this.tween("attr." + nameNS, name.local ? attrTweenNS : attrTween);
+};
+
+d3_transitionPrototype.style = function(name, value, priority) {
+ var n = arguments.length;
+ if (n < 3) {
+
+ // For style(object) or style(object, string), the object specifies the
+ // names and values of the attributes to set or remove. The values may be
+ // functions that are evaluated for each element. The optional string
+ // specifies the priority.
+ if (typeof name !== "string") {
+ if (n < 2) value = "";
+ for (priority in name) this.style(priority, name[priority], value);
+ return this;
+ }
+
+ // For style(string, string) or style(string, function), use the default
+ // priority. The priority is ignored for style(string, null).
+ priority = "";
+ }
+
+ var interpolate = d3_interpolateByName(name);
+
+ // For style(name, null) or style(name, null, priority), remove the style
+ // property with the specified name. The priority is ignored.
+ function styleNull() {
+ this.style.removeProperty(name);
+ }
+
+ // Otherwise, a name, value and priority are specified, and handled as below.
+ return d3_transition_tween(this, "style." + name, value, function(b) {
+
+ // For style(name, string) or style(name, string, priority), set the style
+ // property with the specified name, using the specified priority.
+ function styleString() {
+ var a = d3_window.getComputedStyle(this, null).getPropertyValue(name), i;
+ return a !== b && (i = interpolate(a, b), function(t) { this.style.setProperty(name, i(t), priority); });
+ }
+
+ return b == null ? styleNull
+ : (b += "", styleString);
+ });
+};
+
+d3_transitionPrototype.styleTween = function(name, tween, priority) {
+ if (arguments.length < 3) priority = "";
+ return this.tween("style." + name, function(d, i) {
+ var f = tween.call(this, d, i, d3_window.getComputedStyle(this, null).getPropertyValue(name));
+ return f && function(t) { this.style.setProperty(name, f(t), priority); };
+ });
+};
+
+d3_transitionPrototype.text = function(value) {
+ return d3_transition_tween(this, "text", value, d3_transition_text);
+};
+
+function d3_transition_text(b) {
+ if (b == null) b = "";
+ return function() { this.textContent = b; };
+}
+
+d3_transitionPrototype.remove = function() {
+ return this.each("end.transition", function() {
+ var p;
+ if (!this.__transition__ && (p = this.parentNode)) p.removeChild(this);
+ });
+};
+
+d3_transitionPrototype.ease = function(value) {
+ var id = this.id;
+ if (arguments.length < 1) return this.node().__transition__[id].ease;
+ if (typeof value !== "function") value = d3.ease.apply(d3, arguments);
+ return d3_selection_each(this, function(node) { node.__transition__[id].ease = value; });
+};
+
+d3_transitionPrototype.delay = function(value) {
+ var id = this.id;
+ return d3_selection_each(this, typeof value === "function"
+ ? function(node, i, j) { node.__transition__[id].delay = value.call(node, node.__data__, i, j) | 0; }
+ : (value |= 0, function(node) { node.__transition__[id].delay = value; }));
+};
+
+d3_transitionPrototype.duration = function(value) {
+ var id = this.id;
+ return d3_selection_each(this, typeof value === "function"
+ ? function(node, i, j) { node.__transition__[id].duration = Math.max(1, value.call(node, node.__data__, i, j) | 0); }
+ : (value = Math.max(1, value | 0), function(node) { node.__transition__[id].duration = value; }));
+};
+
+d3_transitionPrototype.each = function(type, listener) {
+ var id = this.id;
+ if (arguments.length < 2) {
+ var inherit = d3_transitionInherit,
+ inheritId = d3_transitionInheritId;
+ d3_transitionInheritId = id;
+ d3_selection_each(this, function(node, i, j) {
+ d3_transitionInherit = node.__transition__[id];
+ type.call(node, node.__data__, i, j);
+ });
+ d3_transitionInherit = inherit;
+ d3_transitionInheritId = inheritId;
+ } else {
+ d3_selection_each(this, function(node) {
+ node.__transition__[id].event.on(type, listener);
+ });
+ }
+ return this;
+};
+
+d3_transitionPrototype.transition = function() {
+ var id0 = this.id,
+ id1 = ++d3_transitionId,
+ subgroups = [],
+ subgroup,
+ group,
+ node,
+ transition;
+
+ for (var j = 0, m = this.length; j < m; j++) {
+ subgroups.push(subgroup = []);
+ for (var group = this[j], i = 0, n = group.length; i < n; i++) {
+ if (node = group[i]) {
+ transition = Object.create(node.__transition__[id0]);
+ transition.delay += transition.duration;
+ d3_transitionNode(node, i, id1, transition);
+ }
+ subgroup.push(node);
+ }
+ }
+
+ return d3_transition(subgroups, id1);
+};
+
+function d3_transitionNode(node, i, id, inherit) {
+ var lock = node.__transition__ || (node.__transition__ = {active: 0, count: 0}),
+ transition = lock[id];
+
+ if (!transition) {
+ var time = inherit.time;
+
+ transition = lock[id] = {
+ tween: new d3_Map,
+ event: d3.dispatch("start", "end"), // TODO construct lazily?
+ time: time,
+ ease: inherit.ease,
+ delay: inherit.delay,
+ duration: inherit.duration
+ };
+
+ ++lock.count;
+
+ d3.timer(function(elapsed) {
+ var d = node.__data__,
+ ease = transition.ease,
+ event = transition.event,
+ delay = transition.delay,
+ duration = transition.duration,
+ tweened = [];
+
+ return delay <= elapsed
+ ? start(elapsed)
+ : d3.timer(start, delay, time), 1;
+
+ function start(elapsed) {
+ if (lock.active > id) return stop();
+ lock.active = id;
+ event.start.call(node, d, i);
+
+ transition.tween.forEach(function(key, value) {
+ if (value = value.call(node, d, i)) {
+ tweened.push(value);
+ }
+ });
+
+ if (!tick(elapsed)) d3.timer(tick, 0, time);
+ return 1;
+ }
+
+ function tick(elapsed) {
+ if (lock.active !== id) return stop();
+
+ var t = (elapsed - delay) / duration,
+ e = ease(t),
+ n = tweened.length;
+
+ while (n > 0) {
+ tweened[--n].call(node, e);
+ }
+
+ if (t >= 1) {
+ stop();
+ event.end.call(node, d, i);
+ return 1;
+ }
+ }
+
+ function stop() {
+ if (--lock.count) delete lock[id];
+ else delete node.__transition__;
+ return 1;
+ }
+ }, 0, time);
+
+ return transition;
+ }
+}
+
+d3.xhr = function(url, mimeType, callback) {
+ var xhr = {},
+ dispatch = d3.dispatch("progress", "load", "error"),
+ headers = {},
+ response = d3_identity,
+ request = new (d3_window.XDomainRequest && /^(http(s)?:)?\/\//.test(url) ? XDomainRequest : XMLHttpRequest);
+
+ "onload" in request
+ ? request.onload = request.onerror = respond
+ : request.onreadystatechange = function() { request.readyState > 3 && respond(); };
+
+ function respond() {
+ var s = request.status;
+ !s && request.responseText || s >= 200 && s < 300 || s === 304
+ ? dispatch.load.call(xhr, response.call(xhr, request))
+ : dispatch.error.call(xhr, request);
+ }
+
+ request.onprogress = function(event) {
+ var o = d3.event;
+ d3.event = event;
+ try { dispatch.progress.call(xhr, request); }
+ finally { d3.event = o; }
+ };
+
+ xhr.header = function(name, value) {
+ name = (name + "").toLowerCase();
+ if (arguments.length < 2) return headers[name];
+ if (value == null) delete headers[name];
+ else headers[name] = value + "";
+ return xhr;
+ };
+
+ // If mimeType is non-null and no Accept header is set, a default is used.
+ xhr.mimeType = function(value) {
+ if (!arguments.length) return mimeType;
+ mimeType = value == null ? null : value + "";
+ return xhr;
+ };
+
+ // Specify how to convert the response content to a specific type;
+ // changes the callback value on "load" events.
+ xhr.response = function(value) {
+ response = value;
+ return xhr;
+ };
+
+ // Convenience methods.
+ ["get", "post"].forEach(function(method) {
+ xhr[method] = function() {
+ return xhr.send.apply(xhr, [method].concat(d3_array(arguments)));
+ };
+ });
+
+ // If callback is non-null, it will be used for error and load events.
+ xhr.send = function(method, data, callback) {
+ if (arguments.length === 2 && typeof data === "function") callback = data, data = null;
+ request.open(method, url, true);
+ if (mimeType != null && !("accept" in headers)) headers["accept"] = mimeType + ",*/*";
+ if (request.setRequestHeader) for (var name in headers) request.setRequestHeader(name, headers[name]);
+ if (mimeType != null && request.overrideMimeType) request.overrideMimeType(mimeType);
+ if (callback != null) xhr.on("error", callback).on("load", function(request) { callback(null, request); });
+ request.send(data == null ? null : data);
+ return xhr;
+ };
+
+ xhr.abort = function() {
+ request.abort();
+ return xhr;
+ };
+
+ d3.rebind(xhr, dispatch, "on");
+
+ if (arguments.length === 2 && typeof mimeType === "function") callback = mimeType, mimeType = null;
+ return callback == null ? xhr : xhr.get(d3_xhr_fixCallback(callback));
+};
+
+function d3_xhr_fixCallback(callback) {
+ return callback.length === 1
+ ? function(error, request) { callback(error == null ? request : null); }
+ : callback;
+}
+
+d3.text = function() {
+ return d3.xhr.apply(d3, arguments).response(d3_text);
+};
+
+function d3_text(request) {
+ return request.responseText;
+}
+
+d3.json = function(url, callback) {
+ return d3.xhr(url, "application/json", callback).response(d3_json);
+};
+
+function d3_json(request) {
+ return JSON.parse(request.responseText);
+}
+
+d3.html = function(url, callback) {
+ return d3.xhr(url, "text/html", callback).response(d3_html);
+};
+
+function d3_html(request) {
+ var range = d3_document.createRange();
+ range.selectNode(d3_document.body);
+ return range.createContextualFragment(request.responseText);
+}
+
+d3.xml = function() {
+ return d3.xhr.apply(d3, arguments).response(d3_xml);
+};
+
+function d3_xml(request) {
+ return request.responseXML;
+}
+ return d3;
+})();
+d3.combobox = function() {
+ var event = d3.dispatch('accept'),
+ id = d3.combobox.id ++,
+ data = [];
+
+ var fetcher = function(val, data, cb) {
+ cb(data.filter(function(d) {
+ return d.title
+ .toString()
+ .toLowerCase()
+ .indexOf(val.toLowerCase()) !== -1;
+ }));
+ };
+
+ var combobox = function(input) {
+ var idx = -1, container, shown = false;
+
+ input
+ .classed('combobox-input', true)
+ .each(function() {
+ var parent = this.parentNode,
+ sibling = this.nextSibling;
+ d3.select(parent)
+ .insert('div', function() { return sibling; })
+ .attr('class', 'combobox-carat')
+ .on('mousedown', function () {
+ // prevent the form element from blurring. it blurs
+ // on mousedown
+ d3.event.stopPropagation();
+ d3.event.preventDefault();
+ mousedown();
+ });
+ });
+
+ function updateSize() {
+ var rect = input.node().getBoundingClientRect();
+ container.style({
+ 'left': rect.left + 'px',
+ 'width': rect.width + 'px',
+ 'top': rect.height + rect.top + 'px'
+ });
+ }
+
+ function blur() {
+ // hide the combobox whenever the input element
+ // loses focus
+ slowHide();
+ }
+
+ function show() {
+ if (!shown) {
+ container = d3.select(document.body)
+ .insert('div', ':first-child')
+ .attr('class', 'combobox')
+ .style({
+ position: 'absolute',
+ display: 'block',
+ left: '0px'
+ });
+
+ shown = true;
+ }
+ }
+
+ function hide() {
+ if (shown) {
+ idx = -1;
+ container.remove();
+ shown = false;
+ }
+ }
+
+ function slowHide() {
+ window.setTimeout(hide, 150);
+ }
+ function keydown() {
+ if (!shown) return;
+ switch (d3.event.keyCode) {
+ // down arrow
+ case 40:
+ next();
+ d3.event.preventDefault();
+ break;
+ // up arrow
+ case 38:
+ prev();
+ d3.event.preventDefault();
+ break;
+ // escape, tab
+ case 13:
+ d3.event.preventDefault();
+ break;
+ }
+ d3.event.stopPropagation();
+ }
+
+ function keyup() {
+ switch (d3.event.keyCode) {
+ // escape
+ case 27:
+ hide();
+ break;
+ // escape, tab
+ case 9:
+ case 13:
+ if (!shown) return;
+ accept();
+ break;
+ default:
+ update();
+ d3.event.preventDefault();
+ }
+ d3.event.stopPropagation();
+ }
+
+ function accept() {
+ if (container.select('a.selected').node()) {
+ select(container.select('a.selected').datum());
+ }
+ hide();
+ }
+
+ function next() {
+ var len = container.selectAll('a').data().length;
+ idx = Math.min(idx + 1, len - 1);
+ highlight();
+ }
+
+ function prev() {
+ idx = Math.max(idx - 1, 0);
+ highlight();
+ }
+
+ var prevValue, prevCompletion;
+
+ function autocomplete(e, data) {
+
+ var value = input.property('value'),
+ match;
+
+ for (var i = 0; i < data.length; i++) {
+ if (data[i].value.toLowerCase().indexOf(value.toLowerCase()) === 0) {
+ match = data[i].value;
+ break;
+ }
+ }
+
+ // backspace
+ if (e.keyCode === 8) {
+ prevValue = value;
+ prevCompletion = '';
+
+ } else if (value && match && value !== prevValue + prevCompletion) {
+ prevValue = value;
+ prevCompletion = match.substr(value.length);
+ input.property('value', prevValue + prevCompletion);
+ input.node().setSelectionRange(value.length, value.length + prevCompletion.length);
+ }
+ }
+
+
+ function highlight() {
+ container
+ .selectAll('a')
+ .classed('selected', function(d, i) { return i == idx; });
+ var height = container.node().offsetHeight,
+ top = container.select('a.selected').node().offsetTop,
+ selectedHeight = container.select('a.selected').node().offsetHeight;
+ if ((top + selectedHeight) < height) {
+ container.node().scrollTop = 0;
+ } else {
+ container.node().scrollTop = top;
+ }
+ }
+
+ function update(value) {
+
+ if (typeof value === 'undefined') {
+ value = input.property('value');
+ }
+
+ var e = d3.event;
+
+ function render(data) {
+
+ if (data.length &&
+ document.activeElement === input.node()) show();
+ else return hide();
+
+ autocomplete(e, data);
+
+ updateSize();
+
+ var options = container
+ .selectAll('a.combobox-option')
+ .data(data, function(d) { return d.value; });
+
+ options.enter()
+ .append('a')
+ .text(function(d) { return d.value; })
+ .attr('class', 'combobox-option')
+ .attr('title', function(d) { return d.title; })
+ .on('click', select);
+
+ options.exit().remove();
+
+ options
+ .classed('selected', function(d, i) { return i == idx; })
+ .order();
+ }
+
+ fetcher.apply(input, [value, data, render]);
+ }
+
+ // select the choice given as d
+ function select(d) {
+ input
+ .property('value', d.value)
+ .trigger('change');
+ event.accept(d);
+ hide();
+ }
+
+ function mousedown() {
+
+ if (shown) return hide();
+
+ input.node().focus();
+ update('');
+
+ if (!container) return;
+
+ var entries = container.selectAll('a'),
+ height = container.node().scrollHeight / entries[0].length,
+ w = d3.select(window);
+
+ function getIndex(m) {
+ return Math.floor((m[1] + container.node().scrollTop) / height);
+ }
+
+ function withinBounds(m) {
+ var n = container.node();
+ return m[0] >= 0 && m[0] < n.offsetWidth &&
+ m[1] >= 0 && m[1] < n.offsetHeight;
+ }
+
+ w.on('mousemove.typeahead', function() {
+ var m = d3.mouse(container.node());
+ var within = withinBounds(m);
+ var n = getIndex(m);
+ entries.classed('selected', function(d, i) { return within && i === n; });
+ });
+
+ w.on('mouseup.typeahead', function() {
+ var m = d3.mouse(container.node());
+ if (withinBounds(m)) select(d3.select(entries[0][getIndex(m)]).datum());
+ entries.classed('selected', false);
+ w.on('mouseup.typeahead', null);
+ w.on('mousemove.typeahead', null);
+ });
+ }
+
+ input
+ .on('blur.typeahead', blur)
+ .on('keydown.typeahead', keydown)
+ .on('keyup.typeahead', keyup)
+ .on('mousedown.typeahead', mousedown);
+
+ d3.select(document.body).on('scroll.combo' + id, function() {
+ if (shown) updateSize();
+ }, true);
+ };
+
+ combobox.fetcher = function(_) {
+ if (!arguments.length) return fetcher;
+ fetcher = _;
+ return combobox;
+ };
+
+ combobox.data = function(_) {
+ if (!arguments.length) return data;
+ data = _;
+ return combobox;
+ };
+
+ return d3.rebind(combobox, event, 'on');
+};
+
+d3.combobox.id = 0;
+d3.geo.tile = function() {
+ var size = [960, 500],
+ scale = 256,
+ scaleExtent = [0, 20],
+ translate = [size[0] / 2, size[1] / 2],
+ zoomDelta = 0;
+
+ function bound(_) {
+ return Math.min(scaleExtent[1], Math.max(scaleExtent[0], _));
+ }
+
+ function tile() {
+ var z = Math.max(Math.log(scale) / Math.LN2 - 8, 0),
+ z0 = bound(Math.round(z + zoomDelta)),
+ k = Math.pow(2, z - z0 + 8),
+ origin = [(translate[0] - scale / 2) / k, (translate[1] - scale / 2) / k],
+ tiles = [],
+ cols = d3.range(Math.max(0, Math.floor(-origin[0])), Math.max(0, Math.ceil(size[0] / k - origin[0]))),
+ rows = d3.range(Math.max(0, Math.floor(-origin[1])), Math.max(0, Math.ceil(size[1] / k - origin[1])));
+
+ rows.forEach(function(y) {
+ cols.forEach(function(x) {
+ tiles.push([x, y, z0]);
+ });
+ });
+
+ tiles.translate = origin;
+ tiles.scale = k;
+
+ return tiles;
+ }
+
+ tile.scaleExtent = function(_) {
+ if (!arguments.length) return scaleExtent;
+ scaleExtent = _;
+ return tile;
+ };
+
+ tile.size = function(_) {
+ if (!arguments.length) return size;
+ size = _;
+ return tile;
+ };
+
+ tile.scale = function(_) {
+ if (!arguments.length) return scale;
+ scale = _;
+ return tile;
+ };
+
+ tile.translate = function(_) {
+ if (!arguments.length) return translate;
+ translate = _;
+ return tile;
+ };
+
+ tile.zoomDelta = function(_) {
+ if (!arguments.length) return zoomDelta;
+ zoomDelta = +_;
+ return tile;
+ };
+
+ return tile;
+};
+d3.jsonp = function (url, callback) {
+ function rand() {
+ var chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz',
+ c = '', i = -1;
+ while (++i < 15) c += chars.charAt(Math.floor(Math.random() * 52));
+ return c;
+ }
+
+ function create(url) {
+ var e = url.match(/callback=d3.jsonp.(\w+)/),
+ c = e ? e[1] : rand();
+ d3.jsonp[c] = function(data) {
+ callback(data);
+ delete d3.jsonp[c];
+ script.remove();
+ };
+ return 'd3.jsonp.' + c;
+ }
+
+ var cb = create(url),
+ script = d3.select('head')
+ .append('script')
+ .attr('type', 'text/javascript')
+ .attr('src', url.replace(/(\{|%7B)callback(\}|%7D)/, cb));
+};
+/*
+ * This code is licensed under the MIT license.
+ *
+ * Copyright © 2013, iD authors.
+ *
+ * Portions copyright © 2011, Keith Cirkel
+ * See https://github.com/keithamus/jwerty
+ *
+ */
+d3.keybinding = function(namespace) {
+ var bindings = [];
+
+ function matches(binding, event) {
+ for (var p in binding.event) {
+ if (event[p] != binding.event[p])
+ return false;
+ }
+
+ return (!binding.capture) === (event.eventPhase !== Event.CAPTURING_PHASE);
+ }
+
+ function capture() {
+ for (var i = 0; i < bindings.length; i++) {
+ var binding = bindings[i];
+ if (matches(binding, d3.event)) {
+ binding.callback();
+ }
+ }
+ }
+
+ function bubble() {
+ var tagName = d3.select(d3.event.target).node().tagName;
+ if (tagName == 'INPUT' || tagName == 'SELECT' || tagName == 'TEXTAREA') {
+ return;
+ }
+ capture();
+ }
+
+ function keybinding(selection) {
+ selection = selection || d3.select(document);
+ selection.on('keydown.capture' + namespace, capture, true);
+ selection.on('keydown.bubble' + namespace, bubble, false);
+ return keybinding;
+ }
+
+ keybinding.off = function(selection) {
+ selection = selection || d3.select(document);
+ selection.on('keydown.capture' + namespace, null);
+ selection.on('keydown.bubble' + namespace, null);
+ return keybinding;
+ };
+
+ keybinding.on = function(code, callback, capture) {
+ var binding = {
+ event: {
+ keyCode: 0,
+ shiftKey: false,
+ ctrlKey: false,
+ altKey: false,
+ metaKey: false
+ },
+ capture: capture,
+ callback: callback
+ };
+
+ code = code.toLowerCase().match(/(?:(?:[^+⇧⌃⌥⌘])+|[⇧⌃⌥⌘]|\+\+|^\+$)/g);
+
+ for (var i = 0; i < code.length; i++) {
+ // Normalise matching errors
+ if (code[i] === '++') code[i] = '+';
+
+ if (code[i] in d3.keybinding.modifierCodes) {
+ binding.event[d3.keybinding.modifierProperties[d3.keybinding.modifierCodes[code[i]]]] = true;
+ } else if (code[i] in d3.keybinding.keyCodes) {
+ binding.event.keyCode = d3.keybinding.keyCodes[code[i]];
+ }
+ }
+
+ bindings.push(binding);
+
+ return keybinding;
+ };
+
+ return keybinding;
+};
+
+(function () {
+ d3.keybinding.modifierCodes = {
+ // Shift key, ⇧
+ '⇧': 16, shift: 16,
+ // CTRL key, on Mac: ⌃
+ '⌃': 17, ctrl: 17,
+ // ALT key, on Mac: ⌥ (Alt)
+ '⌥': 18, alt: 18, option: 18,
+ // META, on Mac: ⌘ (CMD), on Windows (Win), on Linux (Super)
+ '⌘': 91, meta: 91, cmd: 91, 'super': 91, win: 91
+ };
+
+ d3.keybinding.modifierProperties = {
+ 16: 'shiftKey',
+ 17: 'ctrlKey',
+ 18: 'altKey',
+ 91: 'metaKey'
+ };
+
+ d3.keybinding.keyCodes = {
+ // Backspace key, on Mac: ⌫ (Backspace)
+ '⌫': 8, backspace: 8,
+ // Tab Key, on Mac: ⇥ (Tab), on Windows ⇥⇥
+ '⇥': 9, '⇆': 9, tab: 9,
+ // Return key, ↩
+ '↩': 13, 'return': 13, enter: 13, '⌅': 13,
+ // Pause/Break key
+ 'pause': 19, 'pause-break': 19,
+ // Caps Lock key, ⇪
+ '⇪': 20, caps: 20, 'caps-lock': 20,
+ // Escape key, on Mac: ⎋, on Windows: Esc
+ '⎋': 27, escape: 27, esc: 27,
+ // Space key
+ space: 32,
+ // Page-Up key, or pgup, on Mac: ↖
+ '↖': 33, pgup: 33, 'page-up': 33,
+ // Page-Down key, or pgdown, on Mac: ↘
+ '↘': 34, pgdown: 34, 'page-down': 34,
+ // END key, on Mac: ⇟
+ '⇟': 35, end: 35,
+ // HOME key, on Mac: ⇞
+ '⇞': 36, home: 36,
+ // Insert key, or ins
+ ins: 45, insert: 45,
+ // Delete key, on Mac: ⌦ (Delete)
+ '⌦': 46, del: 46, 'delete': 46,
+ // Left Arrow Key, or ←
+ '←': 37, left: 37, 'arrow-left': 37,
+ // Up Arrow Key, or ↑
+ '↑': 38, up: 38, 'arrow-up': 38,
+ // Right Arrow Key, or →
+ '→': 39, right: 39, 'arrow-right': 39,
+ // Up Arrow Key, or ↓
+ '↓': 40, down: 40, 'arrow-down': 40,
+ // odities, printing characters that come out wrong:
+ // Num-Multiply, or *
+ '*': 106, star: 106, asterisk: 106, multiply: 106,
+ // Num-Plus or +
+ '+': 107, 'plus': 107,
+ // Num-Subtract, or -
+ '-': 109, subtract: 109,
+ // Semicolon
+ ';': 186, semicolon:186,
+ // = or equals
+ '=': 187, 'equals': 187,
+ // Comma, or ,
+ ',': 188, comma: 188,
+ 'dash': 189, //???
+ // Period, or ., or full-stop
+ '.': 190, period: 190, 'full-stop': 190,
+ // Slash, or /, or forward-slash
+ '/': 191, slash: 191, 'forward-slash': 191,
+ // Tick, or `, or back-quote
+ '`': 192, tick: 192, 'back-quote': 192,
+ // Open bracket, or [
+ '[': 219, 'open-bracket': 219,
+ // Back slash, or \
+ '\\': 220, 'back-slash': 220,
+ // Close backet, or ]
+ ']': 221, 'close-bracket': 221,
+ // Apostrophe, or Quote, or '
+ '\'': 222, quote: 222, apostrophe: 222
+ };
+
+ // NUMPAD 0-9
+ var i = 95, n = 0;
+ while (++i < 106) {
+ d3.keybinding.keyCodes['num-' + n] = i;
+ ++n;
+ }
+
+ // 0-9
+ i = 47; n = 0;
+ while (++i < 58) {
+ d3.keybinding.keyCodes[n] = i;
+ ++n;
+ }
+
+ // F1-F25
+ i = 111; n = 1;
+ while (++i < 136) {
+ d3.keybinding.keyCodes['f' + n] = i;
+ ++n;
+ }
+
+ // a-z
+ i = 64;
+ while (++i < 91) {
+ d3.keybinding.keyCodes[String.fromCharCode(i).toLowerCase()] = i;
+ }
+})();
+d3.selection.prototype.one = function (type, listener, capture) {
+ var target = this, typeOnce = type + ".once";
+ function one() {
+ target.on(typeOnce, null);
+ listener.apply(this, arguments);
+ }
+ target.on(typeOnce, one, capture);
+ return this;
+};
+d3.selection.prototype.size = function (size) {
+ if (!arguments.length) {
+ var node = this.node();
+ return [node.offsetWidth,
+ node.offsetHeight];
+ }
+ return this.attr({width: size[0], height: size[1]});
+};
+d3.selection.prototype.trigger = function (type) {
+ this.each(function() {
+ var evt = document.createEvent('HTMLEvents');
+ evt.initEvent(type, true, true);
+ this.dispatchEvent(evt);
+ });
+};
+d3.typeahead = function() {
+ var event = d3.dispatch('accept'),
+ autohighlight = false,
+ data;
+
+ var typeahead = function(selection) {
+ var container,
+ hidden,
+ idx = autohighlight ? 0 : -1;
+
+ function setup() {
+ var rect = selection.node().getBoundingClientRect();
+ container = d3.select(document.body)
+ .append('div').attr('class', 'typeahead')
+ .style({
+ position: 'absolute',
+ left: rect.left + 'px',
+ top: rect.bottom + 'px'
+ });
+ selection
+ .on('keyup.typeahead', key);
+ hidden = false;
+ }
+
+ function hide() {
+ container.remove();
+ idx = autohighlight ? 0 : -1;
+ hidden = true;
+ }
+
+ function slowHide() {
+ if (autohighlight) {
+ if (container.select('a.selected').node()) {
+ select(container.select('a.selected').datum());
+ event.accept();
+ }
+ }
+ window.setTimeout(hide, 150);
+ }
+
+ selection
+ .on('focus.typeahead', setup)
+ .on('blur.typeahead', slowHide);
+
+ function key() {
+ var len = container.selectAll('a').data().length;
+ if (d3.event.keyCode === 40) {
+ idx = Math.min(idx + 1, len - 1);
+ return highlight();
+ } else if (d3.event.keyCode === 38) {
+ idx = Math.max(idx - 1, 0);
+ return highlight();
+ } else if (d3.event.keyCode === 13) {
+ if (container.select('a.selected').node()) {
+ select(container.select('a.selected').datum());
+ }
+ event.accept();
+ hide();
+ } else {
+ update();
+ }
+ }
+
+ function highlight() {
+ container
+ .selectAll('a')
+ .classed('selected', function(d, i) { return i == idx; });
+ }
+
+ function update() {
+ if (hidden) setup();
+
+ data(selection, function(data) {
+ container.style('display', function() {
+ return data.length ? 'block' : 'none';
+ });
+
+ var options = container
+ .selectAll('a')
+ .data(data, function(d) { return d.value; });
+
+ options.enter()
+ .append('a')
+ .text(function(d) { return d.value; })
+ .attr('title', function(d) { return d.title; })
+ .on('click', select);
+
+ options.exit().remove();
+
+ options
+ .classed('selected', function(d, i) { return i == idx; });
+ });
+ }
+
+ function select(d) {
+ selection
+ .property('value', d.value)
+ .trigger('change');
+ }
+
+ };
+
+ typeahead.data = function(_) {
+ if (!arguments.length) return data;
+ data = _;
+ return typeahead;
+ };
+
+ typeahead.autohighlight = function(_) {
+ if (!arguments.length) return autohighlight;
+ autohighlight = _;
+ return typeahead;
+ };
+
+ return d3.rebind(typeahead, event, 'on');
+};
+// Tooltips and svg mask used to highlight certain features
+d3.curtain = function() {
+
+ var event = d3.dispatch(),
+ surface,
+ tooltip,
+ darkness;
+
+ function curtain(selection) {
+
+ surface = selection.append('svg')
+ .attr('id', 'curtain')
+ .style({
+ 'z-index': 1000,
+ 'pointer-events': 'none',
+ 'position': 'absolute',
+ 'top': 0,
+ 'left': 0
+ });
+
+ darkness = surface.append('path')
+ .attr({
+ x: 0,
+ y: 0,
+ 'class': 'curtain-darkness'
+ });
+
+ d3.select(window).on('resize.curtain', resize);
+
+ tooltip = selection.append('div')
+ .attr('class', 'tooltip')
+ .style('z-index', 1002);
+
+ tooltip.append('div').attr('class', 'tooltip-arrow');
+ tooltip.append('div').attr('class', 'tooltip-inner');
+
+ resize();
+
+ function resize() {
+ surface.attr({
+ width: window.innerWidth,
+ height: window.innerHeight
+ });
+ curtain.cut(darkness.datum());
+ }
+ }
+
+ curtain.reveal = function(box, text, tooltipclass, duration) {
+ if (typeof box === 'string') box = d3.select(box).node();
+ if (box.getBoundingClientRect) box = box.getBoundingClientRect();
+
+ curtain.cut(box, duration);
+
+ if (text) {
+ // pseudo markdown bold text hack
+ var parts = text.split('**');
+ var html = parts[0] ? '' + parts[0] + '' : '';
+ if (parts[1]) html += '' + parts[1] + '';
+
+ var size = tooltip.classed('in', true)
+ .select('.tooltip-inner')
+ .html(html)
+ .size();
+
+ var pos;
+
+ var w = window.innerWidth,
+ h = window.innerHeight;
+
+ if (box.top + box.height < Math.min(100, box.width + box.left)) {
+ side = 'bottom';
+ pos = [box.left + box.width / 2 - size[0]/ 2, box.top + box.height];
+
+ } else if (box.left + box.width + 300 < window.innerWidth) {
+ side = 'right';
+ pos = [box.left + box.width, box.top + box.height / 2 - size[1] / 2];
+
+ } else if (box.left > 300) {
+ side = 'left';
+ pos = [box.left - 200, box.top + box.height / 2 - size[1] / 2];
+ } else {
+ side = 'bottom';
+ pos = [box.left, box.top + box.height];
+ }
+
+ pos = [
+ Math.min(Math.max(10, pos[0]), w - size[0] - 10),
+ Math.min(Math.max(10, pos[1]), h - size[1] - 10)
+ ];
+
+
+ if (duration !== 0 || !tooltip.classed(side)) tooltip.call(iD.ui.Toggle(true));
+
+ tooltip
+ .style('top', pos[1] + 'px')
+ .style('left', pos[0] + 'px')
+ .attr('class', 'curtain-tooltip tooltip in ' + side + ' ' + tooltipclass)
+ .select('.tooltip-inner')
+ .html(html);
+
+ } else {
+ tooltip.call(iD.ui.Toggle(false));
+ }
+ };
+
+ curtain.cut = function(datum, duration) {
+ darkness.datum(datum);
+
+ (duration === 0 ? darkness : darkness.transition().duration(duration || 600))
+ .attr('d', function(d) {
+ var string = "M 0,0 L 0," + window.innerHeight + " L " +
+ window.innerWidth + "," + window.innerHeight + "L" +
+ window.innerWidth + ",0 Z";
+
+ if (!d) return string;
+ return string + 'M' +
+ d.left + ',' + d.top + 'L' +
+ d.left + ',' + (d.top + d.height) + 'L' +
+ (d.left + d.width) + ',' + (d.top + d.height) + 'L' +
+ (d.left + d.width) + ',' + (d.top) + 'Z';
+
+ });
+ };
+
+ curtain.remove = function() {
+ surface.remove();
+ tooltip.remove();
+ };
+
+ return d3.rebind(curtain, event, 'on');
+};
+var JXON = new (function () {
+ var
+ sValueProp = "keyValue", sAttributesProp = "keyAttributes", sAttrPref = "@", /* you can customize these values */
+ aCache = [], rIsNull = /^\s*$/, rIsBool = /^(?:true|false)$/i;
+
+ function parseText (sValue) {
+ if (rIsNull.test(sValue)) { return null; }
+ if (rIsBool.test(sValue)) { return sValue.toLowerCase() === "true"; }
+ if (isFinite(sValue)) { return parseFloat(sValue); }
+ if (isFinite(Date.parse(sValue))) { return new Date(sValue); }
+ return sValue;
+ }
+
+ function EmptyTree () { }
+ EmptyTree.prototype.toString = function () { return "null"; };
+ EmptyTree.prototype.valueOf = function () { return null; };
+
+ function objectify (vValue) {
+ return vValue === null ? new EmptyTree() : vValue instanceof Object ? vValue : new vValue.constructor(vValue);
+ }
+
+ function createObjTree (oParentNode, nVerb, bFreeze, bNesteAttr) {
+ var
+ nLevelStart = aCache.length, bChildren = oParentNode.hasChildNodes(),
+ bAttributes = oParentNode.hasAttributes(), bHighVerb = Boolean(nVerb & 2);
+
+ var
+ sProp, vContent, nLength = 0, sCollectedTxt = "",
+ vResult = bHighVerb ? {} : /* put here the default value for empty nodes: */ true;
+
+ if (bChildren) {
+ for (var oNode, nItem = 0; nItem < oParentNode.childNodes.length; nItem++) {
+ oNode = oParentNode.childNodes.item(nItem);
+ if (oNode.nodeType === 4) { sCollectedTxt += oNode.nodeValue; } /* nodeType is "CDATASection" (4) */
+ else if (oNode.nodeType === 3) { sCollectedTxt += oNode.nodeValue.trim(); } /* nodeType is "Text" (3) */
+ else if (oNode.nodeType === 1 && !oNode.prefix) { aCache.push(oNode); } /* nodeType is "Element" (1) */
+ }
+ }
+
+ var nLevelEnd = aCache.length, vBuiltVal = parseText(sCollectedTxt);
+
+ if (!bHighVerb && (bChildren || bAttributes)) { vResult = nVerb === 0 ? objectify(vBuiltVal) : {}; }
+
+ for (var nElId = nLevelStart; nElId < nLevelEnd; nElId++) {
+ sProp = aCache[nElId].nodeName.toLowerCase();
+ vContent = createObjTree(aCache[nElId], nVerb, bFreeze, bNesteAttr);
+ if (vResult.hasOwnProperty(sProp)) {
+ if (vResult[sProp].constructor !== Array) { vResult[sProp] = [vResult[sProp]]; }
+ vResult[sProp].push(vContent);
+ } else {
+ vResult[sProp] = vContent;
+ nLength++;
+ }
+ }
+
+ if (bAttributes) {
+ var
+ nAttrLen = oParentNode.attributes.length,
+ sAPrefix = bNesteAttr ? "" : sAttrPref, oAttrParent = bNesteAttr ? {} : vResult;
+
+ for (var oAttrib, nAttrib = 0; nAttrib < nAttrLen; nLength++, nAttrib++) {
+ oAttrib = oParentNode.attributes.item(nAttrib);
+ oAttrParent[sAPrefix + oAttrib.name.toLowerCase()] = parseText(oAttrib.value.trim());
+ }
+
+ if (bNesteAttr) {
+ if (bFreeze) { Object.freeze(oAttrParent); }
+ vResult[sAttributesProp] = oAttrParent;
+ nLength -= nAttrLen - 1;
+ }
+ }
+
+ if (nVerb === 3 || (nVerb === 2 || nVerb === 1 && nLength > 0) && sCollectedTxt) {
+ vResult[sValueProp] = vBuiltVal;
+ } else if (!bHighVerb && nLength === 0 && sCollectedTxt) {
+ vResult = vBuiltVal;
+ }
+
+ if (bFreeze && (bHighVerb || nLength > 0)) { Object.freeze(vResult); }
+
+ aCache.length = nLevelStart;
+
+ return vResult;
+ }
+
+ function loadObjTree (oXMLDoc, oParentEl, oParentObj) {
+ var vValue, oChild;
+
+ if (oParentObj instanceof String || oParentObj instanceof Number || oParentObj instanceof Boolean) {
+ oParentEl.appendChild(oXMLDoc.createTextNode(oParentObj.toString())); /* verbosity level is 0 */
+ } else if (oParentObj.constructor === Date) {
+ oParentEl.appendChild(oXMLDoc.createTextNode(oParentObj.toGMTString()));
+ }
+
+ for (var sName in oParentObj) {
+ vValue = oParentObj[sName];
+ if (isFinite(sName) || vValue instanceof Function) { continue; } /* verbosity level is 0 */
+ if (sName === sValueProp) {
+ if (vValue !== null && vValue !== true) { oParentEl.appendChild(oXMLDoc.createTextNode(vValue.constructor === Date ? vValue.toGMTString() : String(vValue))); }
+ } else if (sName === sAttributesProp) { /* verbosity level is 3 */
+ for (var sAttrib in vValue) { oParentEl.setAttribute(sAttrib, vValue[sAttrib]); }
+ } else if (sName.charAt(0) === sAttrPref) {
+ oParentEl.setAttribute(sName.slice(1), vValue);
+ } else if (vValue.constructor === Array) {
+ for (var nItem = 0; nItem < vValue.length; nItem++) {
+ oChild = oXMLDoc.createElement(sName);
+ loadObjTree(oXMLDoc, oChild, vValue[nItem]);
+ oParentEl.appendChild(oChild);
+ }
+ } else {
+ oChild = oXMLDoc.createElement(sName);
+ if (vValue instanceof Object) {
+ loadObjTree(oXMLDoc, oChild, vValue);
+ } else if (vValue !== null && vValue !== true) {
+ oChild.appendChild(oXMLDoc.createTextNode(vValue.toString()));
+ }
+ oParentEl.appendChild(oChild);
+ }
+ }
+ }
+
+ this.build = function (oXMLParent, nVerbosity /* optional */, bFreeze /* optional */, bNesteAttributes /* optional */) {
+ var _nVerb = arguments.length > 1 && typeof nVerbosity === "number" ? nVerbosity & 3 : /* put here the default verbosity level: */ 1;
+ return createObjTree(oXMLParent, _nVerb, bFreeze || false, arguments.length > 3 ? bNesteAttributes : _nVerb === 3);
+ };
+
+ this.unbuild = function (oObjTree) {
+ var oNewDoc = document.implementation.createDocument("", "", null);
+ loadObjTree(oNewDoc, oNewDoc, oObjTree);
+ return oNewDoc;
+ };
+
+ this.stringify = function (oObjTree) {
+ return (new XMLSerializer()).serializeToString(JXON.unbuild(oObjTree));
+ };
+})();
+// var myObject = JXON.build(doc);
+// we got our javascript object! try: alert(JSON.stringify(myObject));
+
+// var newDoc = JXON.unbuild(myObject);
+// we got our Document instance! try: alert((new XMLSerializer()).serializeToString(newDoc));
+/*!
+ * Lo-Dash 1.0.0-rc.3
+ * (c) 2012 John-David Dalton
+ * Based on Underscore.js 1.4.3
+ * (c) 2009-2012 Jeremy Ashkenas, DocumentCloud Inc.
+ * Available under MIT license
+ */
+;(function(window, undefined) {
+
+ /** Detect free variable `exports` */
+ var freeExports = typeof exports == 'object' && exports;
+
+ /** Detect free variable `global` and use it as `window` */
+ var freeGlobal = typeof global == 'object' && global;
+ if (freeGlobal.global === freeGlobal) {
+ window = freeGlobal;
+ }
+
+ /** Used for array and object method references */
+ var arrayRef = [],
+ // avoid a Closure Compiler bug by creatively creating an object
+ objectRef = new function(){};
+
+ /** Used to generate unique IDs */
+ var idCounter = 0;
+
+ /** Used internally to indicate various things */
+ var indicatorObject = objectRef;
+
+ /** Used by `cachedContains` as the default size when optimizations are enabled for large arrays */
+ var largeArraySize = 30;
+
+ /** Used to restore the original `_` reference in `noConflict` */
+ var oldDash = window._;
+
+ /** Used to detect template delimiter values that require a with-statement */
+ var reComplexDelimiter = /[-?+=!~*%&^<>|{(\/]|\[\D|\b(?:delete|in|instanceof|new|typeof|void)\b/;
+
+ /** Used to match HTML entities */
+ var reEscapedHtml = /&(?:amp|lt|gt|quot|#x27);/g;
+
+ /** Used to match empty string literals in compiled template source */
+ var reEmptyStringLeading = /\b__p \+= '';/g,
+ reEmptyStringMiddle = /\b(__p \+=) '' \+/g,
+ reEmptyStringTrailing = /(__e\(.*?\)|\b__t\)) \+\n'';/g;
+
+ /** Used to match regexp flags from their coerced string values */
+ var reFlags = /\w*$/;
+
+ /** Used to insert the data object variable into compiled template source */
+ var reInsertVariable = /(?:__e|__t = )\(\s*(?![\d\s"']|this\.)/g;
+
+ /** Used to detect if a method is native */
+ var reNative = RegExp('^' +
+ (objectRef.valueOf + '')
+ .replace(/[.*+?^=!:${}()|[\]\/\\]/g, '\\$&')
+ .replace(/valueOf|for [^\]]+/g, '.+?') + '$'
+ );
+
+ /**
+ * Used to match ES6 template delimiters
+ * http://people.mozilla.org/~jorendorff/es6-draft.html#sec-7.8.6
+ */
+ var reEsTemplate = /\$\{((?:(?=\\?)\\?[\s\S])*?)}/g;
+
+ /** Used to match "interpolate" template delimiters */
+ var reInterpolate = /<%=([\s\S]+?)%>/g;
+
+ /** Used to ensure capturing order of template delimiters */
+ var reNoMatch = /($^)/;
+
+ /** Used to match HTML characters */
+ var reUnescapedHtml = /[&<>"']/g;
+
+ /** Used to match unescaped characters in compiled string literals */
+ var reUnescapedString = /['\n\r\t\u2028\u2029\\]/g;
+
+ /** Used to fix the JScript [[DontEnum]] bug */
+ var shadowed = [
+ 'constructor', 'hasOwnProperty', 'isPrototypeOf', 'propertyIsEnumerable',
+ 'toLocaleString', 'toString', 'valueOf'
+ ];
+
+ /** Used to make template sourceURLs easier to identify */
+ var templateCounter = 0;
+
+ /** Native method shortcuts */
+ var ceil = Math.ceil,
+ concat = arrayRef.concat,
+ floor = Math.floor,
+ getPrototypeOf = reNative.test(getPrototypeOf = Object.getPrototypeOf) && getPrototypeOf,
+ hasOwnProperty = objectRef.hasOwnProperty,
+ push = arrayRef.push,
+ propertyIsEnumerable = objectRef.propertyIsEnumerable,
+ toString = objectRef.toString;
+
+ /* Native method shortcuts for methods with the same name as other `lodash` methods */
+ var nativeBind = reNative.test(nativeBind = slice.bind) && nativeBind,
+ nativeIsArray = reNative.test(nativeIsArray = Array.isArray) && nativeIsArray,
+ nativeIsFinite = window.isFinite,
+ nativeIsNaN = window.isNaN,
+ nativeKeys = reNative.test(nativeKeys = Object.keys) && nativeKeys,
+ nativeMax = Math.max,
+ nativeMin = Math.min,
+ nativeRandom = Math.random;
+
+ /** `Object#toString` result shortcuts */
+ var argsClass = '[object Arguments]',
+ arrayClass = '[object Array]',
+ boolClass = '[object Boolean]',
+ dateClass = '[object Date]',
+ funcClass = '[object Function]',
+ numberClass = '[object Number]',
+ objectClass = '[object Object]',
+ regexpClass = '[object RegExp]',
+ stringClass = '[object String]';
+
+ /** Detect various environments */
+ var isIeOpera = !!window.attachEvent,
+ isV8 = nativeBind && !/\n|true/.test(nativeBind + isIeOpera);
+
+ /* Detect if `Function#bind` exists and is inferred to be fast (all but V8) */
+ var isBindFast = nativeBind && !isV8;
+
+ /* Detect if `Object.keys` exists and is inferred to be fast (IE, Opera, V8) */
+ var isKeysFast = nativeKeys && (isIeOpera || isV8);
+
+ /**
+ * Detect the JScript [[DontEnum]] bug:
+ *
+ * In IE < 9 an objects own properties, shadowing non-enumerable ones, are
+ * made non-enumerable as well.
+ */
+ var hasDontEnumBug;
+
+ /** Detect if own properties are iterated after inherited properties (IE < 9) */
+ var iteratesOwnLast;
+
+ /**
+ * Detect if `Array#shift` and `Array#splice` augment array-like objects
+ * incorrectly:
+ *
+ * Firefox < 10, IE compatibility mode, and IE < 9 have buggy Array `shift()`
+ * and `splice()` functions that fail to remove the last element, `value[0]`,
+ * of array-like objects even though the `length` property is set to `0`.
+ * The `shift()` method is buggy in IE 8 compatibility mode, while `splice()`
+ * is buggy regardless of mode in IE < 9 and buggy in compatibility mode in IE 9.
+ */
+ var hasObjectSpliceBug = (hasObjectSpliceBug = { '0': 1, 'length': 1 },
+ arrayRef.splice.call(hasObjectSpliceBug, 0, 1), hasObjectSpliceBug[0]);
+
+ /** Detect if an `arguments` object's indexes are non-enumerable (IE < 9) */
+ var nonEnumArgs = true;
+
+ (function() {
+ var props = [];
+ function ctor() { this.x = 1; }
+ ctor.prototype = { 'valueOf': 1, 'y': 1 };
+ for (var prop in new ctor) { props.push(prop); }
+ for (prop in arguments) { nonEnumArgs = !prop; }
+
+ hasDontEnumBug = !/valueOf/.test(props);
+ iteratesOwnLast = props[0] != 'x';
+ }(1));
+
+ /** Detect if `arguments` objects are `Object` objects (all but Opera < 10.5) */
+ var argsAreObjects = arguments.constructor == Object;
+
+ /** Detect if `arguments` objects [[Class]] is unresolvable (Firefox < 4, IE < 9) */
+ var noArgsClass = !isArguments(arguments);
+
+ /**
+ * Detect lack of support for accessing string characters by index:
+ *
+ * IE < 8 can't access characters by index and IE 8 can only access
+ * characters by index on string literals.
+ */
+ var noCharByIndex = ('x'[0] + Object('x')[0]) != 'xx';
+
+ /**
+ * Detect if a node's [[Class]] is unresolvable (IE < 9)
+ * and that the JS engine won't error when attempting to coerce an object to
+ * a string without a `toString` property value of `typeof` "function".
+ */
+ try {
+ var noNodeClass = ({ 'toString': 0 } + '', toString.call(document) == objectClass);
+ } catch(e) { }
+
+ /**
+ * Detect if sourceURL syntax is usable without erroring:
+ *
+ * The JS engine embedded in Adobe products will throw a syntax error when
+ * it encounters a single line comment beginning with the `@` symbol.
+ *
+ * The JS engine in Narwhal will generate the function `function anonymous(){//}`
+ * and throw a syntax error.
+ *
+ * Avoid comments beginning `@` symbols in IE because they are part of its
+ * non-standard conditional compilation support.
+ * http://msdn.microsoft.com/en-us/library/121hztk3(v=vs.94).aspx
+ */
+ try {
+ var useSourceURL = (Function('//@')(), !isIeOpera);
+ } catch(e) { }
+
+ /** Used to identify object classifications that `_.clone` supports */
+ var cloneableClasses = {};
+ cloneableClasses[funcClass] = false;
+ cloneableClasses[argsClass] = cloneableClasses[arrayClass] =
+ cloneableClasses[boolClass] = cloneableClasses[dateClass] =
+ cloneableClasses[numberClass] = cloneableClasses[objectClass] =
+ cloneableClasses[regexpClass] = cloneableClasses[stringClass] = true;
+
+ /** Used to lookup a built-in constructor by [[Class]] */
+ var ctorByClass = {};
+ ctorByClass[arrayClass] = Array;
+ ctorByClass[boolClass] = Boolean;
+ ctorByClass[dateClass] = Date;
+ ctorByClass[objectClass] = Object;
+ ctorByClass[numberClass] = Number;
+ ctorByClass[regexpClass] = RegExp;
+ ctorByClass[stringClass] = String;
+
+ /** Used to determine if values are of the language type Object */
+ var objectTypes = {
+ 'boolean': false,
+ 'function': true,
+ 'object': true,
+ 'number': false,
+ 'string': false,
+ 'undefined': false
+ };
+
+ /** Used to escape characters for inclusion in compiled string literals */
+ var stringEscapes = {
+ '\\': '\\',
+ "'": "'",
+ '\n': 'n',
+ '\r': 'r',
+ '\t': 't',
+ '\u2028': 'u2028',
+ '\u2029': 'u2029'
+ };
+
+ /*--------------------------------------------------------------------------*/
+
+ /**
+ * Creates a `lodash` object, that wraps the given `value`, to enable
+ * method chaining.
+ *
+ * The chainable wrapper functions are:
+ * `after`, `assign`, `bind`, `bindAll`, `bindKey`, `chain`, `compact`, `compose`,
+ * `concat`, `countBy`, `debounce`, `defaults`, `defer`, `delay`, `difference`,
+ * `filter`, `flatten`, `forEach`, `forIn`, `forOwn`, `functions`, `groupBy`,
+ * `initial`, `intersection`, `invert`, `invoke`, `keys`, `map`, `max`, `memoize`,
+ * `merge`, `min`, `object`, `omit`, `once`, `pairs`, `partial`, `pick`, `pluck`,
+ * `push`, `range`, `reject`, `rest`, `reverse`, `shuffle`, `slice`, `sort`,
+ * `sortBy`, `splice`, `tap`, `throttle`, `times`, `toArray`, `union`, `uniq`,
+ * `unshift`, `values`, `where`, `without`, `wrap`, and `zip`
+ *
+ * The non-chainable wrapper functions are:
+ * `clone`, `cloneDeep`, `contains`, `escape`, `every`, `find`, `has`, `identity`,
+ * `indexOf`, `isArguments`, `isArray`, `isBoolean`, `isDate`, `isElement`, `isEmpty`,
+ * `isEqual`, `isFinite`, `isFunction`, `isNaN`, `isNull`, `isNumber`, `isObject`,
+ * `isPlainObject`, `isRegExp`, `isString`, `isUndefined`, `join`, `lastIndexOf`,
+ * `mixin`, `noConflict`, `pop`, `random`, `reduce`, `reduceRight`, `result`,
+ * `shift`, `size`, `some`, `sortedIndex`, `template`, `unescape`, and `uniqueId`
+ *
+ * The wrapper functions `first` and `last` return wrapped values when `n` is
+ * passed, otherwise they return unwrapped values.
+ *
+ * @name _
+ * @constructor
+ * @category Chaining
+ * @param {Mixed} value The value to wrap in a `lodash` instance.
+ * @returns {Object} Returns a `lodash` instance.
+ */
+ function lodash(value) {
+ // exit early if already wrapped, even if wrapped by a different `lodash` constructor
+ if (value && typeof value == 'object' && value.__wrapped__) {
+ return value;
+ }
+ // allow invoking `lodash` without the `new` operator
+ if (!(this instanceof lodash)) {
+ return new lodash(value);
+ }
+ this.__wrapped__ = value;
+ }
+
+ /**
+ * By default, the template delimiters used by Lo-Dash are similar to those in
+ * embedded Ruby (ERB). Change the following template settings to use alternative
+ * delimiters.
+ *
+ * @static
+ * @memberOf _
+ * @type Object
+ */
+ lodash.templateSettings = {
+
+ /**
+ * Used to detect `data` property values to be HTML-escaped.
+ *
+ * @static
+ * @memberOf _.templateSettings
+ * @type RegExp
+ */
+ 'escape': /<%-([\s\S]+?)%>/g,
+
+ /**
+ * Used to detect code to be evaluated.
+ *
+ * @static
+ * @memberOf _.templateSettings
+ * @type RegExp
+ */
+ 'evaluate': /<%([\s\S]+?)%>/g,
+
+ /**
+ * Used to detect `data` property values to inject.
+ *
+ * @static
+ * @memberOf _.templateSettings
+ * @type RegExp
+ */
+ 'interpolate': reInterpolate,
+
+ /**
+ * Used to reference the data object in the template text.
+ *
+ * @static
+ * @memberOf _.templateSettings
+ * @type String
+ */
+ 'variable': ''
+ };
+
+ /*--------------------------------------------------------------------------*/
+
+ /**
+ * The template used to create iterator functions.
+ *
+ * @private
+ * @param {Obect} data The data object used to populate the text.
+ * @returns {String} Returns the interpolated text.
+ */
+ var iteratorTemplate = template(
+ // conditional strict mode
+ "<% if (obj.useStrict) { %>'use strict';\n<% } %>" +
+
+ // the `iteratee` may be reassigned by the `top` snippet
+ 'var index, iteratee = <%= firstArg %>, ' +
+ // assign the `result` variable an initial value
+ 'result = <%= firstArg %>;\n' +
+ // exit early if the first argument is falsey
+ 'if (!<%= firstArg %>) return result;\n' +
+ // add code before the iteration branches
+ '<%= top %>;\n' +
+
+ // array-like iteration:
+ '<% if (arrayLoop) { %>' +
+ 'var length = iteratee.length; index = -1;\n' +
+ "if (typeof length == 'number') {" +
+
+ // add support for accessing string characters by index if needed
+ ' <% if (noCharByIndex) { %>\n' +
+ ' if (isString(iteratee)) {\n' +
+ " iteratee = iteratee.split('')\n" +
+ ' }' +
+ ' <% } %>\n' +
+
+ // iterate over the array-like value
+ ' while (++index < length) {\n' +
+ ' <%= arrayLoop %>\n' +
+ ' }\n' +
+ '}\n' +
+ 'else {' +
+
+ // object iteration:
+ // add support for iterating over `arguments` objects if needed
+ ' <% } else if (nonEnumArgs) { %>\n' +
+ ' var length = iteratee.length; index = -1;\n' +
+ ' if (length && isArguments(iteratee)) {\n' +
+ ' while (++index < length) {\n' +
+ " index += '';\n" +
+ ' <%= objectLoop %>\n' +
+ ' }\n' +
+ ' } else {' +
+ ' <% } %>' +
+
+ // Firefox < 3.6, Opera > 9.50 - Opera < 11.60, and Safari < 5.1
+ // (if the prototype or a property on the prototype has been set)
+ // incorrectly sets a function's `prototype` property [[Enumerable]]
+ // value to `true`. Because of this Lo-Dash standardizes on skipping
+ // the the `prototype` property of functions regardless of its
+ // [[Enumerable]] value.
+ ' <% if (!hasDontEnumBug) { %>\n' +
+ " var skipProto = typeof iteratee == 'function' && \n" +
+ " propertyIsEnumerable.call(iteratee, 'prototype');\n" +
+ ' <% } %>' +
+
+ // iterate own properties using `Object.keys` if it's fast
+ ' <% if (isKeysFast && useHas) { %>\n' +
+ ' var ownIndex = -1,\n' +
+ ' ownProps = objectTypes[typeof iteratee] ? nativeKeys(iteratee) : [],\n' +
+ ' length = ownProps.length;\n\n' +
+ ' while (++ownIndex < length) {\n' +
+ ' index = ownProps[ownIndex];\n' +
+ " <% if (!hasDontEnumBug) { %>if (!(skipProto && index == 'prototype')) {\n <% } %>" +
+ ' <%= objectLoop %>\n' +
+ ' <% if (!hasDontEnumBug) { %>}\n<% } %>' +
+ ' }' +
+
+ // else using a for-in loop
+ ' <% } else { %>\n' +
+ ' for (index in iteratee) {<%' +
+ ' if (!hasDontEnumBug || useHas) { %>\n if (<%' +
+ " if (!hasDontEnumBug) { %>!(skipProto && index == 'prototype')<% }" +
+ ' if (!hasDontEnumBug && useHas) { %> && <% }' +
+ ' if (useHas) { %>hasOwnProperty.call(iteratee, index)<% }' +
+ ' %>) {' +
+ ' <% } %>\n' +
+ ' <%= objectLoop %>;' +
+ ' <% if (!hasDontEnumBug || useHas) { %>\n }<% } %>\n' +
+ ' }' +
+ ' <% } %>' +
+
+ // Because IE < 9 can't set the `[[Enumerable]]` attribute of an
+ // existing property and the `constructor` property of a prototype
+ // defaults to non-enumerable, Lo-Dash skips the `constructor`
+ // property when it infers it's iterating over a `prototype` object.
+ ' <% if (hasDontEnumBug) { %>\n\n' +
+ ' var ctor = iteratee.constructor;\n' +
+ ' <% for (var k = 0; k < 7; k++) { %>\n' +
+ " index = '<%= shadowed[k] %>';\n" +
+ ' if (<%' +
+ " if (shadowed[k] == 'constructor') {" +
+ ' %>!(ctor && ctor.prototype === iteratee) && <%' +
+ ' } %>hasOwnProperty.call(iteratee, index)) {\n' +
+ ' <%= objectLoop %>\n' +
+ ' }' +
+ ' <% } %>' +
+ ' <% } %>' +
+ ' <% if (arrayLoop || nonEnumArgs) { %>\n}<% } %>\n' +
+
+ // add code to the bottom of the iteration function
+ '<%= bottom %>;\n' +
+ // finally, return the `result`
+ 'return result'
+ );
+
+ /** Reusable iterator options for `assign` and `defaults` */
+ var assignIteratorOptions = {
+ 'args': 'object, source, guard',
+ 'top':
+ "for (var argsIndex = 1, argsLength = typeof guard == 'number' ? 2 : arguments.length; argsIndex < argsLength; argsIndex++) {\n" +
+ ' if ((iteratee = arguments[argsIndex])) {',
+ 'objectLoop': 'result[index] = iteratee[index]',
+ 'bottom': ' }\n}'
+ };
+
+ /**
+ * Reusable iterator options shared by `each`, `forIn`, and `forOwn`.
+ */
+ var eachIteratorOptions = {
+ 'args': 'collection, callback, thisArg',
+ 'top': "callback = callback && typeof thisArg == 'undefined' ? callback : createCallback(callback, thisArg)",
+ 'arrayLoop': 'if (callback(iteratee[index], index, collection) === false) return result',
+ 'objectLoop': 'if (callback(iteratee[index], index, collection) === false) return result'
+ };
+
+ /** Reusable iterator options for `forIn` and `forOwn` */
+ var forOwnIteratorOptions = {
+ 'arrayLoop': null
+ };
+
+ /*--------------------------------------------------------------------------*/
+
+ /**
+ * Creates a function optimized to search large arrays for a given `value`,
+ * starting at `fromIndex`, using strict equality for comparisons, i.e. `===`.
+ *
+ * @private
+ * @param {Array} array The array to search.
+ * @param {Mixed} value The value to search for.
+ * @param {Number} [fromIndex=0] The index to search from.
+ * @param {Number} [largeSize=30] The length at which an array is considered large.
+ * @returns {Boolean} Returns `true` if `value` is found, else `false`.
+ */
+ function cachedContains(array, fromIndex, largeSize) {
+ fromIndex || (fromIndex = 0);
+
+ var length = array.length,
+ isLarge = (length - fromIndex) >= (largeSize || largeArraySize);
+
+ if (isLarge) {
+ var cache = {},
+ index = fromIndex - 1;
+
+ while (++index < length) {
+ // manually coerce `value` to a string because `hasOwnProperty`, in some
+ // older versions of Firefox, coerces objects incorrectly
+ var key = array[index] + '';
+ (hasOwnProperty.call(cache, key) ? cache[key] : (cache[key] = [])).push(array[index]);
+ }
+ }
+ return function(value) {
+ if (isLarge) {
+ var key = value + '';
+ return hasOwnProperty.call(cache, key) && indexOf(cache[key], value) > -1;
+ }
+ return indexOf(array, value, fromIndex) > -1;
+ }
+ }
+
+ /**
+ * Used by `_.max` and `_.min` as the default `callback` when a given
+ * `collection` is a string value.
+ *
+ * @private
+ * @param {String} value The character to inspect.
+ * @returns {Number} Returns the code unit of given character.
+ */
+ function charAtCallback(value) {
+ return value.charCodeAt(0);
+ }
+
+ /**
+ * Used by `sortBy` to compare transformed `collection` values, stable sorting
+ * them in ascending order.
+ *
+ * @private
+ * @param {Object} a The object to compare to `b`.
+ * @param {Object} b The object to compare to `a`.
+ * @returns {Number} Returns the sort order indicator of `1` or `-1`.
+ */
+ function compareAscending(a, b) {
+ var ai = a.index,
+ bi = b.index;
+
+ a = a.criteria;
+ b = b.criteria;
+
+ // ensure a stable sort in V8 and other engines
+ // http://code.google.com/p/v8/issues/detail?id=90
+ if (a !== b) {
+ if (a > b || typeof a == 'undefined') {
+ return 1;
+ }
+ if (a < b || typeof b == 'undefined') {
+ return -1;
+ }
+ }
+ return ai < bi ? -1 : 1;
+ }
+
+ /**
+ * Creates a function that, when called, invokes `func` with the `this`
+ * binding of `thisArg` and prepends any `partailArgs` to the arguments passed
+ * to the bound function.
+ *
+ * @private
+ * @param {Function|String} func The function to bind or the method name.
+ * @param {Mixed} [thisArg] The `this` binding of `func`.
+ * @param {Array} partialArgs An array of arguments to be partially applied.
+ * @returns {Function} Returns the new bound function.
+ */
+ function createBound(func, thisArg, partialArgs) {
+ var isFunc = isFunction(func),
+ isPartial = !partialArgs,
+ key = thisArg;
+
+ // juggle arguments
+ if (isPartial) {
+ partialArgs = thisArg;
+ }
+ if (!isFunc) {
+ thisArg = func;
+ }
+
+ function bound() {
+ // `Function#bind` spec
+ // http://es5.github.com/#x15.3.4.5
+ var args = arguments,
+ thisBinding = isPartial ? this : thisArg;
+
+ if (!isFunc) {
+ func = thisArg[key];
+ }
+ if (partialArgs.length) {
+ args = args.length
+ ? partialArgs.concat(slice(args))
+ : partialArgs;
+ }
+ if (this instanceof bound) {
+ // ensure `new bound` is an instance of `bound` and `func`
+ noop.prototype = func.prototype;
+ thisBinding = new noop;
+ noop.prototype = null;
+
+ // mimic the constructor's `return` behavior
+ // http://es5.github.com/#x13.2.2
+ var result = func.apply(thisBinding, args);
+ return isObject(result) ? result : thisBinding;
+ }
+ return func.apply(thisBinding, args);
+ }
+ return bound;
+ }
+
+ /**
+ * Produces an iteration callback bound to an optional `thisArg`. If `func` is
+ * a property name, the callback will return the property value for a given element.
+ *
+ * @private
+ * @param {Function|String} [func=identity|property] The function called per
+ * iteration or property name to query.
+ * @param {Mixed} [thisArg] The `this` binding of `callback`.
+ * @param {Object} [accumulating] Used to indicate that the callback should
+ * accept an `accumulator` argument.
+ * @returns {Function} Returns a callback function.
+ */
+ function createCallback(func, thisArg, accumulating) {
+ if (!func) {
+ return identity;
+ }
+ if (typeof func != 'function') {
+ return function(object) {
+ return object[func];
+ };
+ }
+ if (typeof thisArg != 'undefined') {
+ if (accumulating) {
+ return function(accumulator, value, index, object) {
+ return func.call(thisArg, accumulator, value, index, object);
+ };
+ }
+ return function(value, index, object) {
+ return func.call(thisArg, value, index, object);
+ };
+ }
+ return func;
+ }
+
+ /**
+ * Creates compiled iteration functions.
+ *
+ * @private
+ * @param {Object} [options1, options2, ...] The compile options object(s).
+ * useHas - A boolean to specify using `hasOwnProperty` checks in the object loop.
+ * args - A string of comma separated arguments the iteration function will accept.
+ * top - A string of code to execute before the iteration branches.
+ * arrayLoop - A string of code to execute in the array loop.
+ * objectLoop - A string of code to execute in the object loop.
+ * bottom - A string of code to execute after the iteration branches.
+ *
+ * @returns {Function} Returns the compiled function.
+ */
+ function createIterator() {
+ var data = {
+ 'arrayLoop': '',
+ 'bottom': '',
+ 'hasDontEnumBug': hasDontEnumBug,
+ 'isKeysFast': isKeysFast,
+ 'objectLoop': '',
+ 'nonEnumArgs': nonEnumArgs,
+ 'noCharByIndex': noCharByIndex,
+ 'shadowed': shadowed,
+ 'top': '',
+ 'useHas': true
+ };
+
+ // merge options into a template data object
+ for (var object, index = 0; object = arguments[index]; index++) {
+ for (var key in object) {
+ data[key] = object[key];
+ }
+ }
+ var args = data.args;
+ data.firstArg = /^[^,]+/.exec(args)[0];
+
+ // create the function factory
+ var factory = Function(
+ 'createCallback, hasOwnProperty, isArguments, isString, objectTypes, ' +
+ 'nativeKeys, propertyIsEnumerable',
+ 'return function(' + args + ') {\n' + iteratorTemplate(data) + '\n}'
+ );
+ // return the compiled function
+ return factory(
+ createCallback, hasOwnProperty, isArguments, isString, objectTypes,
+ nativeKeys, propertyIsEnumerable
+ );
+ }
+
+ /**
+ * A function compiled to iterate `arguments` objects, arrays, objects, and
+ * strings consistenly across environments, executing the `callback` for each
+ * element in the `collection`. The `callback` is bound to `thisArg` and invoked
+ * with three arguments; (value, index|key, collection). Callbacks may exit
+ * iteration early by explicitly returning `false`.
+ *
+ * @private
+ * @param {Array|Object|String} collection The collection to iterate over.
+ * @param {Function} [callback=identity] The function called per iteration.
+ * @param {Mixed} [thisArg] The `this` binding of `callback`.
+ * @returns {Array|Object|String} Returns `collection`.
+ */
+ var each = createIterator(eachIteratorOptions);
+
+ /**
+ * Used by `template` to escape characters for inclusion in compiled
+ * string literals.
+ *
+ * @private
+ * @param {String} match The matched character to escape.
+ * @returns {String} Returns the escaped character.
+ */
+ function escapeStringChar(match) {
+ return '\\' + stringEscapes[match];
+ }
+
+ /**
+ * Used by `escape` to convert characters to HTML entities.
+ *
+ * @private
+ * @param {String} match The matched character to escape.
+ * @returns {String} Returns the escaped character.
+ */
+ function escapeHtmlChar(match) {
+ return htmlEscapes[match];
+ }
+
+ /**
+ * Checks if `value` is a DOM node in IE < 9.
+ *
+ * @private
+ * @param {Mixed} value The value to check.
+ * @returns {Boolean} Returns `true` if the `value` is a DOM node, else `false`.
+ */
+ function isNode(value) {
+ // IE < 9 presents DOM nodes as `Object` objects except they have `toString`
+ // methods that are `typeof` "string" and still can coerce nodes to strings
+ return typeof value.toString != 'function' && typeof (value + '') == 'string';
+ }
+
+ /**
+ * A no-operation function.
+ *
+ * @private
+ */
+ function noop() {
+ // no operation performed
+ }
+
+ /**
+ * Slices the `collection` from the `start` index up to, but not including,
+ * the `end` index.
+ *
+ * Note: This function is used, instead of `Array#slice`, to support node lists
+ * in IE < 9 and to ensure dense arrays are returned.
+ *
+ * @private
+ * @param {Array|Object|String} collection The collection to slice.
+ * @param {Number} start The start index.
+ * @param {Number} end The end index.
+ * @returns {Array} Returns the new array.
+ */
+ function slice(array, start, end) {
+ start || (start = 0);
+ if (typeof end == 'undefined') {
+ end = array ? array.length : 0;
+ }
+ var index = -1,
+ length = end - start || 0,
+ result = Array(length < 0 ? 0 : length);
+
+ while (++index < length) {
+ result[index] = array[start + index];
+ }
+ return result;
+ }
+
+ /**
+ * Used by `unescape` to convert HTML entities to characters.
+ *
+ * @private
+ * @param {String} match The matched character to unescape.
+ * @returns {String} Returns the unescaped character.
+ */
+ function unescapeHtmlChar(match) {
+ return htmlUnescapes[match];
+ }
+
+ /*--------------------------------------------------------------------------*/
+
+ /**
+ * Assigns own enumerable properties of source object(s) to the `destination`
+ * object. Subsequent sources will overwrite propery assignments of previous
+ * sources.
+ *
+ * @static
+ * @memberOf _
+ * @alias extend
+ * @category Objects
+ * @param {Object} object The destination object.
+ * @param {Object} [source1, source2, ...] The source objects.
+ * @returns {Object} Returns the destination object.
+ * @example
+ *
+ * _.assign({ 'name': 'moe' }, { 'age': 40 });
+ * // => { 'name': 'moe', 'age': 40 }
+ */
+ var assign = createIterator(assignIteratorOptions);
+
+ /**
+ * Checks if `value` is an `arguments` object.
+ *
+ * @static
+ * @memberOf _
+ * @category Objects
+ * @param {Mixed} value The value to check.
+ * @returns {Boolean} Returns `true` if the `value` is an `arguments` object, else `false`.
+ * @example
+ *
+ * (function() { return _.isArguments(arguments); })(1, 2, 3);
+ * // => true
+ *
+ * _.isArguments([1, 2, 3]);
+ * // => false
+ */
+ function isArguments(value) {
+ return toString.call(value) == argsClass;
+ }
+ // fallback for browsers that can't detect `arguments` objects by [[Class]]
+ if (noArgsClass) {
+ isArguments = function(value) {
+ return value ? hasOwnProperty.call(value, 'callee') : false;
+ };
+ }
+
+ /**
+ * Iterates over `object`'s own and inherited enumerable properties, executing
+ * the `callback` for each property. The `callback` is bound to `thisArg` and
+ * invoked with three arguments; (value, key, object). Callbacks may exit iteration
+ * early by explicitly returning `false`.
+ *
+ * @static
+ * @memberOf _
+ * @category Objects
+ * @param {Object} object The object to iterate over.
+ * @param {Function} [callback=identity] The function called per iteration.
+ * @param {Mixed} [thisArg] The `this` binding of `callback`.
+ * @returns {Object} Returns `object`.
+ * @example
+ *
+ * function Dog(name) {
+ * this.name = name;
+ * }
+ *
+ * Dog.prototype.bark = function() {
+ * alert('Woof, woof!');
+ * };
+ *
+ * _.forIn(new Dog('Dagny'), function(value, key) {
+ * alert(key);
+ * });
+ * // => alerts 'name' and 'bark' (order is not guaranteed)
+ */
+ var forIn = createIterator(eachIteratorOptions, forOwnIteratorOptions, {
+ 'useHas': false
+ });
+
+ /**
+ * Iterates over an object's own enumerable properties, executing the `callback`
+ * for each property. The `callback` is bound to `thisArg` and invoked with three
+ * arguments; (value, key, object). Callbacks may exit iteration early by explicitly
+ * returning `false`.
+ *
+ * @static
+ * @memberOf _
+ * @category Objects
+ * @param {Object} object The object to iterate over.
+ * @param {Function} [callback=identity] The function called per iteration.
+ * @param {Mixed} [thisArg] The `this` binding of `callback`.
+ * @returns {Object} Returns `object`.
+ * @example
+ *
+ * _.forOwn({ '0': 'zero', '1': 'one', 'length': 2 }, function(num, key) {
+ * alert(key);
+ * });
+ * // => alerts '0', '1', and 'length' (order is not guaranteed)
+ */
+ var forOwn = createIterator(eachIteratorOptions, forOwnIteratorOptions);
+
+ /**
+ * A fallback implementation of `isPlainObject` that checks if a given `value`
+ * is an object created by the `Object` constructor, assuming objects created
+ * by the `Object` constructor have no inherited enumerable properties and that
+ * there are no `Object.prototype` extensions.
+ *
+ * @private
+ * @param {Mixed} value The value to check.
+ * @returns {Boolean} Returns `true` if `value` is a plain object, else `false`.
+ */
+ function shimIsPlainObject(value) {
+ // avoid non-objects and false positives for `arguments` objects
+ var result = false;
+ if (!(value && typeof value == 'object') || isArguments(value)) {
+ return result;
+ }
+ // check that the constructor is `Object` (i.e. `Object instanceof Object`)
+ var ctor = value.constructor;
+ if ((!isFunction(ctor) && (!noNodeClass || !isNode(value))) || ctor instanceof ctor) {
+ // IE < 9 iterates inherited properties before own properties. If the first
+ // iterated property is an object's own property then there are no inherited
+ // enumerable properties.
+ if (iteratesOwnLast) {
+ forIn(value, function(value, key, object) {
+ result = !hasOwnProperty.call(object, key);
+ return false;
+ });
+ return result === false;
+ }
+ // In most environments an object's own properties are iterated before
+ // its inherited properties. If the last iterated property is an object's
+ // own property then there are no inherited enumerable properties.
+ forIn(value, function(value, key) {
+ result = key;
+ });
+ return result === false || hasOwnProperty.call(value, result);
+ }
+ return result;
+ }
+
+ /**
+ * A fallback implementation of `Object.keys` that produces an array of the
+ * given object's own enumerable property names.
+ *
+ * @private
+ * @param {Object} object The object to inspect.
+ * @returns {Array} Returns a new array of property names.
+ */
+ function shimKeys(object) {
+ var result = [];
+ forOwn(object, function(value, key) {
+ result.push(key);
+ });
+ return result;
+ }
+
+ /**
+ * Used to convert characters to HTML entities:
+ *
+ * Though the `>` character is escaped for symmetry, characters like `>` and `/`
+ * don't require escaping in HTML and have no special meaning unless they're part
+ * of a tag or an unquoted attribute value.
+ * http://mathiasbynens.be/notes/ambiguous-ampersands (under "semi-related fun fact")
+ */
+ var htmlEscapes = {
+ '&': '&',
+ '<': '<',
+ '>': '>',
+ '"': '"',
+ "'": '''
+ };
+
+ /** Used to convert HTML entities to characters */
+ var htmlUnescapes = invert(htmlEscapes);
+
+ /*--------------------------------------------------------------------------*/
+
+ /**
+ * Creates a clone of `value`. If `deep` is `true`, nested objects will also
+ * be cloned, otherwise they will be assigned by reference.
+ *
+ * @static
+ * @memberOf _
+ * @category Objects
+ * @param {Mixed} value The value to clone.
+ * @param {Boolean} deep A flag to indicate a deep clone.
+ * @param- {Object} [guard] Internally used to allow this method to work with
+ * others like `_.map` without using their callback `index` argument for `deep`.
+ * @param- {Array} [stackA=[]] Internally used to track traversed source objects.
+ * @param- {Array} [stackB=[]] Internally used to associate clones with their
+ * source counterparts.
+ * @returns {Mixed} Returns the cloned `value`.
+ * @example
+ *
+ * var stooges = [
+ * { 'name': 'moe', 'age': 40 },
+ * { 'name': 'larry', 'age': 50 },
+ * { 'name': 'curly', 'age': 60 }
+ * ];
+ *
+ * var shallow = _.clone(stooges);
+ * shallow[0] === stooges[0];
+ * // => true
+ *
+ * var deep = _.clone(stooges, true);
+ * deep[0] === stooges[0];
+ * // => false
+ */
+ function clone(value, deep, guard, stackA, stackB) {
+ if (value == null) {
+ return value;
+ }
+ if (guard) {
+ deep = false;
+ }
+ // inspect [[Class]]
+ var isObj = isObject(value);
+ if (isObj) {
+ var className = toString.call(value);
+ if (!cloneableClasses[className] || (noNodeClass && isNode(value))) {
+ return value;
+ }
+ var isArr = isArray(value);
+ }
+ // shallow clone
+ if (!isObj || !deep) {
+ return isObj
+ ? (isArr ? slice(value) : assign({}, value))
+ : value;
+ }
+ var ctor = ctorByClass[className];
+ switch (className) {
+ case boolClass:
+ case dateClass:
+ return new ctor(+value);
+
+ case numberClass:
+ case stringClass:
+ return new ctor(value);
+
+ case regexpClass:
+ return ctor(value.source, reFlags.exec(value));
+ }
+ // check for circular references and return corresponding clone
+ stackA || (stackA = []);
+ stackB || (stackB = []);
+
+ var length = stackA.length;
+ while (length--) {
+ if (stackA[length] == value) {
+ return stackB[length];
+ }
+ }
+ // init cloned object
+ var result = isArr ? ctor(value.length) : {};
+
+ // add the source value to the stack of traversed objects
+ // and associate it with its clone
+ stackA.push(value);
+ stackB.push(result);
+
+ // recursively populate clone (susceptible to call stack limits)
+ (isArr ? forEach : forOwn)(value, function(objValue, key) {
+ result[key] = clone(objValue, deep, null, stackA, stackB);
+ });
+
+ // add array properties assigned by `RegExp#exec`
+ if (isArr) {
+ if (hasOwnProperty.call(value, 'index')) {
+ result.index = value.index;
+ }
+ if (hasOwnProperty.call(value, 'input')) {
+ result.input = value.input;
+ }
+ }
+ return result;
+ }
+
+ /**
+ * Creates a deep clone of `value`. Functions and DOM nodes are **not** cloned.
+ * The enumerable properties of `arguments` objects and objects created by
+ * constructors other than `Object` are cloned to plain `Object` objects.
+ *
+ * Note: This function is loosely based on the structured clone algorithm.
+ * See http://www.w3.org/TR/html5/common-dom-interfaces.html#internal-structured-cloning-algorithm.
+ *
+ * @static
+ * @memberOf _
+ * @category Objects
+ * @param {Mixed} value The value to deep clone.
+ * @returns {Mixed} Returns the deep cloned `value`.
+ * @example
+ *
+ * var stooges = [
+ * { 'name': 'moe', 'age': 40 },
+ * { 'name': 'larry', 'age': 50 },
+ * { 'name': 'curly', 'age': 60 }
+ * ];
+ *
+ * var deep = _.cloneDeep(stooges);
+ * deep[0] === stooges[0];
+ * // => false
+ */
+ function cloneDeep(value) {
+ return clone(value, true);
+ }
+
+ /**
+ * Assigns own enumerable properties of source object(s) to the `destination`
+ * object for all `destination` properties that resolve to `null`/`undefined`.
+ * Once a property is set, additional defaults of the same property will be
+ * ignored.
+ *
+ * @static
+ * @memberOf _
+ * @category Objects
+ * @param {Object} object The destination object.
+ * @param {Object} [default1, default2, ...] The default objects.
+ * @returns {Object} Returns the destination object.
+ * @example
+ *
+ * var iceCream = { 'flavor': 'chocolate' };
+ * _.defaults(iceCream, { 'flavor': 'vanilla', 'sprinkles': 'rainbow' });
+ * // => { 'flavor': 'chocolate', 'sprinkles': 'rainbow' }
+ */
+ var defaults = createIterator(assignIteratorOptions, {
+ 'objectLoop': 'if (result[index] == null) ' + assignIteratorOptions.objectLoop
+ });
+
+ /**
+ * Creates a sorted array of all enumerable properties, own and inherited,
+ * of `object` that have function values.
+ *
+ * @static
+ * @memberOf _
+ * @alias methods
+ * @category Objects
+ * @param {Object} object The object to inspect.
+ * @returns {Array} Returns a new array of property names that have function values.
+ * @example
+ *
+ * _.functions(_);
+ * // => ['all', 'any', 'bind', 'bindAll', 'clone', 'compact', 'compose', ...]
+ */
+ function functions(object) {
+ var result = [];
+ forIn(object, function(value, key) {
+ if (isFunction(value)) {
+ result.push(key);
+ }
+ });
+ return result.sort();
+ }
+
+ /**
+ * Checks if the specified object `property` exists and is a direct property,
+ * instead of an inherited property.
+ *
+ * @static
+ * @memberOf _
+ * @category Objects
+ * @param {Object} object The object to check.
+ * @param {String} property The property to check for.
+ * @returns {Boolean} Returns `true` if key is a direct property, else `false`.
+ * @example
+ *
+ * _.has({ 'a': 1, 'b': 2, 'c': 3 }, 'b');
+ * // => true
+ */
+ function has(object, property) {
+ return object ? hasOwnProperty.call(object, property) : false;
+ }
+
+ /**
+ * Creates an object composed of the inverted keys and values of the given `object`.
+ *
+ * @static
+ * @memberOf _
+ * @category Objects
+ * @param {Object} object The object to invert.
+ * @returns {Object} Returns the created inverted object.
+ * @example
+ *
+ * _.invert({ 'first': 'Moe', 'second': 'Larry', 'third': 'Curly' });
+ * // => { 'Moe': 'first', 'Larry': 'second', 'Curly': 'third' } (order is not guaranteed)
+ */
+ function invert(object) {
+ var result = {};
+ forOwn(object, function(value, key) {
+ result[value] = key;
+ });
+ return result;
+ }
+
+ /**
+ * Checks if `value` is an array.
+ *
+ * @static
+ * @memberOf _
+ * @category Objects
+ * @param {Mixed} value The value to check.
+ * @returns {Boolean} Returns `true` if the `value` is an array, else `false`.
+ * @example
+ *
+ * (function() { return _.isArray(arguments); })();
+ * // => false
+ *
+ * _.isArray([1, 2, 3]);
+ * // => true
+ */
+ var isArray = nativeIsArray || function(value) {
+ // `instanceof` may cause a memory leak in IE 7 if `value` is a host object
+ // http://ajaxian.com/archives/working-aroung-the-instanceof-memory-leak
+ return (argsAreObjects && value instanceof Array) || toString.call(value) == arrayClass;
+ };
+
+ /**
+ * Checks if `value` is a boolean (`true` or `false`) value.
+ *
+ * @static
+ * @memberOf _
+ * @category Objects
+ * @param {Mixed} value The value to check.
+ * @returns {Boolean} Returns `true` if the `value` is a boolean value, else `false`.
+ * @example
+ *
+ * _.isBoolean(null);
+ * // => false
+ */
+ function isBoolean(value) {
+ return value === true || value === false || toString.call(value) == boolClass;
+ }
+
+ /**
+ * Checks if `value` is a date.
+ *
+ * @static
+ * @memberOf _
+ * @category Objects
+ * @param {Mixed} value The value to check.
+ * @returns {Boolean} Returns `true` if the `value` is a date, else `false`.
+ * @example
+ *
+ * _.isDate(new Date);
+ * // => true
+ */
+ function isDate(value) {
+ return value instanceof Date || toString.call(value) == dateClass;
+ }
+
+ /**
+ * Checks if `value` is a DOM element.
+ *
+ * @static
+ * @memberOf _
+ * @category Objects
+ * @param {Mixed} value The value to check.
+ * @returns {Boolean} Returns `true` if the `value` is a DOM element, else `false`.
+ * @example
+ *
+ * _.isElement(document.body);
+ * // => true
+ */
+ function isElement(value) {
+ return value ? value.nodeType === 1 : false;
+ }
+
+ /**
+ * Checks if `value` is empty. Arrays, strings, or `arguments` objects with a
+ * length of `0` and objects with no own enumerable properties are considered
+ * "empty".
+ *
+ * @static
+ * @memberOf _
+ * @category Objects
+ * @param {Array|Object|String} value The value to inspect.
+ * @returns {Boolean} Returns `true` if the `value` is empty, else `false`.
+ * @example
+ *
+ * _.isEmpty([1, 2, 3]);
+ * // => false
+ *
+ * _.isEmpty({});
+ * // => true
+ *
+ * _.isEmpty('');
+ * // => true
+ */
+ function isEmpty(value) {
+ var result = true;
+ if (!value) {
+ return result;
+ }
+ var className = toString.call(value),
+ length = value.length;
+
+ if ((className == arrayClass || className == stringClass ||
+ className == argsClass || (noArgsClass && isArguments(value))) ||
+ (className == objectClass && typeof length == 'number' && isFunction(value.splice))) {
+ return !length;
+ }
+ forOwn(value, function() {
+ return (result = false);
+ });
+ return result;
+ }
+
+ /**
+ * Performs a deep comparison between two values to determine if they are
+ * equivalent to each other.
+ *
+ * @static
+ * @memberOf _
+ * @category Objects
+ * @param {Mixed} a The value to compare.
+ * @param {Mixed} b The other value to compare.
+ * @param- {Object} [stackA=[]] Internally used track traversed `a` objects.
+ * @param- {Object} [stackB=[]] Internally used track traversed `b` objects.
+ * @returns {Boolean} Returns `true` if the values are equvalent, else `false`.
+ * @example
+ *
+ * var moe = { 'name': 'moe', 'luckyNumbers': [13, 27, 34] };
+ * var clone = { 'name': 'moe', 'luckyNumbers': [13, 27, 34] };
+ *
+ * moe == clone;
+ * // => false
+ *
+ * _.isEqual(moe, clone);
+ * // => true
+ */
+ function isEqual(a, b, stackA, stackB) {
+ // exit early for identical values
+ if (a === b) {
+ // treat `+0` vs. `-0` as not equal
+ return a !== 0 || (1 / a == 1 / b);
+ }
+ // a strict comparison is necessary because `null == undefined`
+ if (a == null || b == null) {
+ return a === b;
+ }
+ // compare [[Class]] names
+ var className = toString.call(a),
+ otherName = toString.call(b);
+
+ if (className == argsClass) {
+ className = objectClass;
+ }
+ if (otherName == argsClass) {
+ otherName = objectClass;
+ }
+ if (className != otherName) {
+ return false;
+ }
+ switch (className) {
+ case boolClass:
+ case dateClass:
+ // coerce dates and booleans to numbers, dates to milliseconds and booleans
+ // to `1` or `0`, treating invalid dates coerced to `NaN` as not equal
+ return +a == +b;
+
+ case numberClass:
+ // treat `NaN` vs. `NaN` as equal
+ return a != +a
+ ? b != +b
+ // but treat `+0` vs. `-0` as not equal
+ : (a == 0 ? (1 / a == 1 / b) : a == +b);
+
+ case regexpClass:
+ case stringClass:
+ // coerce regexes to strings (http://es5.github.com/#x15.10.6.4)
+ // treat string primitives and their corresponding object instances as equal
+ return a == b + '';
+ }
+ var isArr = className == arrayClass;
+ if (!isArr) {
+ // unwrap any `lodash` wrapped values
+ if (a.__wrapped__ || b.__wrapped__) {
+ return isEqual(a.__wrapped__ || a, b.__wrapped__ || b);
+ }
+ // exit for functions and DOM nodes
+ if (className != objectClass || (noNodeClass && (isNode(a) || isNode(b)))) {
+ return false;
+ }
+ // in older versions of Opera, `arguments` objects have `Array` constructors
+ var ctorA = !argsAreObjects && isArguments(a) ? Object : a.constructor,
+ ctorB = !argsAreObjects && isArguments(b) ? Object : b.constructor;
+
+ // non `Object` object instances with different constructors are not equal
+ if (ctorA != ctorB && !(
+ isFunction(ctorA) && ctorA instanceof ctorA &&
+ isFunction(ctorB) && ctorB instanceof ctorB
+ )) {
+ return false;
+ }
+ }
+ // assume cyclic structures are equal
+ // the algorithm for detecting cyclic structures is adapted from ES 5.1
+ // section 15.12.3, abstract operation `JO` (http://es5.github.com/#x15.12.3)
+ stackA || (stackA = []);
+ stackB || (stackB = []);
+
+ var length = stackA.length;
+ while (length--) {
+ if (stackA[length] == a) {
+ return stackB[length] == b;
+ }
+ }
+ var index = -1,
+ result = true,
+ size = 0;
+
+ // add `a` and `b` to the stack of traversed objects
+ stackA.push(a);
+ stackB.push(b);
+
+ // recursively compare objects and arrays (susceptible to call stack limits)
+ if (isArr) {
+ // compare lengths to determine if a deep comparison is necessary
+ size = a.length;
+ result = size == b.length;
+
+ if (result) {
+ // deep compare the contents, ignoring non-numeric properties
+ while (size--) {
+ if (!(result = isEqual(a[size], b[size], stackA, stackB))) {
+ break;
+ }
+ }
+ }
+ return result;
+ }
+ // deep compare objects using `forIn`, instead of `forOwn`, to avoid `Object.keys`
+ // which, in this case, is more costly
+ forIn(a, function(value, key, a) {
+ if (hasOwnProperty.call(a, key)) {
+ // count the number of properties.
+ size++;
+ // deep compare each property value.
+ return (result = hasOwnProperty.call(b, key) && isEqual(value, b[key], stackA, stackB));
+ }
+ });
+
+ if (result) {
+ // ensure both objects have the same number of properties
+ forIn(b, function(value, key, b) {
+ if (hasOwnProperty.call(b, key)) {
+ // `size` will be `-1` if `b` has more properties than `a`
+ return (result = --size > -1);
+ }
+ });
+ }
+ return result;
+ }
+
+ /**
+ * Checks if `value` is, or can be coerced to, a finite number.
+ *
+ * Note: This is not the same as native `isFinite`, which will return true for
+ * booleans and empty strings. See http://es5.github.com/#x15.1.2.5.
+ *
+ * @static
+ * @memberOf _
+ * @category Objects
+ * @param {Mixed} value The value to check.
+ * @returns {Boolean} Returns `true` if the `value` is a finite number, else `false`.
+ * @example
+ *
+ * _.isFinite(-101);
+ * // => true
+ *
+ * _.isFinite('10');
+ * // => true
+ *
+ * _.isFinite(true);
+ * // => false
+ *
+ * _.isFinite('');
+ * // => false
+ *
+ * _.isFinite(Infinity);
+ * // => false
+ */
+ function isFinite(value) {
+ return nativeIsFinite(value) && !nativeIsNaN(parseFloat(value));
+ }
+
+ /**
+ * Checks if `value` is a function.
+ *
+ * @static
+ * @memberOf _
+ * @category Objects
+ * @param {Mixed} value The value to check.
+ * @returns {Boolean} Returns `true` if the `value` is a function, else `false`.
+ * @example
+ *
+ * _.isFunction(_);
+ * // => true
+ */
+ function isFunction(value) {
+ return typeof value == 'function';
+ }
+ // fallback for older versions of Chrome and Safari
+ if (isFunction(/x/)) {
+ isFunction = function(value) {
+ return value instanceof Function || toString.call(value) == funcClass;
+ };
+ }
+
+ /**
+ * Checks if `value` is the language type of Object.
+ * (e.g. arrays, functions, objects, regexes, `new Number(0)`, and `new String('')`)
+ *
+ * @static
+ * @memberOf _
+ * @category Objects
+ * @param {Mixed} value The value to check.
+ * @returns {Boolean} Returns `true` if the `value` is an object, else `false`.
+ * @example
+ *
+ * _.isObject({});
+ * // => true
+ *
+ * _.isObject([1, 2, 3]);
+ * // => true
+ *
+ * _.isObject(1);
+ * // => false
+ */
+ function isObject(value) {
+ // check if the value is the ECMAScript language type of Object
+ // http://es5.github.com/#x8
+ // and avoid a V8 bug
+ // http://code.google.com/p/v8/issues/detail?id=2291
+ return value ? objectTypes[typeof value] : false;
+ }
+
+ /**
+ * Checks if `value` is `NaN`.
+ *
+ * Note: This is not the same as native `isNaN`, which will return `true` for
+ * `undefined` and other values. See http://es5.github.com/#x15.1.2.4.
+ *
+ * @static
+ * @memberOf _
+ * @category Objects
+ * @param {Mixed} value The value to check.
+ * @returns {Boolean} Returns `true` if the `value` is `NaN`, else `false`.
+ * @example
+ *
+ * _.isNaN(NaN);
+ * // => true
+ *
+ * _.isNaN(new Number(NaN));
+ * // => true
+ *
+ * isNaN(undefined);
+ * // => true
+ *
+ * _.isNaN(undefined);
+ * // => false
+ */
+ function isNaN(value) {
+ // `NaN` as a primitive is the only value that is not equal to itself
+ // (perform the [[Class]] check first to avoid errors with some host objects in IE)
+ return isNumber(value) && value != +value
+ }
+
+ /**
+ * Checks if `value` is `null`.
+ *
+ * @static
+ * @memberOf _
+ * @category Objects
+ * @param {Mixed} value The value to check.
+ * @returns {Boolean} Returns `true` if the `value` is `null`, else `false`.
+ * @example
+ *
+ * _.isNull(null);
+ * // => true
+ *
+ * _.isNull(undefined);
+ * // => false
+ */
+ function isNull(value) {
+ return value === null;
+ }
+
+ /**
+ * Checks if `value` is a number.
+ *
+ * @static
+ * @memberOf _
+ * @category Objects
+ * @param {Mixed} value The value to check.
+ * @returns {Boolean} Returns `true` if the `value` is a number, else `false`.
+ * @example
+ *
+ * _.isNumber(8.4 * 5);
+ * // => true
+ */
+ function isNumber(value) {
+ return typeof value == 'number' || toString.call(value) == numberClass;
+ }
+
+ /**
+ * Checks if a given `value` is an object created by the `Object` constructor.
+ *
+ * @static
+ * @memberOf _
+ * @category Objects
+ * @param {Mixed} value The value to check.
+ * @returns {Boolean} Returns `true` if `value` is a plain object, else `false`.
+ * @example
+ *
+ * function Stooge(name, age) {
+ * this.name = name;
+ * this.age = age;
+ * }
+ *
+ * _.isPlainObject(new Stooge('moe', 40));
+ * // => false
+ *
+ * _.isPlainObject([1, 2, 3]);
+ * // => false
+ *
+ * _.isPlainObject({ 'name': 'moe', 'age': 40 });
+ * // => true
+ */
+ var isPlainObject = !getPrototypeOf ? shimIsPlainObject : function(value) {
+ if (!(value && typeof value == 'object')) {
+ return false;
+ }
+ var valueOf = value.valueOf,
+ objProto = typeof valueOf == 'function' && (objProto = getPrototypeOf(valueOf)) && getPrototypeOf(objProto);
+
+ return objProto
+ ? value == objProto || (getPrototypeOf(value) == objProto && !isArguments(value))
+ : shimIsPlainObject(value);
+ };
+
+ /**
+ * Checks if `value` is a regular expression.
+ *
+ * @static
+ * @memberOf _
+ * @category Objects
+ * @param {Mixed} value The value to check.
+ * @returns {Boolean} Returns `true` if the `value` is a regular expression, else `false`.
+ * @example
+ *
+ * _.isRegExp(/moe/);
+ * // => true
+ */
+ function isRegExp(value) {
+ return value instanceof RegExp || toString.call(value) == regexpClass;
+ }
+
+ /**
+ * Checks if `value` is a string.
+ *
+ * @static
+ * @memberOf _
+ * @category Objects
+ * @param {Mixed} value The value to check.
+ * @returns {Boolean} Returns `true` if the `value` is a string, else `false`.
+ * @example
+ *
+ * _.isString('moe');
+ * // => true
+ */
+ function isString(value) {
+ return typeof value == 'string' || toString.call(value) == stringClass;
+ }
+
+ /**
+ * Checks if `value` is `undefined`.
+ *
+ * @static
+ * @memberOf _
+ * @category Objects
+ * @param {Mixed} value The value to check.
+ * @returns {Boolean} Returns `true` if the `value` is `undefined`, else `false`.
+ * @example
+ *
+ * _.isUndefined(void 0);
+ * // => true
+ */
+ function isUndefined(value) {
+ return typeof value == 'undefined';
+ }
+
+ /**
+ * Creates an array composed of the own enumerable property names of `object`.
+ *
+ * @static
+ * @memberOf _
+ * @category Objects
+ * @param {Object} object The object to inspect.
+ * @returns {Array} Returns a new array of property names.
+ * @example
+ *
+ * _.keys({ 'one': 1, 'two': 2, 'three': 3 });
+ * // => ['one', 'two', 'three'] (order is not guaranteed)
+ */
+ var keys = !nativeKeys ? shimKeys : function(object) {
+ // avoid iterating over the `prototype` property
+ return typeof object == 'function' && propertyIsEnumerable.call(object, 'prototype')
+ ? shimKeys(object)
+ : (isObject(object) ? nativeKeys(object) : []);
+ };
+
+ /**
+ * Merges enumerable properties of the source object(s) into the `destination`
+ * object. Subsequent sources will overwrite propery assignments of previous
+ * sources.
+ *
+ * @static
+ * @memberOf _
+ * @category Objects
+ * @param {Object} object The destination object.
+ * @param {Object} [source1, source2, ...] The source objects.
+ * @param- {Object} [indicator] Internally used to indicate that the `stack`
+ * argument is an array of traversed objects instead of another source object.
+ * @param- {Array} [stackA=[]] Internally used to track traversed source objects.
+ * @param- {Array} [stackB=[]] Internally used to associate values with their
+ * source counterparts.
+ * @returns {Object} Returns the destination object.
+ * @example
+ *
+ * var stooges = [
+ * { 'name': 'moe' },
+ * { 'name': 'larry' }
+ * ];
+ *
+ * var ages = [
+ * { 'age': 40 },
+ * { 'age': 50 }
+ * ];
+ *
+ * _.merge(stooges, ages);
+ * // => [{ 'name': 'moe', 'age': 40 }, { 'name': 'larry', 'age': 50 }]
+ */
+ function merge(object, source, indicator) {
+ var args = arguments,
+ index = 0,
+ length = 2,
+ stackA = args[3],
+ stackB = args[4];
+
+ if (indicator !== indicatorObject) {
+ stackA = [];
+ stackB = [];
+
+ // work with `_.reduce` by only using its callback `accumulator` and `value` arguments
+ if (typeof indicator != 'number') {
+ length = args.length;
+ }
+ }
+ while (++index < length) {
+ forOwn(args[index], function(source, key) {
+ var found, isArr, value;
+ if (source && ((isArr = isArray(source)) || isPlainObject(source))) {
+ // avoid merging previously merged cyclic sources
+ var stackLength = stackA.length;
+ while (stackLength--) {
+ found = stackA[stackLength] == source;
+ if (found) {
+ break;
+ }
+ }
+ if (found) {
+ object[key] = stackB[stackLength];
+ }
+ else {
+ // add `source` and associated `value` to the stack of traversed objects
+ stackA.push(source);
+ stackB.push(value = (value = object[key], isArr)
+ ? (isArray(value) ? value : [])
+ : (isPlainObject(value) ? value : {})
+ );
+ // recursively merge objects and arrays (susceptible to call stack limits)
+ object[key] = merge(value, source, indicatorObject, stackA, stackB);
+ }
+ } else if (source != null) {
+ object[key] = source;
+ }
+ });
+ }
+ return object;
+ }
+
+ /**
+ * Creates a shallow clone of `object` excluding the specified properties.
+ * Property names may be specified as individual arguments or as arrays of
+ * property names. If `callback` is passed, it will be executed for each property
+ * in the `object`, omitting the properties `callback` returns truthy for. The
+ * `callback` is bound to `thisArg` and invoked with three arguments; (value, key, object).
+ *
+ * @static
+ * @memberOf _
+ * @category Objects
+ * @param {Object} object The source object.
+ * @param {Function|String} callback|[prop1, prop2, ...] The properties to omit
+ * or the function called per iteration.
+ * @param {Mixed} [thisArg] The `this` binding of `callback`.
+ * @returns {Object} Returns an object without the omitted properties.
+ * @example
+ *
+ * _.omit({ 'name': 'moe', 'age': 40, 'userid': 'moe1' }, 'userid');
+ * // => { 'name': 'moe', 'age': 40 }
+ *
+ * _.omit({ 'name': 'moe', '_hint': 'knucklehead', '_seed': '96c4eb' }, function(value, key) {
+ * return key.charAt(0) == '_';
+ * });
+ * // => { 'name': 'moe' }
+ */
+ function omit(object, callback, thisArg) {
+ var isFunc = typeof callback == 'function',
+ result = {};
+
+ if (isFunc) {
+ callback = createCallback(callback, thisArg);
+ } else {
+ var props = concat.apply(arrayRef, arguments);
+ }
+ forIn(object, function(value, key, object) {
+ if (isFunc
+ ? !callback(value, key, object)
+ : indexOf(props, key, 1) < 0
+ ) {
+ result[key] = value;
+ }
+ });
+ return result;
+ }
+
+ /**
+ * Creates a two dimensional array of the given object's key-value pairs,
+ * i.e. `[[key1, value1], [key2, value2]]`.
+ *
+ * @static
+ * @memberOf _
+ * @category Objects
+ * @param {Object} object The object to inspect.
+ * @returns {Array} Returns new array of key-value pairs.
+ * @example
+ *
+ * _.pairs({ 'moe': 30, 'larry': 40, 'curly': 50 });
+ * // => [['moe', 30], ['larry', 40], ['curly', 50]] (order is not guaranteed)
+ */
+ function pairs(object) {
+ var result = [];
+ forOwn(object, function(value, key) {
+ result.push([key, value]);
+ });
+ return result;
+ }
+
+ /**
+ * Creates a shallow clone of `object` composed of the specified properties.
+ * Property names may be specified as individual arguments or as arrays of
+ * property names. If `callback` is passed, it will be executed for each property
+ * in the `object`, picking the properties `callback` returns truthy for. The
+ * `callback` is bound to `thisArg` and invoked with three arguments; (value, key, object).
+ *
+ * @static
+ * @memberOf _
+ * @category Objects
+ * @param {Object} object The source object.
+ * @param {Function|String} callback|[prop1, prop2, ...] The properties to pick
+ * or the function called per iteration.
+ * @param {Mixed} [thisArg] The `this` binding of `callback`.
+ * @returns {Object} Returns an object composed of the picked properties.
+ * @example
+ *
+ * _.pick({ 'name': 'moe', 'age': 40, 'userid': 'moe1' }, 'name', 'age');
+ * // => { 'name': 'moe', 'age': 40 }
+ *
+ * _.pick({ 'name': 'moe', '_hint': 'knucklehead', '_seed': '96c4eb' }, function(value, key) {
+ * return key.charAt(0) != '_';
+ * });
+ * // => { 'name': 'moe' }
+ */
+ function pick(object, callback, thisArg) {
+ var result = {};
+ if (typeof callback != 'function') {
+ var index = 0,
+ props = concat.apply(arrayRef, arguments),
+ length = props.length;
+
+ while (++index < length) {
+ var key = props[index];
+ if (key in object) {
+ result[key] = object[key];
+ }
+ }
+ } else {
+ callback = createCallback(callback, thisArg);
+ forIn(object, function(value, key, object) {
+ if (callback(value, key, object)) {
+ result[key] = value;
+ }
+ });
+ }
+ return result;
+ }
+
+ /**
+ * Creates an array composed of the own enumerable property values of `object`.
+ *
+ * @static
+ * @memberOf _
+ * @category Objects
+ * @param {Object} object The object to inspect.
+ * @returns {Array} Returns a new array of property values.
+ * @example
+ *
+ * _.values({ 'one': 1, 'two': 2, 'three': 3 });
+ * // => [1, 2, 3]
+ */
+ function values(object) {
+ var result = [];
+ forOwn(object, function(value) {
+ result.push(value);
+ });
+ return result;
+ }
+
+ /*--------------------------------------------------------------------------*/
+
+ /**
+ * Checks if a given `target` element is present in a `collection` using strict
+ * equality for comparisons, i.e. `===`. If `fromIndex` is negative, it is used
+ * as the offset from the end of the collection.
+ *
+ * @static
+ * @memberOf _
+ * @alias include
+ * @category Collections
+ * @param {Array|Object|String} collection The collection to iterate over.
+ * @param {Mixed} target The value to check for.
+ * @param {Number} [fromIndex=0] The index to search from.
+ * @returns {Boolean} Returns `true` if the `target` element is found, else `false`.
+ * @example
+ *
+ * _.contains([1, 2, 3], 1);
+ * // => true
+ *
+ * _.contains([1, 2, 3], 1, 2);
+ * // => false
+ *
+ * _.contains({ 'name': 'moe', 'age': 40 }, 'moe');
+ * // => true
+ *
+ * _.contains('curly', 'ur');
+ * // => true
+ */
+ function contains(collection, target, fromIndex) {
+ var index = -1,
+ length = collection ? collection.length : 0,
+ result = false;
+
+ fromIndex = (fromIndex < 0 ? nativeMax(0, length + fromIndex) : fromIndex) || 0;
+ if (typeof length == 'number') {
+ result = (isString(collection)
+ ? collection.indexOf(target, fromIndex)
+ : indexOf(collection, target, fromIndex)
+ ) > -1;
+ } else {
+ each(collection, function(value) {
+ if (++index >= fromIndex) {
+ return !(result = value === target);
+ }
+ });
+ }
+ return result;
+ }
+
+ /**
+ * Creates an object composed of keys returned from running each element of
+ * `collection` through a `callback`. The corresponding value of each key is
+ * the number of times the key was returned by `callback`. The `callback` is
+ * bound to `thisArg` and invoked with three arguments; (value, index|key, collection).
+ * The `callback` argument may also be the name of a property to count by (e.g. 'length').
+ *
+ * @static
+ * @memberOf _
+ * @category Collections
+ * @param {Array|Object|String} collection The collection to iterate over.
+ * @param {Function|String} callback|property The function called per iteration
+ * or property name to count by.
+ * @param {Mixed} [thisArg] The `this` binding of `callback`.
+ * @returns {Object} Returns the composed aggregate object.
+ * @example
+ *
+ * _.countBy([4.3, 6.1, 6.4], function(num) { return Math.floor(num); });
+ * // => { '4': 1, '6': 2 }
+ *
+ * _.countBy([4.3, 6.1, 6.4], function(num) { return this.floor(num); }, Math);
+ * // => { '4': 1, '6': 2 }
+ *
+ * _.countBy(['one', 'two', 'three'], 'length');
+ * // => { '3': 2, '5': 1 }
+ */
+ function countBy(collection, callback, thisArg) {
+ var result = {};
+ callback = createCallback(callback, thisArg);
+
+ forEach(collection, function(value, key, collection) {
+ key = callback(value, key, collection);
+ (hasOwnProperty.call(result, key) ? result[key]++ : result[key] = 1);
+ });
+ return result;
+ }
+
+ /**
+ * Checks if the `callback` returns a truthy value for **all** elements of a
+ * `collection`. The `callback` is bound to `thisArg` and invoked with three
+ * arguments; (value, index|key, collection).
+ *
+ * @static
+ * @memberOf _
+ * @alias all
+ * @category Collections
+ * @param {Array|Object|String} collection The collection to iterate over.
+ * @param {Function} [callback=identity] The function called per iteration.
+ * @param {Mixed} [thisArg] The `this` binding of `callback`.
+ * @returns {Boolean} Returns `true` if all elements pass the callback check,
+ * else `false`.
+ * @example
+ *
+ * _.every([true, 1, null, 'yes'], Boolean);
+ * // => false
+ */
+ function every(collection, callback, thisArg) {
+ var result = true;
+ callback = createCallback(callback, thisArg);
+
+ if (isArray(collection)) {
+ var index = -1,
+ length = collection.length;
+
+ while (++index < length) {
+ if (!(result = !!callback(collection[index], index, collection))) {
+ break;
+ }
+ }
+ } else {
+ each(collection, function(value, index, collection) {
+ return (result = !!callback(value, index, collection));
+ });
+ }
+ return result;
+ }
+
+ /**
+ * Examines each element in a `collection`, returning an array of all elements
+ * the `callback` returns truthy for. The `callback` is bound to `thisArg` and
+ * invoked with three arguments; (value, index|key, collection).
+ *
+ * @static
+ * @memberOf _
+ * @alias select
+ * @category Collections
+ * @param {Array|Object|String} collection The collection to iterate over.
+ * @param {Function} [callback=identity] The function called per iteration.
+ * @param {Mixed} [thisArg] The `this` binding of `callback`.
+ * @returns {Array} Returns a new array of elements that passed the callback check.
+ * @example
+ *
+ * var evens = _.filter([1, 2, 3, 4, 5, 6], function(num) { return num % 2 == 0; });
+ * // => [2, 4, 6]
+ */
+ function filter(collection, callback, thisArg) {
+ var result = [];
+ callback = createCallback(callback, thisArg);
+
+ if (isArray(collection)) {
+ var index = -1,
+ length = collection.length;
+
+ while (++index < length) {
+ var value = collection[index];
+ if (callback(value, index, collection)) {
+ result.push(value);
+ }
+ }
+ } else {
+ each(collection, function(value, index, collection) {
+ if (callback(value, index, collection)) {
+ result.push(value);
+ }
+ });
+ }
+ return result;
+ }
+
+ /**
+ * Examines each element in a `collection`, returning the first one the `callback`
+ * returns truthy for. The function returns as soon as it finds an acceptable
+ * element, and does not iterate over the entire `collection`. The `callback` is
+ * bound to `thisArg` and invoked with three arguments; (value, index|key, collection).
+ *
+ * @static
+ * @memberOf _
+ * @alias detect
+ * @category Collections
+ * @param {Array|Object|String} collection The collection to iterate over.
+ * @param {Function} [callback=identity] The function called per iteration.
+ * @param {Mixed} [thisArg] The `this` binding of `callback`.
+ * @returns {Mixed} Returns the element that passed the callback check,
+ * else `undefined`.
+ * @example
+ *
+ * var even = _.find([1, 2, 3, 4, 5, 6], function(num) { return num % 2 == 0; });
+ * // => 2
+ */
+ function find(collection, callback, thisArg) {
+ var result;
+ callback = createCallback(callback, thisArg);
+
+ forEach(collection, function(value, index, collection) {
+ if (callback(value, index, collection)) {
+ result = value;
+ return false;
+ }
+ });
+ return result;
+ }
+
+ /**
+ * Iterates over a `collection`, executing the `callback` for each element in
+ * the `collection`. The `callback` is bound to `thisArg` and invoked with three
+ * arguments; (value, index|key, collection). Callbacks may exit iteration early
+ * by explicitly returning `false`.
+ *
+ * @static
+ * @memberOf _
+ * @alias each
+ * @category Collections
+ * @param {Array|Object|String} collection The collection to iterate over.
+ * @param {Function} [callback=identity] The function called per iteration.
+ * @param {Mixed} [thisArg] The `this` binding of `callback`.
+ * @returns {Array|Object|String} Returns `collection`.
+ * @example
+ *
+ * _([1, 2, 3]).forEach(alert).join(',');
+ * // => alerts each number and returns '1,2,3'
+ *
+ * _.forEach({ 'one': 1, 'two': 2, 'three': 3 }, alert);
+ * // => alerts each number value (order is not guaranteed)
+ */
+ function forEach(collection, callback, thisArg) {
+ if (callback && typeof thisArg == 'undefined' && isArray(collection)) {
+ var index = -1,
+ length = collection.length;
+
+ while (++index < length) {
+ if (callback(collection[index], index, collection) === false) {
+ break;
+ }
+ }
+ } else {
+ each(collection, callback, thisArg);
+ }
+ return collection;
+ }
+
+ /**
+ * Creates an object composed of keys returned from running each element of
+ * `collection` through a `callback`. The corresponding value of each key is an
+ * array of elements passed to `callback` that returned the key. The `callback`
+ * is bound to `thisArg` and invoked with three arguments; (value, index|key, collection).
+ * The `callback` argument may also be the name of a property to group by (e.g. 'length').
+ *
+ * @static
+ * @memberOf _
+ * @category Collections
+ * @param {Array|Object|String} collection The collection to iterate over.
+ * @param {Function|String} callback|property The function called per iteration
+ * or property name to group by.
+ * @param {Mixed} [thisArg] The `this` binding of `callback`.
+ * @returns {Object} Returns the composed aggregate object.
+ * @example
+ *
+ * _.groupBy([4.2, 6.1, 6.4], function(num) { return Math.floor(num); });
+ * // => { '4': [4.2], '6': [6.1, 6.4] }
+ *
+ * _.groupBy([4.2, 6.1, 6.4], function(num) { return this.floor(num); }, Math);
+ * // => { '4': [4.2], '6': [6.1, 6.4] }
+ *
+ * _.groupBy(['one', 'two', 'three'], 'length');
+ * // => { '3': ['one', 'two'], '5': ['three'] }
+ */
+ function groupBy(collection, callback, thisArg) {
+ var result = {};
+ callback = createCallback(callback, thisArg);
+
+ forEach(collection, function(value, key, collection) {
+ key = callback(value, key, collection);
+ (hasOwnProperty.call(result, key) ? result[key] : result[key] = []).push(value);
+ });
+ return result;
+ }
+
+ /**
+ * Invokes the method named by `methodName` on each element in the `collection`,
+ * returning an array of the results of each invoked method. Additional arguments
+ * will be passed to each invoked method. If `methodName` is a function it will
+ * be invoked for, and `this` bound to, each element in the `collection`.
+ *
+ * @static
+ * @memberOf _
+ * @category Collections
+ * @param {Array|Object|String} collection The collection to iterate over.
+ * @param {Function|String} methodName The name of the method to invoke or
+ * the function invoked per iteration.
+ * @param {Mixed} [arg1, arg2, ...] Arguments to invoke the method with.
+ * @returns {Array} Returns a new array of the results of each invoked method.
+ * @example
+ *
+ * _.invoke([[5, 1, 7], [3, 2, 1]], 'sort');
+ * // => [[1, 5, 7], [1, 2, 3]]
+ *
+ * _.invoke([123, 456], String.prototype.split, '');
+ * // => [['1', '2', '3'], ['4', '5', '6']]
+ */
+ function invoke(collection, methodName) {
+ var args = slice(arguments, 2),
+ isFunc = typeof methodName == 'function',
+ result = [];
+
+ forEach(collection, function(value) {
+ result.push((isFunc ? methodName : value[methodName]).apply(value, args));
+ });
+ return result;
+ }
+
+ /**
+ * Creates an array of values by running each element in the `collection`
+ * through a `callback`. The `callback` is bound to `thisArg` and invoked with
+ * three arguments; (value, index|key, collection).
+ *
+ * @static
+ * @memberOf _
+ * @alias collect
+ * @category Collections
+ * @param {Array|Object|String} collection The collection to iterate over.
+ * @param {Function} [callback=identity] The function called per iteration.
+ * @param {Mixed} [thisArg] The `this` binding of `callback`.
+ * @returns {Array} Returns a new array of the results of each `callback` execution.
+ * @example
+ *
+ * _.map([1, 2, 3], function(num) { return num * 3; });
+ * // => [3, 6, 9]
+ *
+ * _.map({ 'one': 1, 'two': 2, 'three': 3 }, function(num) { return num * 3; });
+ * // => [3, 6, 9] (order is not guaranteed)
+ */
+ function map(collection, callback, thisArg) {
+ var index = -1,
+ length = collection ? collection.length : 0,
+ result = Array(typeof length == 'number' ? length : 0);
+
+ callback = createCallback(callback, thisArg);
+ if (isArray(collection)) {
+ while (++index < length) {
+ result[index] = callback(collection[index], index, collection);
+ }
+ } else {
+ each(collection, function(value, key, collection) {
+ result[++index] = callback(value, key, collection);
+ });
+ }
+ return result;
+ }
+
+ /**
+ * Retrieves the maximum value of an `array`. If `callback` is passed,
+ * it will be executed for each value in the `array` to generate the
+ * criterion by which the value is ranked. The `callback` is bound to
+ * `thisArg` and invoked with three arguments; (value, index, collection).
+ *
+ * @static
+ * @memberOf _
+ * @category Collections
+ * @param {Array|Object|String} collection The collection to iterate over.
+ * @param {Function} [callback] The function called per iteration.
+ * @param {Mixed} [thisArg] The `this` binding of `callback`.
+ * @returns {Mixed} Returns the maximum value.
+ * @example
+ *
+ * var stooges = [
+ * { 'name': 'moe', 'age': 40 },
+ * { 'name': 'larry', 'age': 50 },
+ * { 'name': 'curly', 'age': 60 }
+ * ];
+ *
+ * _.max(stooges, function(stooge) { return stooge.age; });
+ * // => { 'name': 'curly', 'age': 60 };
+ */
+ function max(collection, callback, thisArg) {
+ var computed = -Infinity,
+ index = -1,
+ length = collection ? collection.length : 0,
+ result = computed;
+
+ if (callback || !isArray(collection)) {
+ callback = !callback && isString(collection)
+ ? charAtCallback
+ : createCallback(callback, thisArg);
+
+ each(collection, function(value, index, collection) {
+ var current = callback(value, index, collection);
+ if (current > computed) {
+ computed = current;
+ result = value;
+ }
+ });
+ } else {
+ while (++index < length) {
+ if (collection[index] > result) {
+ result = collection[index];
+ }
+ }
+ }
+ return result;
+ }
+
+ /**
+ * Retrieves the minimum value of an `array`. If `callback` is passed,
+ * it will be executed for each value in the `array` to generate the
+ * criterion by which the value is ranked. The `callback` is bound to `thisArg`
+ * and invoked with three arguments; (value, index, collection).
+ *
+ * @static
+ * @memberOf _
+ * @category Collections
+ * @param {Array|Object|String} collection The collection to iterate over.
+ * @param {Function} [callback] The function called per iteration.
+ * @param {Mixed} [thisArg] The `this` binding of `callback`.
+ * @returns {Mixed} Returns the minimum value.
+ * @example
+ *
+ * _.min([10, 5, 100, 2, 1000]);
+ * // => 2
+ */
+ function min(collection, callback, thisArg) {
+ var computed = Infinity,
+ index = -1,
+ length = collection ? collection.length : 0,
+ result = computed;
+
+ if (callback || !isArray(collection)) {
+ callback = !callback && isString(collection)
+ ? charAtCallback
+ : createCallback(callback, thisArg);
+
+ each(collection, function(value, index, collection) {
+ var current = callback(value, index, collection);
+ if (current < computed) {
+ computed = current;
+ result = value;
+ }
+ });
+ } else {
+ while (++index < length) {
+ if (collection[index] < result) {
+ result = collection[index];
+ }
+ }
+ }
+ return result;
+ }
+
+ /**
+ * Retrieves the value of a specified property from all elements in
+ * the `collection`.
+ *
+ * @static
+ * @memberOf _
+ * @category Collections
+ * @param {Array|Object|String} collection The collection to iterate over.
+ * @param {String} property The property to pluck.
+ * @returns {Array} Returns a new array of property values.
+ * @example
+ *
+ * var stooges = [
+ * { 'name': 'moe', 'age': 40 },
+ * { 'name': 'larry', 'age': 50 },
+ * { 'name': 'curly', 'age': 60 }
+ * ];
+ *
+ * _.pluck(stooges, 'name');
+ * // => ['moe', 'larry', 'curly']
+ */
+ function pluck(collection, property) {
+ return map(collection, property + '');
+ }
+
+ /**
+ * Boils down a `collection` to a single value. The initial state of the
+ * reduction is `accumulator` and each successive step of it should be returned
+ * by the `callback`. The `callback` is bound to `thisArg` and invoked with 4
+ * arguments; for arrays they are (accumulator, value, index|key, collection).
+ *
+ * @static
+ * @memberOf _
+ * @alias foldl, inject
+ * @category Collections
+ * @param {Array|Object|String} collection The collection to iterate over.
+ * @param {Function} [callback=identity] The function called per iteration.
+ * @param {Mixed} [accumulator] Initial value of the accumulator.
+ * @param {Mixed} [thisArg] The `this` binding of `callback`.
+ * @returns {Mixed} Returns the accumulated value.
+ * @example
+ *
+ * var sum = _.reduce([1, 2, 3], function(memo, num) { return memo + num; });
+ * // => 6
+ */
+ function reduce(collection, callback, accumulator, thisArg) {
+ var noaccum = arguments.length < 3;
+ callback = createCallback(callback, thisArg, indicatorObject);
+
+ if (isArray(collection)) {
+ var index = -1,
+ length = collection.length;
+
+ if (noaccum) {
+ accumulator = collection[++index];
+ }
+ while (++index < length) {
+ accumulator = callback(accumulator, collection[index], index, collection);
+ }
+ } else {
+ each(collection, function(value, index, collection) {
+ accumulator = noaccum
+ ? (noaccum = false, value)
+ : callback(accumulator, value, index, collection)
+ });
+ }
+ return accumulator;
+ }
+
+ /**
+ * The right-associative version of `_.reduce`.
+ *
+ * @static
+ * @memberOf _
+ * @alias foldr
+ * @category Collections
+ * @param {Array|Object|String} collection The collection to iterate over.
+ * @param {Function} [callback=identity] The function called per iteration.
+ * @param {Mixed} [accumulator] Initial value of the accumulator.
+ * @param {Mixed} [thisArg] The `this` binding of `callback`.
+ * @returns {Mixed} Returns the accumulated value.
+ * @example
+ *
+ * var list = [[0, 1], [2, 3], [4, 5]];
+ * var flat = _.reduceRight(list, function(a, b) { return a.concat(b); }, []);
+ * // => [4, 5, 2, 3, 0, 1]
+ */
+ function reduceRight(collection, callback, accumulator, thisArg) {
+ var iteratee = collection,
+ length = collection ? collection.length : 0,
+ noaccum = arguments.length < 3;
+
+ if (typeof length != 'number') {
+ var props = keys(collection);
+ length = props.length;
+ } else if (noCharByIndex && isString(collection)) {
+ iteratee = collection.split('');
+ }
+ callback = createCallback(callback, thisArg, indicatorObject);
+ forEach(collection, function(value, index, collection) {
+ index = props ? props[--length] : --length;
+ accumulator = noaccum
+ ? (noaccum = false, iteratee[index])
+ : callback(accumulator, iteratee[index], index, collection);
+ });
+ return accumulator;
+ }
+
+ /**
+ * The opposite of `_.filter`, this method returns the values of a
+ * `collection` that `callback` does **not** return truthy for.
+ *
+ * @static
+ * @memberOf _
+ * @category Collections
+ * @param {Array|Object|String} collection The collection to iterate over.
+ * @param {Function} [callback=identity] The function called per iteration.
+ * @param {Mixed} [thisArg] The `this` binding of `callback`.
+ * @returns {Array} Returns a new array of elements that did **not** pass the
+ * callback check.
+ * @example
+ *
+ * var odds = _.reject([1, 2, 3, 4, 5, 6], function(num) { return num % 2 == 0; });
+ * // => [1, 3, 5]
+ */
+ function reject(collection, callback, thisArg) {
+ callback = createCallback(callback, thisArg);
+ return filter(collection, function(value, index, collection) {
+ return !callback(value, index, collection);
+ });
+ }
+
+ /**
+ * Creates an array of shuffled `array` values, using a version of the
+ * Fisher-Yates shuffle. See http://en.wikipedia.org/wiki/Fisher-Yates_shuffle.
+ *
+ * @static
+ * @memberOf _
+ * @category Collections
+ * @param {Array|Object|String} collection The collection to shuffle.
+ * @returns {Array} Returns a new shuffled collection.
+ * @example
+ *
+ * _.shuffle([1, 2, 3, 4, 5, 6]);
+ * // => [4, 1, 6, 3, 5, 2]
+ */
+ function shuffle(collection) {
+ var index = -1,
+ result = Array(collection ? collection.length : 0);
+
+ forEach(collection, function(value) {
+ var rand = floor(nativeRandom() * (++index + 1));
+ result[index] = result[rand];
+ result[rand] = value;
+ });
+ return result;
+ }
+
+ /**
+ * Gets the size of the `collection` by returning `collection.length` for arrays
+ * and array-like objects or the number of own enumerable properties for objects.
+ *
+ * @static
+ * @memberOf _
+ * @category Collections
+ * @param {Array|Object|String} collection The collection to inspect.
+ * @returns {Number} Returns `collection.length` or number of own enumerable properties.
+ * @example
+ *
+ * _.size([1, 2]);
+ * // => 2
+ *
+ * _.size({ 'one': 1, 'two': 2, 'three': 3 });
+ * // => 3
+ *
+ * _.size('curly');
+ * // => 5
+ */
+ function size(collection) {
+ var length = collection ? collection.length : 0;
+ return typeof length == 'number' ? length : keys(collection).length;
+ }
+
+ /**
+ * Checks if the `callback` returns a truthy value for **any** element of a
+ * `collection`. The function returns as soon as it finds passing value, and
+ * does not iterate over the entire `collection`. The `callback` is bound to
+ * `thisArg` and invoked with three arguments; (value, index|key, collection).
+ *
+ * @static
+ * @memberOf _
+ * @alias any
+ * @category Collections
+ * @param {Array|Object|String} collection The collection to iterate over.
+ * @param {Function} [callback=identity] The function called per iteration.
+ * @param {Mixed} [thisArg] The `this` binding of `callback`.
+ * @returns {Boolean} Returns `true` if any element passes the callback check,
+ * else `false`.
+ * @example
+ *
+ * _.some([null, 0, 'yes', false], Boolean);
+ * // => true
+ */
+ function some(collection, callback, thisArg) {
+ var result;
+ callback = createCallback(callback, thisArg);
+
+ if (isArray(collection)) {
+ var index = -1,
+ length = collection.length;
+
+ while (++index < length) {
+ if ((result = callback(collection[index], index, collection))) {
+ break;
+ }
+ }
+ } else {
+ each(collection, function(value, index, collection) {
+ return !(result = callback(value, index, collection));
+ });
+ }
+ return !!result;
+ }
+
+ /**
+ * Creates an array, stable sorted in ascending order by the results of
+ * running each element of `collection` through a `callback`. The `callback`
+ * is bound to `thisArg` and invoked with three arguments; (value, index|key, collection).
+ * The `callback` argument may also be the name of a property to sort by (e.g. 'length').
+ *
+ * @static
+ * @memberOf _
+ * @category Collections
+ * @param {Array|Object|String} collection The collection to iterate over.
+ * @param {Function|String} callback|property The function called per iteration
+ * or property name to sort by.
+ * @param {Mixed} [thisArg] The `this` binding of `callback`.
+ * @returns {Array} Returns a new array of sorted elements.
+ * @example
+ *
+ * _.sortBy([1, 2, 3], function(num) { return Math.sin(num); });
+ * // => [3, 1, 2]
+ *
+ * _.sortBy([1, 2, 3], function(num) { return this.sin(num); }, Math);
+ * // => [3, 1, 2]
+ *
+ * _.sortBy(['larry', 'brendan', 'moe'], 'length');
+ * // => ['moe', 'larry', 'brendan']
+ */
+ function sortBy(collection, callback, thisArg) {
+ var result = [];
+ callback = createCallback(callback, thisArg);
+
+ forEach(collection, function(value, index, collection) {
+ result.push({
+ 'criteria': callback(value, index, collection),
+ 'index': index,
+ 'value': value
+ });
+ });
+
+ var length = result.length;
+ result.sort(compareAscending);
+ while (length--) {
+ result[length] = result[length].value;
+ }
+ return result;
+ }
+
+ /**
+ * Converts the `collection` to an array.
+ *
+ * @static
+ * @memberOf _
+ * @category Collections
+ * @param {Array|Object|String} collection The collection to convert.
+ * @returns {Array} Returns the new converted array.
+ * @example
+ *
+ * (function() { return _.toArray(arguments).slice(1); })(1, 2, 3, 4);
+ * // => [2, 3, 4]
+ */
+ function toArray(collection) {
+ var length = collection ? collection.length : 0;
+ if (typeof length == 'number') {
+ return noCharByIndex && isString(collection)
+ ? collection.split('')
+ : slice(collection);
+ }
+ return values(collection);
+ }
+
+ /**
+ * Examines each element in a `collection`, returning an array of all elements
+ * that contain the given `properties`.
+ *
+ * @static
+ * @memberOf _
+ * @category Collections
+ * @param {Array|Object|String} collection The collection to iterate over.
+ * @param {Object} properties The object of property values to filter by.
+ * @returns {Array} Returns a new array of elements that contain the given `properties`.
+ * @example
+ *
+ * var stooges = [
+ * { 'name': 'moe', 'age': 40 },
+ * { 'name': 'larry', 'age': 50 },
+ * { 'name': 'curly', 'age': 60 }
+ * ];
+ *
+ * _.where(stooges, { 'age': 40 });
+ * // => [{ 'name': 'moe', 'age': 40 }]
+ */
+ function where(collection, properties) {
+ var props = keys(properties);
+ return filter(collection, function(object) {
+ var length = props.length;
+ while (length--) {
+ var result = object[props[length]] === properties[props[length]];
+ if (!result) {
+ break;
+ }
+ }
+ return !!result;
+ });
+ }
+
+ /*--------------------------------------------------------------------------*/
+
+ /**
+ * Creates an array with all falsey values of `array` removed. The values
+ * `false`, `null`, `0`, `""`, `undefined` and `NaN` are all falsey.
+ *
+ * @static
+ * @memberOf _
+ * @category Arrays
+ * @param {Array} array The array to compact.
+ * @returns {Array} Returns a new filtered array.
+ * @example
+ *
+ * _.compact([0, 1, false, 2, '', 3]);
+ * // => [1, 2, 3]
+ */
+ function compact(array) {
+ var index = -1,
+ length = array ? array.length : 0,
+ result = [];
+
+ while (++index < length) {
+ var value = array[index];
+ if (value) {
+ result.push(value);
+ }
+ }
+ return result;
+ }
+
+ /**
+ * Creates an array of `array` elements not present in the other arrays
+ * using strict equality for comparisons, i.e. `===`.
+ *
+ * @static
+ * @memberOf _
+ * @category Arrays
+ * @param {Array} array The array to process.
+ * @param {Array} [array1, array2, ...] Arrays to check.
+ * @returns {Array} Returns a new array of `array` elements not present in the
+ * other arrays.
+ * @example
+ *
+ * _.difference([1, 2, 3, 4, 5], [5, 2, 10]);
+ * // => [1, 3, 4]
+ */
+ function difference(array) {
+ var index = -1,
+ length = array ? array.length : 0,
+ flattened = concat.apply(arrayRef, arguments),
+ contains = cachedContains(flattened, length),
+ result = [];
+
+ while (++index < length) {
+ var value = array[index];
+ if (!contains(value)) {
+ result.push(value);
+ }
+ }
+ return result;
+ }
+
+ /**
+ * Gets the first element of the `array`. Pass `n` to return the first `n`
+ * elements of the `array`.
+ *
+ * @static
+ * @memberOf _
+ * @alias head, take
+ * @category Arrays
+ * @param {Array} array The array to query.
+ * @param {Number} [n] The number of elements to return.
+ * @param- {Object} [guard] Internally used to allow this method to work with
+ * others like `_.map` without using their callback `index` argument for `n`.
+ * @returns {Mixed} Returns the first element, or an array of the first `n`
+ * elements, of `array`.
+ * @example
+ *
+ * _.first([5, 4, 3, 2, 1]);
+ * // => 5
+ */
+ function first(array, n, guard) {
+ if (array) {
+ var length = array.length;
+ return (n == null || guard)
+ ? array[0]
+ : slice(array, 0, nativeMin(nativeMax(0, n), length));
+ }
+ }
+
+ /**
+ * Flattens a nested array (the nesting can be to any depth). If `shallow` is
+ * truthy, `array` will only be flattened a single level.
+ *
+ * @static
+ * @memberOf _
+ * @category Arrays
+ * @param {Array} array The array to compact.
+ * @param {Boolean} shallow A flag to indicate only flattening a single level.
+ * @returns {Array} Returns a new flattened array.
+ * @example
+ *
+ * _.flatten([1, [2], [3, [[4]]]]);
+ * // => [1, 2, 3, 4];
+ *
+ * _.flatten([1, [2], [3, [[4]]]], true);
+ * // => [1, 2, 3, [[4]]];
+ */
+ function flatten(array, shallow) {
+ var index = -1,
+ length = array ? array.length : 0,
+ result = [];
+
+ while (++index < length) {
+ var value = array[index];
+
+ // recursively flatten arrays (susceptible to call stack limits)
+ if (isArray(value)) {
+ push.apply(result, shallow ? value : flatten(value));
+ } else {
+ result.push(value);
+ }
+ }
+ return result;
+ }
+
+ /**
+ * Gets the index at which the first occurrence of `value` is found using
+ * strict equality for comparisons, i.e. `===`. If the `array` is already
+ * sorted, passing `true` for `fromIndex` will run a faster binary search.
+ *
+ * @static
+ * @memberOf _
+ * @category Arrays
+ * @param {Array} array The array to search.
+ * @param {Mixed} value The value to search for.
+ * @param {Boolean|Number} [fromIndex=0] The index to search from or `true` to
+ * perform a binary search on a sorted `array`.
+ * @returns {Number} Returns the index of the matched value or `-1`.
+ * @example
+ *
+ * _.indexOf([1, 2, 3, 1, 2, 3], 2);
+ * // => 1
+ *
+ * _.indexOf([1, 2, 3, 1, 2, 3], 2, 3);
+ * // => 4
+ *
+ * _.indexOf([1, 1, 2, 2, 3, 3], 2, true);
+ * // => 2
+ */
+ function indexOf(array, value, fromIndex) {
+ var index = -1,
+ length = array ? array.length : 0;
+
+ if (typeof fromIndex == 'number') {
+ index = (fromIndex < 0 ? nativeMax(0, length + fromIndex) : fromIndex || 0) - 1;
+ } else if (fromIndex) {
+ index = sortedIndex(array, value);
+ return array[index] === value ? index : -1;
+ }
+ while (++index < length) {
+ if (array[index] === value) {
+ return index;
+ }
+ }
+ return -1;
+ }
+
+ /**
+ * Gets all but the last element of `array`. Pass `n` to exclude the last `n`
+ * elements from the result.
+ *
+ * @static
+ * @memberOf _
+ * @category Arrays
+ * @param {Array} array The array to query.
+ * @param {Number} [n=1] The number of elements to exclude.
+ * @param- {Object} [guard] Internally used to allow this method to work with
+ * others like `_.map` without using their callback `index` argument for `n`.
+ * @returns {Array} Returns all but the last element, or `n` elements, of `array`.
+ * @example
+ *
+ * _.initial([3, 2, 1]);
+ * // => [3, 2]
+ */
+ function initial(array, n, guard) {
+ if (!array) {
+ return [];
+ }
+ var length = array.length;
+ n = n == null || guard ? 1 : n || 0;
+ return slice(array, 0, nativeMin(nativeMax(0, length - n), length));
+ }
+
+ /**
+ * Computes the intersection of all the passed-in arrays using strict equality
+ * for comparisons, i.e. `===`.
+ *
+ * @static
+ * @memberOf _
+ * @category Arrays
+ * @param {Array} [array1, array2, ...] Arrays to process.
+ * @returns {Array} Returns a new array of unique elements that are present
+ * in **all** of the arrays.
+ * @example
+ *
+ * _.intersection([1, 2, 3], [101, 2, 1, 10], [2, 1]);
+ * // => [1, 2]
+ */
+ function intersection(array) {
+ var args = arguments,
+ argsLength = args.length,
+ cache = { '0': {} },
+ index = -1,
+ length = array ? array.length : 0,
+ isLarge = length >= 100,
+ result = [],
+ seen = result;
+
+ outer:
+ while (++index < length) {
+ var value = array[index];
+ if (isLarge) {
+ var key = value + '';
+ var inited = hasOwnProperty.call(cache[0], key)
+ ? !(seen = cache[0][key])
+ : (seen = cache[0][key] = []);
+ }
+ if (inited || indexOf(seen, value) < 0) {
+ if (isLarge) {
+ seen.push(value);
+ }
+ var argsIndex = argsLength;
+ while (--argsIndex) {
+ if (!(cache[argsIndex] || (cache[argsIndex] = cachedContains(args[argsIndex], 0, 100)))(value)) {
+ continue outer;
+ }
+ }
+ result.push(value);
+ }
+ }
+ return result;
+ }
+
+ /**
+ * Gets the last element of the `array`. Pass `n` to return the last `n`
+ * elements of the `array`.
+ *
+ * @static
+ * @memberOf _
+ * @category Arrays
+ * @param {Array} array The array to query.
+ * @param {Number} [n] The number of elements to return.
+ * @param- {Object} [guard] Internally used to allow this method to work with
+ * others like `_.map` without using their callback `index` argument for `n`.
+ * @returns {Mixed} Returns the last element, or an array of the last `n`
+ * elements, of `array`.
+ * @example
+ *
+ * _.last([3, 2, 1]);
+ * // => 1
+ */
+ function last(array, n, guard) {
+ if (array) {
+ var length = array.length;
+ return (n == null || guard) ? array[length - 1] : slice(array, nativeMax(0, length - n));
+ }
+ }
+
+ /**
+ * Gets the index at which the last occurrence of `value` is found using strict
+ * equality for comparisons, i.e. `===`. If `fromIndex` is negative, it is used
+ * as the offset from the end of the collection.
+ *
+ * @static
+ * @memberOf _
+ * @category Arrays
+ * @param {Array} array The array to search.
+ * @param {Mixed} value The value to search for.
+ * @param {Number} [fromIndex=array.length-1] The index to search from.
+ * @returns {Number} Returns the index of the matched value or `-1`.
+ * @example
+ *
+ * _.lastIndexOf([1, 2, 3, 1, 2, 3], 2);
+ * // => 4
+ *
+ * _.lastIndexOf([1, 2, 3, 1, 2, 3], 2, 3);
+ * // => 1
+ */
+ function lastIndexOf(array, value, fromIndex) {
+ var index = array ? array.length : 0;
+ if (typeof fromIndex == 'number') {
+ index = (fromIndex < 0 ? nativeMax(0, index + fromIndex) : nativeMin(fromIndex, index - 1)) + 1;
+ }
+ while (index--) {
+ if (array[index] === value) {
+ return index;
+ }
+ }
+ return -1;
+ }
+
+ /**
+ * Creates an object composed from arrays of `keys` and `values`. Pass either
+ * a single two dimensional array, i.e. `[[key1, value1], [key2, value2]]`, or
+ * two arrays, one of `keys` and one of corresponding `values`.
+ *
+ * @static
+ * @memberOf _
+ * @category Arrays
+ * @param {Array} keys The array of keys.
+ * @param {Array} [values=[]] The array of values.
+ * @returns {Object} Returns an object composed of the given keys and
+ * corresponding values.
+ * @example
+ *
+ * _.object(['moe', 'larry', 'curly'], [30, 40, 50]);
+ * // => { 'moe': 30, 'larry': 40, 'curly': 50 }
+ */
+ function object(keys, values) {
+ var index = -1,
+ length = keys ? keys.length : 0,
+ result = {};
+
+ while (++index < length) {
+ var key = keys[index];
+ if (values) {
+ result[key] = values[index];
+ } else {
+ result[key[0]] = key[1];
+ }
+ }
+ return result;
+ }
+
+ /**
+ * Creates an array of numbers (positive and/or negative) progressing from
+ * `start` up to but not including `stop`. This method is a port of Python's
+ * `range()` function. See http://docs.python.org/library/functions.html#range.
+ *
+ * @static
+ * @memberOf _
+ * @category Arrays
+ * @param {Number} [start=0] The start of the range.
+ * @param {Number} end The end of the range.
+ * @param {Number} [step=1] The value to increment or descrement by.
+ * @returns {Array} Returns a new range array.
+ * @example
+ *
+ * _.range(10);
+ * // => [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
+ *
+ * _.range(1, 11);
+ * // => [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
+ *
+ * _.range(0, 30, 5);
+ * // => [0, 5, 10, 15, 20, 25]
+ *
+ * _.range(0, -10, -1);
+ * // => [0, -1, -2, -3, -4, -5, -6, -7, -8, -9]
+ *
+ * _.range(0);
+ * // => []
+ */
+ function range(start, end, step) {
+ start = +start || 0;
+ step = +step || 1;
+
+ if (end == null) {
+ end = start;
+ start = 0;
+ }
+ // use `Array(length)` so V8 will avoid the slower "dictionary" mode
+ // http://youtu.be/XAqIpGU8ZZk#t=17m25s
+ var index = -1,
+ length = nativeMax(0, ceil((end - start) / step)),
+ result = Array(length);
+
+ while (++index < length) {
+ result[index] = start;
+ start += step;
+ }
+ return result;
+ }
+
+ /**
+ * The opposite of `_.initial`, this method gets all but the first value of
+ * `array`. Pass `n` to exclude the first `n` values from the result.
+ *
+ * @static
+ * @memberOf _
+ * @alias drop, tail
+ * @category Arrays
+ * @param {Array} array The array to query.
+ * @param {Number} [n=1] The number of elements to exclude.
+ * @param- {Object} [guard] Internally used to allow this method to work with
+ * others like `_.map` without using their callback `index` argument for `n`.
+ * @returns {Array} Returns all but the first element, or `n` elements, of `array`.
+ * @example
+ *
+ * _.rest([3, 2, 1]);
+ * // => [2, 1]
+ */
+ function rest(array, n, guard) {
+ return slice(array, (n == null || guard) ? 1 : nativeMax(0, n));
+ }
+
+ /**
+ * Uses a binary search to determine the smallest index at which the `value`
+ * should be inserted into `array` in order to maintain the sort order of the
+ * sorted `array`. If `callback` is passed, it will be executed for `value` and
+ * each element in `array` to compute their sort ranking. The `callback` is
+ * bound to `thisArg` and invoked with one argument; (value). The `callback`
+ * argument may also be the name of a property to order by.
+ *
+ * @static
+ * @memberOf _
+ * @category Arrays
+ * @param {Array} array The array to iterate over.
+ * @param {Mixed} value The value to evaluate.
+ * @param {Function|String} [callback=identity|property] The function called
+ * per iteration or property name to order by.
+ * @param {Mixed} [thisArg] The `this` binding of `callback`.
+ * @returns {Number} Returns the index at which the value should be inserted
+ * into `array`.
+ * @example
+ *
+ * _.sortedIndex([20, 30, 50], 40);
+ * // => 2
+ *
+ * _.sortedIndex([{ 'x': 20 }, { 'x': 30 }, { 'x': 50 }], { 'x': 40 }, 'x');
+ * // => 2
+ *
+ * var dict = {
+ * 'wordToNumber': { 'twenty': 20, 'thirty': 30, 'fourty': 40, 'fifty': 50 }
+ * };
+ *
+ * _.sortedIndex(['twenty', 'thirty', 'fifty'], 'fourty', function(word) {
+ * return dict.wordToNumber[word];
+ * });
+ * // => 2
+ *
+ * _.sortedIndex(['twenty', 'thirty', 'fifty'], 'fourty', function(word) {
+ * return this.wordToNumber[word];
+ * }, dict);
+ * // => 2
+ */
+ function sortedIndex(array, value, callback, thisArg) {
+ var low = 0,
+ high = array ? array.length : low;
+
+ // explicitly reference `identity` for better inlining in Firefox
+ callback = callback ? createCallback(callback, thisArg) : identity;
+ value = callback(value);
+
+ while (low < high) {
+ var mid = (low + high) >>> 1;
+ callback(array[mid]) < value
+ ? low = mid + 1
+ : high = mid;
+ }
+ return low;
+ }
+
+ /**
+ * Computes the union of the passed-in arrays using strict equality for
+ * comparisons, i.e. `===`.
+ *
+ * @static
+ * @memberOf _
+ * @category Arrays
+ * @param {Array} [array1, array2, ...] Arrays to process.
+ * @returns {Array} Returns a new array of unique values, in order, that are
+ * present in one or more of the arrays.
+ * @example
+ *
+ * _.union([1, 2, 3], [101, 2, 1, 10], [2, 1]);
+ * // => [1, 2, 3, 101, 10]
+ */
+ function union() {
+ return uniq(concat.apply(arrayRef, arguments));
+ }
+
+ /**
+ * Creates a duplicate-value-free version of the `array` using strict equality
+ * for comparisons, i.e. `===`. If the `array` is already sorted, passing `true`
+ * for `isSorted` will run a faster algorithm. If `callback` is passed, each
+ * element of `array` is passed through a callback` before uniqueness is computed.
+ * The `callback` is bound to `thisArg` and invoked with three arguments; (value, index, array).
+ *
+ * @static
+ * @memberOf _
+ * @alias unique
+ * @category Arrays
+ * @param {Array} array The array to process.
+ * @param {Boolean} [isSorted=false] A flag to indicate that the `array` is already sorted.
+ * @param {Function} [callback=identity] The function called per iteration.
+ * @param {Mixed} [thisArg] The `this` binding of `callback`.
+ * @returns {Array} Returns a duplicate-value-free array.
+ * @example
+ *
+ * _.uniq([1, 2, 1, 3, 1]);
+ * // => [1, 2, 3]
+ *
+ * _.uniq([1, 1, 2, 2, 3], true);
+ * // => [1, 2, 3]
+ *
+ * _.uniq([1, 2, 1.5, 3, 2.5], function(num) { return Math.floor(num); });
+ * // => [1, 2, 3]
+ *
+ * _.uniq([1, 2, 1.5, 3, 2.5], function(num) { return this.floor(num); }, Math);
+ * // => [1, 2, 3]
+ */
+ function uniq(array, isSorted, callback, thisArg) {
+ var index = -1,
+ length = array ? array.length : 0,
+ result = [],
+ seen = result;
+
+ // juggle arguments
+ if (typeof isSorted == 'function') {
+ thisArg = callback;
+ callback = isSorted;
+ isSorted = false;
+ }
+ // init value cache for large arrays
+ var isLarge = !isSorted && length >= 75;
+ if (isLarge) {
+ var cache = {};
+ }
+ if (callback) {
+ seen = [];
+ callback = createCallback(callback, thisArg);
+ }
+ while (++index < length) {
+ var value = array[index],
+ computed = callback ? callback(value, index, array) : value;
+
+ if (isLarge) {
+ var key = computed + '';
+ var inited = hasOwnProperty.call(cache, key)
+ ? !(seen = cache[key])
+ : (seen = cache[key] = []);
+ }
+ if (isSorted
+ ? !index || seen[seen.length - 1] !== computed
+ : inited || indexOf(seen, computed) < 0
+ ) {
+ if (callback || isLarge) {
+ seen.push(computed);
+ }
+ result.push(value);
+ }
+ }
+ return result;
+ }
+
+ /**
+ * Creates an array with all occurrences of the passed values removed using
+ * strict equality for comparisons, i.e. `===`.
+ *
+ * @static
+ * @memberOf _
+ * @category Arrays
+ * @param {Array} array The array to filter.
+ * @param {Mixed} [value1, value2, ...] Values to remove.
+ * @returns {Array} Returns a new filtered array.
+ * @example
+ *
+ * _.without([1, 2, 1, 0, 3, 1, 4], 0, 1);
+ * // => [2, 3, 4]
+ */
+ function without(array) {
+ var index = -1,
+ length = array ? array.length : 0,
+ contains = cachedContains(arguments, 1, 20),
+ result = [];
+
+ while (++index < length) {
+ var value = array[index];
+ if (!contains(value)) {
+ result.push(value);
+ }
+ }
+ return result;
+ }
+
+ /**
+ * Groups the elements of each array at their corresponding indexes. Useful for
+ * separate data sources that are coordinated through matching array indexes.
+ * For a matrix of nested arrays, `_.zip.apply(...)` can transpose the matrix
+ * in a similar fashion.
+ *
+ * @static
+ * @memberOf _
+ * @category Arrays
+ * @param {Array} [array1, array2, ...] Arrays to process.
+ * @returns {Array} Returns a new array of grouped elements.
+ * @example
+ *
+ * _.zip(['moe', 'larry', 'curly'], [30, 40, 50], [true, false, false]);
+ * // => [['moe', 30, true], ['larry', 40, false], ['curly', 50, false]]
+ */
+ function zip(array) {
+ var index = -1,
+ length = array ? max(pluck(arguments, 'length')) : 0,
+ result = Array(length);
+
+ while (++index < length) {
+ result[index] = pluck(arguments, index);
+ }
+ return result;
+ }
+
+ /*--------------------------------------------------------------------------*/
+
+ /**
+ * Creates a function that is restricted to executing `func` only after it is
+ * called `n` times. The `func` is executed with the `this` binding of the
+ * created function.
+ *
+ * @static
+ * @memberOf _
+ * @category Functions
+ * @param {Number} n The number of times the function must be called before
+ * it is executed.
+ * @param {Function} func The function to restrict.
+ * @returns {Function} Returns the new restricted function.
+ * @example
+ *
+ * var renderNotes = _.after(notes.length, render);
+ * _.forEach(notes, function(note) {
+ * note.asyncSave({ 'success': renderNotes });
+ * });
+ * // `renderNotes` is run once, after all notes have saved
+ */
+ function after(n, func) {
+ if (n < 1) {
+ return func();
+ }
+ return function() {
+ if (--n < 1) {
+ return func.apply(this, arguments);
+ }
+ };
+ }
+
+ /**
+ * Creates a function that, when called, invokes `func` with the `this`
+ * binding of `thisArg` and prepends any additional `bind` arguments to those
+ * passed to the bound function.
+ *
+ * @static
+ * @memberOf _
+ * @category Functions
+ * @param {Function} func The function to bind.
+ * @param {Mixed} [thisArg] The `this` binding of `func`.
+ * @param {Mixed} [arg1, arg2, ...] Arguments to be partially applied.
+ * @returns {Function} Returns the new bound function.
+ * @example
+ *
+ * var func = function(greeting) {
+ * return greeting + ' ' + this.name;
+ * };
+ *
+ * func = _.bind(func, { 'name': 'moe' }, 'hi');
+ * func();
+ * // => 'hi moe'
+ */
+ function bind(func, thisArg) {
+ // use `Function#bind` if it exists and is fast
+ // (in V8 `Function#bind` is slower except when partially applied)
+ return isBindFast || (nativeBind && arguments.length > 2)
+ ? nativeBind.call.apply(nativeBind, arguments)
+ : createBound(func, thisArg, slice(arguments, 2));
+ }
+
+ /**
+ * Binds methods on `object` to `object`, overwriting the existing method.
+ * If no method names are provided, all the function properties of `object`
+ * will be bound.
+ *
+ * @static
+ * @memberOf _
+ * @category Functions
+ * @param {Object} object The object to bind and assign the bound methods to.
+ * @param {String} [methodName1, methodName2, ...] Method names on the object to bind.
+ * @returns {Object} Returns `object`.
+ * @example
+ *
+ * var buttonView = {
+ * 'label': 'lodash',
+ * 'onClick': function() { alert('clicked: ' + this.label); }
+ * };
+ *
+ * _.bindAll(buttonView);
+ * jQuery('#lodash_button').on('click', buttonView.onClick);
+ * // => When the button is clicked, `this.label` will have the correct value
+ */
+ function bindAll(object) {
+ var funcs = arguments,
+ index = funcs.length > 1 ? 0 : (funcs = functions(object), -1),
+ length = funcs.length;
+
+ while (++index < length) {
+ var key = funcs[index];
+ object[key] = bind(object[key], object);
+ }
+ return object;
+ }
+
+ /**
+ * Creates a function that, when called, invokes the method at `object[key]`
+ * and prepends any additional `bindKey` arguments to those passed to the bound
+ * function. This method differs from `_.bind` by allowing bound functions to
+ * reference methods that will be redefined or don't yet exist.
+ * See http://michaux.ca/articles/lazy-function-definition-pattern.
+ *
+ * @static
+ * @memberOf _
+ * @category Functions
+ * @param {Object} object The object the method belongs to.
+ * @param {String} key The key of the method.
+ * @param {Mixed} [arg1, arg2, ...] Arguments to be partially applied.
+ * @returns {Function} Returns the new bound function.
+ * @example
+ *
+ * var object = {
+ * 'name': 'moe',
+ * 'greet': function(greeting) {
+ * return greeting + ' ' + this.name;
+ * }
+ * };
+ *
+ * var func = _.bindKey(object, 'greet', 'hi');
+ * func();
+ * // => 'hi moe'
+ *
+ * object.greet = function(greeting) {
+ * return greeting + ', ' + this.name + '!';
+ * };
+ *
+ * func();
+ * // => 'hi, moe!'
+ */
+ function bindKey(object, key) {
+ return createBound(object, key, slice(arguments, 2));
+ }
+
+ /**
+ * Creates a function that is the composition of the passed functions,
+ * where each function consumes the return value of the function that follows.
+ * In math terms, composing the functions `f()`, `g()`, and `h()` produces `f(g(h()))`.
+ * Each function is executed with the `this` binding of the composed function.
+ *
+ * @static
+ * @memberOf _
+ * @category Functions
+ * @param {Function} [func1, func2, ...] Functions to compose.
+ * @returns {Function} Returns the new composed function.
+ * @example
+ *
+ * var greet = function(name) { return 'hi: ' + name; };
+ * var exclaim = function(statement) { return statement + '!'; };
+ * var welcome = _.compose(exclaim, greet);
+ * welcome('moe');
+ * // => 'hi: moe!'
+ */
+ function compose() {
+ var funcs = arguments;
+ return function() {
+ var args = arguments,
+ length = funcs.length;
+
+ while (length--) {
+ args = [funcs[length].apply(this, args)];
+ }
+ return args[0];
+ };
+ }
+
+ /**
+ * Creates a function that will delay the execution of `func` until after
+ * `wait` milliseconds have elapsed since the last time it was invoked. Pass
+ * `true` for `immediate` to cause debounce to invoke `func` on the leading,
+ * instead of the trailing, edge of the `wait` timeout. Subsequent calls to
+ * the debounced function will return the result of the last `func` call.
+ *
+ * @static
+ * @memberOf _
+ * @category Functions
+ * @param {Function} func The function to debounce.
+ * @param {Number} wait The number of milliseconds to delay.
+ * @param {Boolean} immediate A flag to indicate execution is on the leading
+ * edge of the timeout.
+ * @returns {Function} Returns the new debounced function.
+ * @example
+ *
+ * var lazyLayout = _.debounce(calculateLayout, 300);
+ * jQuery(window).on('resize', lazyLayout);
+ */
+ function debounce(func, wait, immediate) {
+ var args,
+ result,
+ thisArg,
+ timeoutId;
+
+ function delayed() {
+ timeoutId = null;
+ if (!immediate) {
+ result = func.apply(thisArg, args);
+ }
+ }
+ return function() {
+ var isImmediate = immediate && !timeoutId;
+ args = arguments;
+ thisArg = this;
+
+ clearTimeout(timeoutId);
+ timeoutId = setTimeout(delayed, wait);
+
+ if (isImmediate) {
+ result = func.apply(thisArg, args);
+ }
+ return result;
+ };
+ }
+
+ /**
+ * Executes the `func` function after `wait` milliseconds. Additional arguments
+ * will be passed to `func` when it is invoked.
+ *
+ * @static
+ * @memberOf _
+ * @category Functions
+ * @param {Function} func The function to delay.
+ * @param {Number} wait The number of milliseconds to delay execution.
+ * @param {Mixed} [arg1, arg2, ...] Arguments to invoke the function with.
+ * @returns {Number} Returns the `setTimeout` timeout id.
+ * @example
+ *
+ * var log = _.bind(console.log, console);
+ * _.delay(log, 1000, 'logged later');
+ * // => 'logged later' (Appears after one second.)
+ */
+ function delay(func, wait) {
+ var args = slice(arguments, 2);
+ return setTimeout(function() { func.apply(undefined, args); }, wait);
+ }
+
+ /**
+ * Defers executing the `func` function until the current call stack has cleared.
+ * Additional arguments will be passed to `func` when it is invoked.
+ *
+ * @static
+ * @memberOf _
+ * @category Functions
+ * @param {Function} func The function to defer.
+ * @param {Mixed} [arg1, arg2, ...] Arguments to invoke the function with.
+ * @returns {Number} Returns the `setTimeout` timeout id.
+ * @example
+ *
+ * _.defer(function() { alert('deferred'); });
+ * // returns from the function before `alert` is called
+ */
+ function defer(func) {
+ var args = slice(arguments, 1);
+ return setTimeout(function() { func.apply(undefined, args); }, 1);
+ }
+
+ /**
+ * Creates a function that memoizes the result of `func`. If `resolver` is
+ * passed, it will be used to determine the cache key for storing the result
+ * based on the arguments passed to the memoized function. By default, the first
+ * argument passed to the memoized function is used as the cache key. The `func`
+ * is executed with the `this` binding of the memoized function.
+ *
+ * @static
+ * @memberOf _
+ * @category Functions
+ * @param {Function} func The function to have its output memoized.
+ * @param {Function} [resolver] A function used to resolve the cache key.
+ * @returns {Function} Returns the new memoizing function.
+ * @example
+ *
+ * var fibonacci = _.memoize(function(n) {
+ * return n < 2 ? n : fibonacci(n - 1) + fibonacci(n - 2);
+ * });
+ */
+ function memoize(func, resolver) {
+ var cache = {};
+ return function() {
+ var key = resolver ? resolver.apply(this, arguments) : arguments[0];
+ return hasOwnProperty.call(cache, key)
+ ? cache[key]
+ : (cache[key] = func.apply(this, arguments));
+ };
+ }
+
+ /**
+ * Creates a function that is restricted to execute `func` once. Repeat calls to
+ * the function will return the value of the first call. The `func` is executed
+ * with the `this` binding of the created function.
+ *
+ * @static
+ * @memberOf _
+ * @category Functions
+ * @param {Function} func The function to restrict.
+ * @returns {Function} Returns the new restricted function.
+ * @example
+ *
+ * var initialize = _.once(createApplication);
+ * initialize();
+ * initialize();
+ * // Application is only created once.
+ */
+ function once(func) {
+ var result,
+ ran = false;
+
+ return function() {
+ if (ran) {
+ return result;
+ }
+ ran = true;
+ result = func.apply(this, arguments);
+
+ // clear the `func` variable so the function may be garbage collected
+ func = null;
+ return result;
+ };
+ }
+
+ /**
+ * Creates a function that, when called, invokes `func` with any additional
+ * `partial` arguments prepended to those passed to the new function. This
+ * method is similar to `bind`, except it does **not** alter the `this` binding.
+ *
+ * @static
+ * @memberOf _
+ * @category Functions
+ * @param {Function} func The function to partially apply arguments to.
+ * @param {Mixed} [arg1, arg2, ...] Arguments to be partially applied.
+ * @returns {Function} Returns the new partially applied function.
+ * @example
+ *
+ * var greet = function(greeting, name) { return greeting + ': ' + name; };
+ * var hi = _.partial(greet, 'hi');
+ * hi('moe');
+ * // => 'hi: moe'
+ */
+ function partial(func) {
+ return createBound(func, slice(arguments, 1));
+ }
+
+ /**
+ * Creates a function that, when executed, will only call the `func`
+ * function at most once per every `wait` milliseconds. If the throttled
+ * function is invoked more than once during the `wait` timeout, `func` will
+ * also be called on the trailing edge of the timeout. Subsequent calls to the
+ * throttled function will return the result of the last `func` call.
+ *
+ * @static
+ * @memberOf _
+ * @category Functions
+ * @param {Function} func The function to throttle.
+ * @param {Number} wait The number of milliseconds to throttle executions to.
+ * @returns {Function} Returns the new throttled function.
+ * @example
+ *
+ * var throttled = _.throttle(updatePosition, 100);
+ * jQuery(window).on('scroll', throttled);
+ */
+ function throttle(func, wait) {
+ var args,
+ result,
+ thisArg,
+ timeoutId,
+ lastCalled = 0;
+
+ function trailingCall() {
+ lastCalled = new Date;
+ timeoutId = null;
+ result = func.apply(thisArg, args);
+ }
+ return function() {
+ var now = new Date,
+ remaining = wait - (now - lastCalled);
+
+ args = arguments;
+ thisArg = this;
+
+ if (remaining <= 0) {
+ clearTimeout(timeoutId);
+ timeoutId = null;
+ lastCalled = now;
+ result = func.apply(thisArg, args);
+ }
+ else if (!timeoutId) {
+ timeoutId = setTimeout(trailingCall, remaining);
+ }
+ return result;
+ };
+ }
+
+ /**
+ * Creates a function that passes `value` to the `wrapper` function as its
+ * first argument. Additional arguments passed to the function are appended
+ * to those passed to the `wrapper` function. The `wrapper` is executed with
+ * the `this` binding of the created function.
+ *
+ * @static
+ * @memberOf _
+ * @category Functions
+ * @param {Mixed} value The value to wrap.
+ * @param {Function} wrapper The wrapper function.
+ * @returns {Function} Returns the new function.
+ * @example
+ *
+ * var hello = function(name) { return 'hello ' + name; };
+ * hello = _.wrap(hello, function(func) {
+ * return 'before, ' + func('moe') + ', after';
+ * });
+ * hello();
+ * // => 'before, hello moe, after'
+ */
+ function wrap(value, wrapper) {
+ return function() {
+ var args = [value];
+ push.apply(args, arguments);
+ return wrapper.apply(this, args);
+ };
+ }
+
+ /*--------------------------------------------------------------------------*/
+
+ /**
+ * Converts the characters `&`, `<`, `>`, `"`, and `'` in `string` to their
+ * corresponding HTML entities.
+ *
+ * @static
+ * @memberOf _
+ * @category Utilities
+ * @param {String} string The string to escape.
+ * @returns {String} Returns the escaped string.
+ * @example
+ *
+ * _.escape('Moe, Larry & Curly');
+ * // => 'Moe, Larry & Curly'
+ */
+ function escape(string) {
+ return string == null ? '' : (string + '').replace(reUnescapedHtml, escapeHtmlChar);
+ }
+
+ /**
+ * This function returns the first argument passed to it.
+ *
+ * @static
+ * @memberOf _
+ * @category Utilities
+ * @param {Mixed} value Any value.
+ * @returns {Mixed} Returns `value`.
+ * @example
+ *
+ * var moe = { 'name': 'moe' };
+ * moe === _.identity(moe);
+ * // => true
+ */
+ function identity(value) {
+ return value;
+ }
+
+ /**
+ * Adds functions properties of `object` to the `lodash` function and chainable
+ * wrapper.
+ *
+ * @static
+ * @memberOf _
+ * @category Utilities
+ * @param {Object} object The object of function properties to add to `lodash`.
+ * @example
+ *
+ * _.mixin({
+ * 'capitalize': function(string) {
+ * return string.charAt(0).toUpperCase() + string.slice(1).toLowerCase();
+ * }
+ * });
+ *
+ * _.capitalize('larry');
+ * // => 'Larry'
+ *
+ * _('curly').capitalize();
+ * // => 'Curly'
+ */
+ function mixin(object) {
+ forEach(functions(object), function(methodName) {
+ var func = lodash[methodName] = object[methodName];
+
+ lodash.prototype[methodName] = function() {
+ var args = [this.__wrapped__];
+ push.apply(args, arguments);
+
+ var result = func.apply(lodash, args);
+ return new lodash(result);
+ };
+ });
+ }
+
+ /**
+ * Reverts the '_' variable to its previous value and returns a reference to
+ * the `lodash` function.
+ *
+ * @static
+ * @memberOf _
+ * @category Utilities
+ * @returns {Function} Returns the `lodash` function.
+ * @example
+ *
+ * var lodash = _.noConflict();
+ */
+ function noConflict() {
+ window._ = oldDash;
+ return this;
+ }
+
+ /**
+ * Produces a random number between `min` and `max` (inclusive). If only one
+ * argument is passed, a number between `0` and the given number will be returned.
+ *
+ * @static
+ * @memberOf _
+ * @category Utilities
+ * @param {Number} [min=0] The minimum possible value.
+ * @param {Number} [max=1] The maximum possible value.
+ * @returns {Number} Returns a random number.
+ * @example
+ *
+ * _.random(0, 5);
+ * // => a number between 1 and 5
+ *
+ * _.random(5);
+ * // => also a number between 1 and 5
+ */
+ function random(min, max) {
+ if (min == null && max == null) {
+ max = 1;
+ }
+ min = +min || 0;
+ if (max == null) {
+ max = min;
+ min = 0;
+ }
+ return min + floor(nativeRandom() * ((+max || 0) - min + 1));
+ }
+
+ /**
+ * Resolves the value of `property` on `object`. If `property` is a function
+ * it will be invoked and its result returned, else the property value is
+ * returned. If `object` is falsey, then `null` is returned.
+ *
+ * @static
+ * @memberOf _
+ * @category Utilities
+ * @param {Object} object The object to inspect.
+ * @param {String} property The property to get the value of.
+ * @returns {Mixed} Returns the resolved value.
+ * @example
+ *
+ * var object = {
+ * 'cheese': 'crumpets',
+ * 'stuff': function() {
+ * return 'nonsense';
+ * }
+ * };
+ *
+ * _.result(object, 'cheese');
+ * // => 'crumpets'
+ *
+ * _.result(object, 'stuff');
+ * // => 'nonsense'
+ */
+ function result(object, property) {
+ // based on Backbone's private `getValue` function
+ // https://github.com/documentcloud/backbone/blob/0.9.2/backbone.js#L1419-1424
+ var value = object ? object[property] : null;
+ return isFunction(value) ? object[property]() : value;
+ }
+
+ /**
+ * A micro-templating method that handles arbitrary delimiters, preserves
+ * whitespace, and correctly escapes quotes within interpolated code.
+ *
+ * Note: In the development build `_.template` utilizes sourceURLs for easier
+ * debugging. See http://www.html5rocks.com/en/tutorials/developertools/sourcemaps/#toc-sourceurl
+ *
+ * Note: Lo-Dash may be used in Chrome extensions by either creating a `lodash csp`
+ * build and avoiding `_.template` use, or loading Lo-Dash in a sandboxed page.
+ * See http://developer.chrome.com/trunk/extensions/sandboxingEval.html
+ *
+ * @static
+ * @memberOf _
+ * @category Utilities
+ * @param {String} text The template text.
+ * @param {Obect} data The data object used to populate the text.
+ * @param {Object} options The options object.
+ * escape - The "escape" delimiter regexp.
+ * evaluate - The "evaluate" delimiter regexp.
+ * interpolate - The "interpolate" delimiter regexp.
+ * sourceURL - The sourceURL of the template's compiled source.
+ * variable - The data object variable name.
+ *
+ * @returns {Function|String} Returns a compiled function when no `data` object
+ * is given, else it returns the interpolated text.
+ * @example
+ *
+ * // using a compiled template
+ * var compiled = _.template('hello <%= name %>');
+ * compiled({ 'name': 'moe' });
+ * // => 'hello moe'
+ *
+ * var list = '<% _.forEach(people, function(name) { %><%= name %><% }); %>';
+ * _.template(list, { 'people': ['moe', 'larry', 'curly'] });
+ * // => 'moelarrycurly'
+ *
+ * // using the "escape" delimiter to escape HTML in data property values
+ * _.template('<%- value %>', { 'value': '