From 7a98913d0cf74887180aca3520c628a52b0e7071 Mon Sep 17 00:00:00 2001 From: Tom MacWright Date: Mon, 1 Apr 2013 17:19:15 -0400 Subject: [PATCH] Add iD editor MIME-Version: 1.0 Content-Type: text/plain; charset=utf8 Content-Transfer-Encoding: 8bit iD sources live in vendor/assets/iD, and are managed with vendorer, using the "rewrite" functionality to replace relative paths in the CSS with <%= asset_path(…) %> directives. To avoid needing to namespace all of its CSS classes, iD is embedded via an iframe. To setup, edit application.yml to include an `id_key` key. (It can use the same consumer key as P2 if you like.) --- Vendorfile | 10 + app/assets/stylesheets/common.css.scss | 8 + app/controllers/site_controller.rb | 4 + app/views/site/_id.html.erb | 34 + app/views/site/id_iframe.html.erb | 37 + config/environments/production.rb | 1 + config/example.application.yml | 2 + config/locales/en.yml | 4 + config/routes.rb | 1 + lib/editors.rb | 4 +- vendor/assets/iD/iD.css.erb | 4320 + vendor/assets/iD/iD.js | 70963 ++++++++++++++++ .../assets/iD/iD/img/background-pattern-1.png | Bin 0 -> 89 bytes .../iD/iD/img/background-pattern-opacity.png | Bin 0 -> 90 bytes vendor/assets/iD/iD/img/bing_maps.png | Bin 0 -> 3576 bytes .../iD/iD/img/cursor-draw-connect-line.png | Bin 0 -> 452 bytes .../iD/iD/img/cursor-draw-connect-line2x.png | Bin 0 -> 832 bytes .../iD/iD/img/cursor-draw-connect-vertex.png | Bin 0 -> 227 bytes .../iD/img/cursor-draw-connect-vertex2x.png | Bin 0 -> 412 bytes vendor/assets/iD/iD/img/cursor-draw.png | Bin 0 -> 190 bytes vendor/assets/iD/iD/img/cursor-draw2x.png | Bin 0 -> 351 bytes vendor/assets/iD/iD/img/cursor-grab.png | Bin 0 -> 441 bytes vendor/assets/iD/iD/img/cursor-grab2x.png | Bin 0 -> 851 bytes vendor/assets/iD/iD/img/cursor-grabbing.png | Bin 0 -> 328 bytes vendor/assets/iD/iD/img/cursor-grabbing2x.png | Bin 0 -> 627 bytes vendor/assets/iD/iD/img/cursor-pointer.png | Bin 0 -> 364 bytes vendor/assets/iD/iD/img/cursor-pointer2x.png | Bin 0 -> 685 bytes vendor/assets/iD/iD/img/cursor-pointing.png | Bin 0 -> 352 bytes vendor/assets/iD/iD/img/cursor-pointing2x.png | Bin 0 -> 665 bytes .../assets/iD/iD/img/cursor-select-acting.png | Bin 0 -> 226 bytes .../iD/iD/img/cursor-select-acting2x.png | Bin 0 -> 376 bytes vendor/assets/iD/iD/img/cursor-select-add.png | Bin 0 -> 303 bytes .../assets/iD/iD/img/cursor-select-add2x.png | Bin 0 -> 603 bytes .../assets/iD/iD/img/cursor-select-area.png | Bin 0 -> 282 bytes .../assets/iD/iD/img/cursor-select-area2x.png | Bin 0 -> 515 bytes .../assets/iD/iD/img/cursor-select-line.png | Bin 0 -> 335 bytes .../assets/iD/iD/img/cursor-select-line2x.png | Bin 0 -> 1071 bytes .../assets/iD/iD/img/cursor-select-point.png | Bin 0 -> 354 bytes .../iD/iD/img/cursor-select-point2x.png | Bin 0 -> 685 bytes .../assets/iD/iD/img/cursor-select-remove.png | Bin 0 -> 290 bytes .../iD/iD/img/cursor-select-remove2x.png | Bin 0 -> 568 bytes .../assets/iD/iD/img/cursor-select-split.png | Bin 0 -> 297 bytes .../iD/iD/img/cursor-select-split2x.png | Bin 0 -> 566 bytes .../assets/iD/iD/img/cursor-select-vertex.png | Bin 0 -> 305 bytes .../iD/iD/img/cursor-select-vertex2x.png | Bin 0 -> 611 bytes vendor/assets/iD/iD/img/feature-icons.png | Bin 0 -> 66894 bytes vendor/assets/iD/iD/img/loader-black.gif | Bin 0 -> 1715 bytes vendor/assets/iD/iD/img/loader-white.gif | Bin 0 -> 1715 bytes vendor/assets/iD/iD/img/loader_bg.gif | Bin 0 -> 606 bytes vendor/assets/iD/iD/img/logo.png | Bin 0 -> 3693 bytes vendor/assets/iD/iD/img/mini-loader.gif | Bin 0 -> 287 bytes vendor/assets/iD/iD/img/pattern/cemetery.png | Bin 0 -> 292 bytes .../assets/iD/iD/img/pattern/construction.png | Bin 0 -> 280 bytes vendor/assets/iD/iD/img/pattern/dots.png | Bin 0 -> 222 bytes vendor/assets/iD/iD/img/pattern/farmland.png | Bin 0 -> 206 bytes vendor/assets/iD/iD/img/pattern/orchard.png | Bin 0 -> 339 bytes vendor/assets/iD/iD/img/pattern/vineyard.png | Bin 0 -> 354 bytes vendor/assets/iD/iD/img/pattern/wetland.png | Bin 0 -> 301 bytes vendor/assets/iD/iD/img/sprite.svg | 2242 + 59 files changed, 77628 insertions(+), 2 deletions(-) create mode 100644 app/views/site/_id.html.erb create mode 100644 app/views/site/id_iframe.html.erb create mode 100644 vendor/assets/iD/iD.css.erb create mode 100644 vendor/assets/iD/iD.js create mode 100644 vendor/assets/iD/iD/img/background-pattern-1.png create mode 100644 vendor/assets/iD/iD/img/background-pattern-opacity.png create mode 100644 vendor/assets/iD/iD/img/bing_maps.png create mode 100644 vendor/assets/iD/iD/img/cursor-draw-connect-line.png create mode 100644 vendor/assets/iD/iD/img/cursor-draw-connect-line2x.png create mode 100644 vendor/assets/iD/iD/img/cursor-draw-connect-vertex.png create mode 100644 vendor/assets/iD/iD/img/cursor-draw-connect-vertex2x.png create mode 100644 vendor/assets/iD/iD/img/cursor-draw.png create mode 100644 vendor/assets/iD/iD/img/cursor-draw2x.png create mode 100644 vendor/assets/iD/iD/img/cursor-grab.png create mode 100644 vendor/assets/iD/iD/img/cursor-grab2x.png create mode 100644 vendor/assets/iD/iD/img/cursor-grabbing.png create mode 100644 vendor/assets/iD/iD/img/cursor-grabbing2x.png create mode 100644 vendor/assets/iD/iD/img/cursor-pointer.png create mode 100644 vendor/assets/iD/iD/img/cursor-pointer2x.png create mode 100644 vendor/assets/iD/iD/img/cursor-pointing.png create mode 100644 vendor/assets/iD/iD/img/cursor-pointing2x.png create mode 100644 vendor/assets/iD/iD/img/cursor-select-acting.png create mode 100644 vendor/assets/iD/iD/img/cursor-select-acting2x.png create mode 100644 vendor/assets/iD/iD/img/cursor-select-add.png create mode 100644 vendor/assets/iD/iD/img/cursor-select-add2x.png create mode 100644 vendor/assets/iD/iD/img/cursor-select-area.png create mode 100644 vendor/assets/iD/iD/img/cursor-select-area2x.png create mode 100644 vendor/assets/iD/iD/img/cursor-select-line.png create mode 100644 vendor/assets/iD/iD/img/cursor-select-line2x.png create mode 100644 vendor/assets/iD/iD/img/cursor-select-point.png create mode 100644 vendor/assets/iD/iD/img/cursor-select-point2x.png create mode 100644 vendor/assets/iD/iD/img/cursor-select-remove.png create mode 100644 vendor/assets/iD/iD/img/cursor-select-remove2x.png create mode 100644 vendor/assets/iD/iD/img/cursor-select-split.png create mode 100644 vendor/assets/iD/iD/img/cursor-select-split2x.png create mode 100644 vendor/assets/iD/iD/img/cursor-select-vertex.png create mode 100644 vendor/assets/iD/iD/img/cursor-select-vertex2x.png create mode 100644 vendor/assets/iD/iD/img/feature-icons.png create mode 100644 vendor/assets/iD/iD/img/loader-black.gif create mode 100644 vendor/assets/iD/iD/img/loader-white.gif create mode 100644 vendor/assets/iD/iD/img/loader_bg.gif create mode 100644 vendor/assets/iD/iD/img/logo.png create mode 100644 vendor/assets/iD/iD/img/mini-loader.gif create mode 100644 vendor/assets/iD/iD/img/pattern/cemetery.png create mode 100644 vendor/assets/iD/iD/img/pattern/construction.png create mode 100644 vendor/assets/iD/iD/img/pattern/dots.png create mode 100644 vendor/assets/iD/iD/img/pattern/farmland.png create mode 100644 vendor/assets/iD/iD/img/pattern/orchard.png create mode 100644 vendor/assets/iD/iD/img/pattern/vineyard.png create mode 100644 vendor/assets/iD/iD/img/pattern/wetland.png create mode 100644 vendor/assets/iD/iD/img/sprite.svg 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'] }); + * // => '
  • moe
  • larry
  • curly
  • ' + * + * // using the "escape" delimiter to escape HTML in data property values + * _.template('<%- value %>', { 'value': '