]> git.openstreetmap.org Git - rails.git/commitdiff
iD 1.1.0
authorJohn Firebaugh <john.firebaugh@gmail.com>
Tue, 30 Jul 2013 22:40:16 +0000 (15:40 -0700)
committerTom Hughes <tom@compton.nu>
Fri, 9 Aug 2013 23:38:36 +0000 (00:38 +0100)
47 files changed:
vendor/assets/iD/iD.css.erb
vendor/assets/iD/iD.js
vendor/assets/iD/iD/img/line-presets.png
vendor/assets/iD/iD/img/maki-sprite.png
vendor/assets/iD/iD/img/relation-presets.png [new file with mode: 0644]
vendor/assets/iD/iD/img/sprite.svg
vendor/assets/iD/iD/locales/af.json
vendor/assets/iD/iD/locales/ar.json [new file with mode: 0644]
vendor/assets/iD/iD/locales/ast.json
vendor/assets/iD/iD/locales/bg-BG.json [new file with mode: 0644]
vendor/assets/iD/iD/locales/bs.json
vendor/assets/iD/iD/locales/ca.json
vendor/assets/iD/iD/locales/cs.json
vendor/assets/iD/iD/locales/da.json
vendor/assets/iD/iD/locales/de.json
vendor/assets/iD/iD/locales/en.json
vendor/assets/iD/iD/locales/es.json
vendor/assets/iD/iD/locales/et.json [new file with mode: 0644]
vendor/assets/iD/iD/locales/fi.json
vendor/assets/iD/iD/locales/fr.json
vendor/assets/iD/iD/locales/hr.json
vendor/assets/iD/iD/locales/hu.json
vendor/assets/iD/iD/locales/id.json
vendor/assets/iD/iD/locales/is.json
vendor/assets/iD/iD/locales/it.json
vendor/assets/iD/iD/locales/ja.json
vendor/assets/iD/iD/locales/ko.json [new file with mode: 0644]
vendor/assets/iD/iD/locales/lt.json [new file with mode: 0644]
vendor/assets/iD/iD/locales/lv.json
vendor/assets/iD/iD/locales/nl.json
vendor/assets/iD/iD/locales/no.json [new file with mode: 0644]
vendor/assets/iD/iD/locales/pl.json
vendor/assets/iD/iD/locales/pt-BR.json [new file with mode: 0644]
vendor/assets/iD/iD/locales/pt.json
vendor/assets/iD/iD/locales/ru.json
vendor/assets/iD/iD/locales/sk.json
vendor/assets/iD/iD/locales/sl.json
vendor/assets/iD/iD/locales/sr-RS.json [new file with mode: 0644]
vendor/assets/iD/iD/locales/sr.json
vendor/assets/iD/iD/locales/sv.json
vendor/assets/iD/iD/locales/te.json [new file with mode: 0644]
vendor/assets/iD/iD/locales/tr.json
vendor/assets/iD/iD/locales/uk.json
vendor/assets/iD/iD/locales/vi.json
vendor/assets/iD/iD/locales/zh-CN.json [new file with mode: 0644]
vendor/assets/iD/iD/locales/zh-TW.json
vendor/assets/iD/iD/locales/zh.json

index f6dca171f9d8ca0a34560e990f1bfc6ae3f1b39d..322dad7460d40efcbe892afa3f6ca65e1f271e55 100644 (file)
@@ -180,6 +180,9 @@ img.tile {
 img.tile-loaded {
     opacity: 1;
 }
+img.tile-removing {
+    opacity: 0;
+}
 
 /* base styles */
 path {
@@ -221,7 +224,7 @@ g.point .shadow {
     stroke-opacity: 0;
 }
 
-.behavior-hover g.point.hover:not(.selected) .shadow {
+g.point.hover:not(.selected) .shadow {
     stroke-opacity: 0.5;
 }
 
@@ -236,7 +239,7 @@ g.point.active, g.point.active * {
 /* vertices and midpoints */
 
 g.vertex .fill {
-    fill: none;
+    fill: #000;
 }
 
 g.vertex .stroke {
@@ -249,10 +252,6 @@ g.vertex.shared .stroke {
     fill: #aaa;
 }
 
-g.vertex.tagged .fill {
-    fill: #000;
-}
-
 g.midpoint .fill {
     fill: #ddd;
     stroke: black;
@@ -270,17 +269,17 @@ g.vertex.vertex-hover {
     display: none;
 }
 
-.mode-draw-area .behavior-hover g.vertex.vertex-hover,
-.mode-draw-line .behavior-hover g.vertex.vertex-hover,
-.mode-add-area  .behavior-hover g.vertex.vertex-hover,
-.mode-add-line  .behavior-hover g.vertex.vertex-hover,
-.mode-add-point .behavior-hover g.vertex.vertex-hover,
-.mode-drag-node .behavior-hover g.vertex.vertex-hover {
+.mode-draw-area g.vertex.vertex-hover,
+.mode-draw-line g.vertex.vertex-hover,
+.mode-add-area  g.vertex.vertex-hover,
+.mode-add-line  g.vertex.vertex-hover,
+.mode-add-point g.vertex.vertex-hover,
+.mode-drag-node g.vertex.vertex-hover {
     display: block;
 }
 
-.behavior-hover g.vertex.hover:not(.selected) .shadow,
-.behavior-hover g.midpoint.hover:not(.selected) .shadow {
+g.vertex.hover:not(.selected) .shadow,
+g.midpoint.hover:not(.selected) .shadow {
     fill-opacity: 0.3;
 }
 
@@ -292,8 +291,7 @@ g.vertex.selected .shadow {
 .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 {
+.mode-add-point g.midpoint {
   display: none;
 }
 
@@ -315,7 +313,7 @@ path.shadow {
     stroke-opacity: 0;
 }
 
-.behavior-hover path.shadow.hover:not(.selected) {
+path.shadow.hover:not(.selected) {
     stroke-opacity: 0.3;
 }
 
@@ -323,18 +321,11 @@ 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;
+    stroke: white;
+    stroke-width: 1;
 }
+
 path.area.fill {
     fill:#fff;
     fill-opacity:0.3;
@@ -348,7 +339,6 @@ path.line.stroke {
 
 path.stroke.tag-natural {
     stroke: #b6e199;
-    stroke-width:1;
 }
 path.fill.tag-natural {
     fill: #b6e199;
@@ -363,7 +353,6 @@ path.fill.tag-natural-water {
 
 path.stroke.tag-amenity-school {
     stroke: #ffff94;
-    stroke-width: 1;
 }
 path.fill.tag-amenity-school {
     fill: #ffff94;
@@ -380,7 +369,6 @@ path.fill.tag-amenity-university {
 
 path.stroke.tag-building {
     stroke: #e06e5f;
-    stroke-width: 1;
 }
 path.fill.tag-building {
     fill: #e06e5f;
@@ -396,7 +384,6 @@ path.stroke.tag-natural-grassland,
 path.stroke.tag-leisure-pitch,
 path.stroke.tag-leisure-park {
     stroke: #8cd05f;
-    stroke-width: 1;
 }
 
 path.stroke.tag-landuse-residential {
@@ -565,7 +552,6 @@ path.fill.tag-landuse-industrial {
 
 path.stroke.tag-amenity-parking {
     stroke: #aaa;
-    stroke-width: 1;
 }
 path.fill.tag-amenity-parking {
     fill: #aaa;
@@ -825,24 +811,24 @@ path.fill.tag-aeroway-apron {
 
 /* bridges */
 
-path.casing.tag-bridge-yes {
+path.casing.tag-bridge {
     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 {
+path.casing.tag-highway-living_street.tag-bridge,
+path.casing.tag-highway-path.tag-bridge {
     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 {
+path.casing.tag-highway-service.tag-bridge,
+path.casing.tag-highway-track.tag-bridge,
+path.casing.tag-highway-steps.tag-bridge,
+path.casing.tag-highway-footway.tag-bridge,
+path.casing.tag-highway-cycleway.tag-bridge,
+path.casing.tag-highway-bridleway.tag-bridge {
     stroke-width: 8;
 }
 
@@ -850,25 +836,25 @@ 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.tag-highway-living_street.tag-bridge,
+path.shadow.tag-highway-path.tag-bridge,
 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 {
+path.shadow.tag-highway-service.tag-bridge,
+path.shadow.tag-highway-track.tag-bridge,
+path.shadow.tag-highway-steps.tag-bridge,
+path.shadow.tag-highway-footway.tag-bridge,
+path.shadow.tag-highway-cycleway.tag-bridge,
+path.shadow.tag-highway-bridleway.tag-bridge {
     stroke-width: 16;
 }
 
 /* tunnels */
 
-path.stroke.tag-highway.tag-tunnel-yes {
+path.stroke.tag-highway.tag-tunnel {
     stroke-opacity: 0.3;
 }
 
-path.casing.tag-highway.tag-tunnel-yes {
+path.casing.tag-highway.tag-tunnel {
     stroke-opacity: 0.5;
 }
 
@@ -1048,13 +1034,17 @@ text.pointlabel {
     stroke-miterlimit: 1;
 }
 
+text.proximate {
+    opacity: 0;
+}
+
 text.point {
   font-size: 10px;
 }
 
 /* Cursors */
 
-#map:hover {
+#map {
     cursor: auto; /* Opera */
     cursor: url(<%= asset_path("iD/img/cursor-grab.png") %>) 9 9, auto; /* FF */
     cursor: -webkit-image-set(
@@ -1063,15 +1053,6 @@ text.point {
         ) 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 */
@@ -1157,11 +1138,11 @@ text.point {
         ), 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 {
+.mode-draw-line #map,
+.mode-draw-area #map,
+.mode-add-line  #map,
+.mode-add-area  #map,
+.mode-drag-node #map {
     cursor: crosshair; /* Opera */
     cursor: url(<%= asset_path("iD/img/cursor-draw.png") %>) 9 9, crosshair; /* FF */
     cursor: -webkit-image-set(
@@ -1170,11 +1151,11 @@ text.point {
         ) 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 {
+.mode-draw-line .way.hover,
+.mode-draw-area .way.hover,
+.mode-add-line  .way.hover,
+.mode-add-area  .way.hover,
+.mode-drag-node .way.hover {
     cursor: crosshair; /* Opera */
     cursor: url(<%= asset_path("iD/img/cursor-draw-connect-line.png") %>) 9 9, crosshair; /* FF */
     cursor: -webkit-image-set(
@@ -1183,11 +1164,11 @@ text.point {
         ) 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 {
+.mode-draw-line .vertex.hover,
+.mode-draw-area .vertex.hover,
+.mode-add-line  .vertex.hover,
+.mode-add-area  .vertex.hover,
+.mode-drag-node .vertex.hover {
     cursor: crosshair; /* Opera */
     cursor: url(<%= asset_path("iD/img/cursor-draw-connect-vertex.png") %>) 9 9, crosshair; /* FF */
     cursor: -webkit-image-set(
@@ -1196,8 +1177,8 @@ text.point {
         ) 9 9, crosshair;
 }
 
-.mode-add-point #map:hover,
-.lasso #map:hover,
+.mode-add-point #map,
+.lasso #map,
 .lasso .way,
 .lasso .vertex {
     cursor: crosshair; /* Opera */
@@ -1251,18 +1232,17 @@ path.gpx {
   applied to both html and body. https://gist.github.com/jfirebaugh/bd225bcfdd3a633850c4
 */
 html, body {
-  width: 100%;
-  height: 100%;
+    width: 100%;
+    height: 100%;
 }
 
 body {
-    font:normal 12px/1.6666 'Helvetica Neue', Arial, sans-serif;
+    font:normal 12px/1.6667 'Helvetica Neue', Arial, sans-serif;
     margin:0;
     padding:0;
     min-width: 768px;
     color:#333;
     overflow: hidden;
-    -webkit-font-smoothing: subpixel-antialiased;
 }
 
 .unsupported {
@@ -1279,6 +1259,17 @@ body {
     min-width: 768px;
 }
 
+#content {
+    position: relative;
+    overflow: hidden;
+    height: 100%;
+}
+
+.spacer {
+    height: 40px;
+    margin-right: 10px;
+}
+
 .limiter {
     position: relative;
     max-width: 1200px;
@@ -1286,18 +1277,16 @@ body {
 
 .spinner {
     opacity: .5;
-    z-index: 2;
-    position: relative;
 }
 
 .spinner img {
-    position: fixed;
+    position: absolute;
     height: 40px;
     width: 40px;
     right: 10px;
     top: 10px;
-    margin: auto;
     border-radius: 4px;
+    margin-right: 10px;
     background: black;
 }
 
@@ -1405,10 +1394,12 @@ input[type=email] {
     height:30px;
     width: 100%;
     border-radius:4px;
-    -webkit-transition: all 100ms;
-       -moz-transition: all 100ms;
-       -o-transition: all 100ms;
-            transition: all 100ms;
+    text-overflow: ellipsis;
+    overflow: hidden;
+    -webkit-transition: all 200ms;
+       -moz-transition: all 200ms;
+         -o-transition: all 200ms;
+            transition: all 200ms;
 }
 
 textarea:focus,
@@ -1416,19 +1407,20 @@ input:focus {
     background-color: #F1F1F1;
 }
 
-input.major {
-    width: 100%;
-    padding:5px 10px;
-    font-size: 18px;
-    font-weight: bold;
-    height:60px;
+input[type="checkbox"],
+input[type="radio"] {
+    float: left;
+    width: 14px;
+    height: 14px;
+    margin-right: 5px;
+    margin-top: 3px;
 }
 
-/* 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;
+/* remove bottom border radius when combobox is open */
+.combobox + * textarea:focus,
+.combobox + * input:focus {
+    border-bottom-left-radius: 0 !important;
+    border-bottom-right-radius: 0 !important;
 }
 
 /* tables */
@@ -1471,37 +1463,62 @@ table.tags, table.tags td, table.tags th {
 
 ul li { list-style: none;}
 
-ul.toggle-list {
+.toggle-list {
     border-radius: 4px;
     border: 1px solid #CCC;
-    margin-bottom: 10px;
 }
 
-ul.toggle-list li a {
+.toggle-list > label {
     position: relative;
-    padding: 5px 10px 5px 25px;
+    padding: 5px 10px;
     display:block;
-    border-top: 1px solid #ccc;
+    height: 30px;
+    border-bottom: 1px solid #ccc;
+    background-color: white;
+    color: #7092FF;
+    cursor: pointer;
+       -moz-transition: all 100ms;
+         -o-transition: all 100ms;
+            transition: all 100ms;
 }
 
-ul.toggle-list li:first-child a {
-    border-top: 0;
+.toggle-list > label:hover {
+    background-color: #ececec;
+}
+
+.toggle-list > label:first-child {
     border-radius: 3px 3px 0 0;
 }
 
-ul.toggle-list li:last-child a {
+.toggle-list > label:last-child {
     border-radius: 0 0 3px 3px;
+    border-bottom-width: 0;
 }
 
-ul.toggle-list li:only-child a {
+.toggle-list > label:only-child {
     border-radius: 3px;
 }
 
-ul.toggle-list li a:hover { background-color: #ececec;}
+.toggle-list label > span { 
+    display: block;
+    overflow: hidden;
+    white-space: nowrap;
+    text-overflow: ellipsis;
+}
+
+.toggle-list > label.remove span.icon {
+    display: block;
+    width: 14px;
+    float: left;
+    margin-right: 5px;
+    background-position: -204px 0;
+}
 
-ul.toggle-list li a.selected { background-color: #e8ebff;}
+.toggle-list > label.active {
+    background: #E8EBFF;
+}
 
-ul.link-list li {
+.link-list li {
     float: right;
     border-left: 1px solid rgba(255,255,255,.5);
     padding: 5px 0 5px 5px;
@@ -1514,28 +1531,6 @@ ul.link-list li:last-child {
     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 {
@@ -1544,12 +1539,12 @@ ul.link-list li:last-child {
 }
 
 .fillL2 {
-    background: #f7f7f7 url(<%= asset_path("iD/img/background-pattern-1.png") %>) repeat;
+    background: #f6f6f6;
     color: #333;
 }
 
 .fillL3 {
-    background: #f1f1f1;
+    background: #ececec;
     color: #333;
 }
 
@@ -1591,7 +1586,8 @@ a.hide {
 .pane {
     position:absolute;
     width:50%;
-    height:100%;
+    top: 0;
+    bottom: 30px;
 }
 
 .pane:first-child {
@@ -1606,10 +1602,10 @@ a.hide {
 
 button {
     text-align: center;
-    font-weight:bold;
     line-height:20px;
     border:0;
     background: white;
+    font-weight: bold;
     color:#333;
     font-size:12px;
     display: inline-block;
@@ -1634,7 +1630,7 @@ button.disabled {
     cursor: auto;
 }
 
-button.active:not([disabled]):not(.disabled) {
+button.active {
     background: #7092ff;
 }
 
@@ -1643,9 +1639,17 @@ button.minor {
     top: 0;
     right: 0;
     height: 100%;
-    width: 20px;
-    opacity: .5;
+    width: 10%;
     border-radius: 0;
+    background-color: #fafafa;
+}
+
+button.minor .icon {
+    opacity: .5;
+}
+
+button.minor:hover {
+    background-color: #f1f1f1;
 }
 
 .button-wrap {
@@ -1654,8 +1658,13 @@ button.minor {
     margin: 0;
 }
 
-.button-wrap button:only-child { width: 100%;}
-.button-wrap:last-of-type { padding-right: 0;}
+.button-wrap button:only-child {
+    width: 100%;
+}
+
+.button-wrap:last-of-type {
+    padding-right: 0;
+}
 
 .joined button {
     border-radius:0;
@@ -1741,9 +1750,12 @@ button.save.has-count .count::before {
 }
 
 /* Definitions for every icon */
-.icon.browse     { background-position:   0 0;}
+.icon.alert     { background-position:   0 0;}
+.icon.point,
 .icon.add-point  { background-position: -20px 0;}
+.icon.line,
 .icon.add-line   { background-position: -40px 0;}
+.icon.area,
 .icon.add-area   { background-position: -60px 0;}
 .icon.undo       { background-position: -80px 0;}
 .icon.redo       { background-position: -100px 0;}
@@ -1753,8 +1765,6 @@ button.save.has-count .count::before {
 .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;}
@@ -1763,20 +1773,30 @@ button.save.has-count .count::before {
 .icon.nearby     { background-position: -340px 0;}
 .icon.geolocate  { background-position: -360px 0;}
 .icon.warning    { background-position: -380px 0;}
+.icon.bug        { background-position: -400px 0;}
 .icon.back       { background-position: -420px 0;}
 .icon.forward    { background-position: -440px 0;}
 .icon.help       { background-position: -460px 0;}
+.icon.relation    { background-position: -520px 0;}
+.icon.relation.route         { background-position: -540px 0;}
+.icon.relation.multipolygon  { background-position: -560px 0;}
 
 .icon.inspect.light    { background-position: -220px -20px;}
+.icon.plus.light       { background-position: -240px -20px;}
+.icon.zoom-in          { background-position: -240px -20px;}
+.icon.zoom-out         { background-position: -260px -20px;}
 .icon.geocode.light    { background-position: -280px -20px;}
-.icon.help.light       { background-position: -460px -20px;}
+.icon.layers.light     { background-position: -300px -20px;}
 .icon.avatar.light     { background-position: -320px -20px;}
 .icon.nearby.light     { background-position: -340px -20px;}
+.icon.geolocate.light  { background-position: -360px -20px;}
+.icon.bug.light        { background-position: -400px -20px;}
+.icon.help.light       { background-position: -460px -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.alert      { 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;}
@@ -1816,12 +1836,14 @@ a:hover .icon.out-link   { background-position: -500px -14px;}
 ------------------------------------------------------- */
 
 #bar {
-    position:absolute;
-    padding: 10px;
+    position: fixed;
+    padding: 10px 0;
     left:0;
     top:0;
     right:0;
     height:60px;
+    z-index: 1;
+    min-width: 768px;
 }
 
 /* Header for modals / panes
@@ -1829,45 +1851,90 @@ a:hover .icon.out-link   { background-position: -500px -14px;}
 
 .header {
     border-bottom: 1px solid #ccc;
-    z-index: 2;
     height: 60px;
     position: relative;
 }
 
 .header h3 {
-    margin-right: 40px;
+    text-align: center;
     margin-bottom: 0;
     white-space: nowrap;
     text-overflow: ellipsis;
     overflow: hidden;
+    padding: 20px;
 }
 
-.modal > button,
-.header button {
-    height: 100%;
+.header button,
+.modal > button {
     border-radius: 0;
-    border-left: 1px solid #CCC;
     width: 40px;
     text-align: center;
     overflow: hidden;
+}
+
+.header button {
+    position: relative;
+    height: 100%;
+}
+
+.preset-list-pane .header button {
     position: absolute;
     right: 0;
     top: 0;
 }
 
 .modal > button {
+    position: absolute;
+    right: 0;
+    top: 0;
     height: 59px;
     z-index: 3;
 }
 
+.footer {
+    position: absolute;
+    bottom: 0;
+    padding: 5px 30px 5px 30px;
+    border-top: 1px solid #ccc;
+    background-color: #fafafa;
+    width: 100%;
+}
+
+.sidebar-component .body {
+    width: 100%;
+    overflow: auto;
+    top: 60px;
+    bottom: 0;
+    position: absolute;
+}
+
 /* Inspector
 ------------------------------------------------------- */
 
-.inspector-wrap {
-    position: absolute;
+#sidebar {
+    position: relative;
+    float: left;
     height: 100%;
-    right: 0;
     overflow: hidden;
+    z-index: 2;
+    background: #f6f6f6;
+}
+
+.sidebar-component {
+    position: absolute;
+    top: 0;
+    left: 0;
+    bottom: 0;
+    right: 0;
+}
+
+.inspector-wrap {
+    width: 100%;
+    height: 100%;
+}
+
+.inspector-hidden {
+    display: none;
 }
 
 .inspector-body {
@@ -1876,54 +1943,95 @@ a:hover .icon.out-link   { background-position: -500px -14px;}
     position: absolute;
     right: 0;
     left: 0;
-    bottom: 30px;
-    top: 60px;
+    bottom: 0;
 }
 
-.pane:first-child .inspector-body {
+.feature-list-pane .inspector-body {
     top: 120px;
 }
 
+.preset-list-pane .inspector-body {
+    top: 120px;
+}
+
+.entity-editor-pane .inspector-body {
+    top: 60px;
+}
+
 .inspector-inner {
     padding: 20px;
     position: relative;
 }
 
-.inspector-wrap .header button.preset-reset {
-    border-right: 1px solid #CCC;
-    position: relative;
+#sidebar .search-header .icon {
+    display: block;
+    position: absolute;
+    left: 10px;
+    top: 80px;
+    pointer-events: none;
 }
 
-.inspector-wrap .header button.preset-reset > div {
-    height: 100%;
-    padding: 20px 0;
+#sidebar .search-header input {
+    position: absolute;
+    top: 60px;
+    height: 60px;
+    width: 100%;
+    padding: 5px 10px;
+    border-radius: 0;
+    border-width: 0;
+    border-bottom-width: 1px;
+    text-indent: 30px;
+    font-size: 18px;
+    font-weight: bold;
 }
 
-.inspector-wrap .header button.preset-reset .col12:last-child {
-    position: absolute;
+/* Feature list */
+
+.feature-list  {
+    width:100%;
+}
+
+.no-results-item,
+.geocode-item,
+.feature-list-item {
     width: 100%;
-    padding: 20px 0;
-    opacity: 0;
+    position: relative;
+    border-bottom: 1px solid #ccc;
+    border-radius: 0;
 }
 
-.inspector-wrap .header button:hover .col12:first-child {
-    opacity: 0;
+.feature-list-item .label {
+    text-align: left;
+    padding: 5px 10px;
+    white-space: nowrap;
+    text-overflow: ellipsis;
+    overflow: hidden;
+    border-left: 1px solid rgba(0, 0, 0, .1);
+    -moz-transition: all 100ms;
+    -o-transition: all 100ms;
+    transition: all 100ms;
 }
 
-.inspector-wrap .header button:hover .col12:last-child {
-    opacity: 1;
+.feature-list-item .label .icon {
+    opacity: .5;
 }
 
-.inspector-wrap .header button.line > div {
-    padding: 0;
+.feature-list-item:hover .label {
+    background-color: #ececec;
 }
 
-.inspector-toggle {
-    color:#fff;
-    width: 100%;
-    display: block;
-    background:#7092ff;
-    border: 0;
+.feature-list-item .entity-type {
+    color:#7092ff;
+}
+
+.feature-list-item:hover .entity-type {
+    color:#597be7;
+}
+
+.feature-list-item .entity-name {
+    font-weight: normal;
+    color: #666;
+    padding-left: 10px;
 }
 
 /* Presets
@@ -1931,30 +2039,26 @@ a:hover .icon.out-link   { background-position: -500px -14px;}
 
 /* Preset grid  */
 
-.preset-grid  {
+.preset-list  {
     width:100%;
     padding: 20px 20px 10px 20px;
     border-bottom: 1px solid #ccc;
 }
 
-.grid-button-wrap {
-    padding-bottom: 10px;
-    height: 70px;
+.preset-list-button-wrap {
+    position: relative;
+    margin-bottom: 10px;
+    height: 60px;
 }
 
-.grid-entry {
+.preset-list-button {
     width: 100%;
     height: 100%;
     position: relative;
     border: 1px solid #ccc;
-    float: left;
-}
-
-.grid-inner {
-    margin-bottom: 20px;
 }
 
-.preset-grid.filtered .grid-button-wrap:first-child .grid-entry {
+.preset-list.filtered .preset-list-item:first-child .preset-list-button {
     background: #ececec;
 }
 
@@ -1965,47 +2069,49 @@ a:hover .icon.out-link   { background-position: -500px -14px;}
     margin: auto;
 }
 
-.preset-icon-line {
+.preset-icon-line,
+.preset-icon-relation {
     top: 0;
     left: 0;
 }
 
-.grid-entry .label {
-    background: #f6f6f6;
+.preset-list-button .label {
+    background-color: #f6f6f6;
     text-align: left;
     position: absolute;
-    padding: 5px 10px;
-    height: 100%;
+    top: 0;
     bottom: 0;
-    left: 60px;
     right: 0;
+    padding: 5px 10px;
+    left: 60px;
+    line-height: 50px;
     white-space: nowrap;
     text-overflow: ellipsis;
     overflow: hidden;
     border-left: 1px solid rgba(0, 0, 0, .1);
-    line-height: 50px;
+    -moz-transition: all 100ms;
+    -o-transition: all 100ms;
+    transition: all 100ms;
+    border-radius: 0 3px 3px 0;
     }
 
-.grid-entry:hover .label {
-    background: none;
+.preset-list-button:hover .label {
+    background-color: #ececec;
 }
 
-.grid-button-wrap button.tag-reference-button {
-    float: right;
-    position: static;
-    margin-top: -60px;
+.preset-list-item button.tag-reference-button {
     height: 100%;
     border: 1px solid #CCC;
     border-radius: 0 3px 3px 0;
 }
 
-.current .grid-entry,
-.current .grid-entry .label {
+.current .preset-list-button,
+.current .preset-list-button .label {
     background-color: #E8EBFF;
 }
 
-.category .grid-entry:after,
-.category .grid-entry:before {
+.category .preset-list-button:after,
+.category .preset-list-button:before {
     content: "";
     position: absolute;
     top: -5px;
@@ -2016,44 +2122,23 @@ a:hover .icon.out-link   { background-position: -500px -14px;}
     height: 6px;
 }
 
-.category .grid-entry:before {
+.category .preset-list-button: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 .preset-grid {
+.subgrid .preset-list {
     padding: 10px 10px 0 10px;
-    border: 1px solid #CCC;
     margin-top: 0;
+    border: 0;
     border-radius: 8px;
+    width: -webkit-calc(100% + 20px);
+    margin-left: -10px;
 }
 
 .subgrid .arrow {
     border: solid rgba(0, 0, 0, 0);
     border-width: 10px;
-    border-bottom-color: #CCC;
+    border-bottom-color: #f1f1f1;
     width: 0;
     height: 0;
     margin-left: 50%;
@@ -2061,23 +2146,8 @@ a:hover .icon.out-link   { background-position: -500px -14px;}
     margin-top: -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;
@@ -2111,12 +2181,17 @@ a:hover .icon.out-link   { background-position: -500px -14px;}
 
 /* preset form basics */
 
-.tag-wrap .preset-icon-wrap {
-    border-bottom: 1px solid #CCC;
-    background-color: #e8ebff;
+.inspector-preset {
+    overflow: hidden;
+}
+
+.inspector-preset form.preset-form {
+    padding: 10px;
+    margin: 0 10px 20px 10px;
+    border-radius: 8px;
 }
 
-.tag-wrap .preset-icon-wrap::after {
+.entity-editor-pane .preset-list-item::after {
     content: "";
     position: absolute;
     height: 0;
@@ -2127,21 +2202,25 @@ a:hover .icon.out-link   { background-position: -500px -14px;}
     margin: auto;
     border: solid rgba(0, 0, 0, 0);
     border-width: 10px;
-    border-bottom-color: #CCC;
+    border-bottom-color: #ececec;
 }
 
-.tag-wrap .preset-icon-wrap > div {
-    height: 60px;
-    width: 60px;
-    margin: auto;
-    border-radius: 4px;
-    border: 1px solid #CCC;
-    position: relative;
+.entity-editor-pane .preset-list-item .preset-list-button-wrap {
+    margin-bottom: 0;
 }
 
-.inspector-preset .form-field {
-    padding-left: 20px;
-    padding-right: 20px;
+
+.form-field {
+    margin-bottom: 10px;
+    width: 100%;
+    -webkit-transition: margin-bottom 200ms;
+       -moz-transition: margin-bottom 200ms;
+         -o-transition: margin-bottom 200ms;
+            transition: margin-bottom 200ms;
+}
+
+.form-field:last-child {
+    margin-bottom: 0;
 }
 
 .form-label {
@@ -2161,11 +2240,13 @@ a:hover .icon.out-link   { background-position: -500px -14px;}
 .form-label .modified-icon {
     border-right: 0;
     opacity: 0;
-    right: 20px;
+    z-index: -10;
+    right: 10%;
 }
 
 .modified .form-label .modified-icon {
-    opacity: .5;
+    opacity: 1;
+    z-index: 1;
 }
 
 .form-label button.tag-reference-button {
@@ -2176,42 +2257,230 @@ a:hover .icon.out-link   { background-position: -500px -14px;}
 .form-field > textarea,
 .form-field .preset-input-wrap {
     border: 1px solid #CCC;
+    min-height: 30px;
     border-top: 0;
     border-radius: 0 0 4px 4px;
+    overflow: hidden;
 }
 
 .form-field textarea {
     height: 65px;
 }
 
-.form-field-name input.localized-main {
-    height: 35px;
-    font-size: 18px;
-    font-weight: bold;
+.form-field button.remove {
+    border-radius: 0 0 4px 0;
 }
 
-/* adding additional preset fields */
+.inspector-border {
+    border-bottom: 1px solid #CCC
+}
 
-.more-buttons {
-    margin-top: 20px;
-    border-top: 1px solid #CCC;
+/* Preset form (hover mode) */
+
+.inspector-hover .checkselect label:last-of-type,
+.inspector-hover .preset-input-wrap .label,
+.inspector-hover input,
+.inspector-hover label {
+    background: #ececec;
 }
 
-.more-buttons:nth-last-child(2) {
-    border-bottom: 1px solid #CCC;
+.inspector-hover a,
+.inspector-hover .checkselect label:last-of-type {
+    color: #666;
+}
+
+/* hide and remove from layout */
+.inspector-hidden,
+.inspector-hover label input[type="checkbox"],
+.inspector-hover label input[type="radio"],
+.inspector-hover .toggle-list label:not(.active),
+.inspector-hover .toggle-list label span,
+.inspector-hover .inspector-inner .add-tag,
+.inspector-hover .inspector-inner .add-relation,
+.inspector-hover .toggle-list label.remove .icon {
+    height: 0;
+    width: 0;
+    overflow: hidden;
+    opacity: 0 !important;
+    border-width: 0;
+    margin: 0;
+    padding: 0;
+}
+
+/* hide but preserve in layout */
+.inspector-hover .modified .form-label .modified-icon,
+.inspector-hover .entity-editor-pane button.minor,
+.inspector-hover .combobox-carat,
+.inspector-hover .entity-editor-pane .header button,
+.inspector-hover .spin-control,
+.inspector-hover .hide-toggle:before,
+.inspector-hover .more-buttons,
+.inspector-hover .view-on-osm {
+    opacity: 0;
+}
+
+/* Styles for raw tag inspector on hover */
+.inspector-hover .tag-row .key-wrap,
+.inspector-hover .tag-row .input-wrap-position {
+    width: 50%;
+}
+
+.inspector-hover .tag-row:first-child input.value {
+    border-top-right-radius: 4px;
+}
+
+.inspector-hover .tag-row:last-child input.value {
+    border-bottom-right-radius: 4px;
+}
+
+.inspector-hover .tag-row:last-child input.key {
+    border-bottom-left-radius: 4px;
+}
+
+.inspector-hover .inspector-body .more-buttons {
+    max-height: 0;
+    padding-bottom: 0;
+}
+
+/* Unstyle button fields */
+.inspector-hover .toggle-list label.active,
+.inspector-hover .entity-editor-pane a.hide-toggle {
+    opacity: 1;
+    background-color: transparent;
+    color: #666;
+    padding-left: 0;
+    border-width: 0;
+}
+
+.inspector-hover .toggle-list button.active {
+    padding-left: 10px;
+}
+
+/* Add placeholder only on hover for radio buttons */
+.inspector-hover .toggle-list .placeholder {
+    color: #a9a9a9;
+    padding: 5px 10px;
+    opacity: 1;
+    line-height: 20px;
+    width: 100%;
+}
+
+/* Hide placeholder for radio buttons if another is active, or not in hover state */
+.toggle-list label.active ~ .placeholder,
+.toggle-list .placeholder {
+    padding: 0;
+    opacity: 0;
+    width: 0;
+    line-height: 0;
+    display: block;
+    overflow: hidden;
+    -webkit-transition: opacity 200ms, width 0 200ms, padding 0 200ms, line-height 0 200ms;
+       -moz-transition: opacity 200ms, width 0 200ms, padding 0 200ms, line-height 0 200ms;
+         -o-transition: opacity 200ms, width 0 200ms, padding 0 200ms, line-height 0 200ms;
+            transition: opacity 200ms, width 0 200ms, padding 0 200ms, line-height 0 200ms;
+}
+
+/* first phase hover-to-active animations */
+
+textarea,
+.form-label,
+.preset-input-wrap,
+.preset-input-wrap .label {
+    -webkit-transition: all 200ms;
+       -moz-transition: all 200ms;
+         -o-transition: all 200ms;
+            transition: all 200ms;
+}
+
+/* second phase hover-to-active animations */
+
+input,
+.checkselect label:last-of-type {
+    -webkit-transition: opacity 200ms 200ms, width 200ms 200ms, margin-right 200ms 200ms;
+       -moz-transition: opacity 200ms 200ms, width 200ms 200ms, margin-right 200ms 200ms;
+         -o-transition: opacity 200ms 200ms, width 200ms 200ms, margin-right 200ms 200ms;
+            transition: opacity 200ms 200ms, width 200ms 200ms, margin-right 200ms 200ms;
+}
+
+.modified .form-label .modified-icon,
+.entity-editor-pane button.minor,
+.combobox-carat,
+.entity-editor-pane .header button,
+.toggle-list label span,
+.spin-control,
+.more-buttons,
+.view-on-osm,
+.hide-toggle:before,
+.entity-editor-pane .toggle-list label::before,
+.entity-editor-pane .toggle-list label.remove .icon {
+    -webkit-transition: opacity 200ms 200ms;
+       -moz-transition: opacity 200ms 200ms;
+         -o-transition: opacity 200ms 200ms;
+            transition: opacity 200ms 200ms;
+}
+
+.entity-editor-pane a.hide-toggle {
+    -webkit-transition: padding-left 200ms 200ms, color 200ms 200ms;
+       -moz-transition: padding-left 200ms 200ms, color 200ms 200ms;
+         -o-transition: padding-left 200ms 200ms, color 200ms 200ms;
+            transition: padding-left 200ms 200ms, color 200ms 200ms;
+}
+
+.entity-editor-pane .toggle-list label:not(.active) {
+    -webkit-transition: height 200ms 200ms, padding 200ms 200ms, border-width 100ms 300ms;
+       -moz-transition: height 200ms 200ms, padding 200ms 200ms, border-width 100ms 300ms;
+         -o-transition: height 200ms 200ms, padding 200ms 200ms, border-width 100ms 300ms;
+            transition: height 200ms 200ms, padding 200ms 200ms, border-width 100ms 300ms;
+}
+
+.entity-editor-pane .toggle-list label {
+    -webkit-transition: border-width 100ms 300ms, padding 200ms 200ms, background-color 200ms 200ms, color 200ms 200ms;
+       -moz-transition: border-width 100ms 300ms, padding 200ms 200ms, background-color 200ms 200ms, color 200ms 200ms;
+         -o-transition: border-width 100ms 300ms, padding 200ms 200ms, background-color 200ms 200ms, color 200ms 200ms;
+            transition: border-width 100ms 300ms, padding 200ms 200ms, background-color 200ms 200ms, color 200ms 200ms;
+}
+
+/* adding additional preset fields */
+
+.inspector-body .more-buttons {
+    max-height: 100px;
+    padding-top: 0;
+    -webkit-transition: padding 200ms 200ms, max-height 200ms 200ms;
+       -moz-transition: padding 200ms 200ms, max-height 200ms 200ms;
+         -o-transition: padding 200ms 200ms, max-height 200ms 200ms;
+            transition: padding 200ms 200ms, max-height 200ms 200ms;
 }
 
 button.preset-add-field {
+    background: #f6f6f6;
     width: 25%;
     height: 40px;
     -webkit-transition: width 200ms;
-    -moz-transition: width 200ms;
-    -o-transition: width 200ms;
-    transition: width 200ms;
+       -moz-transition: width 200ms;
+         -o-transition: width 200ms;
+            transition: width 200ms;
+}
+
+button.preset-add-field:hover {
+    background: #ececec;
 }
 
 /* set width based on # of buttons */
 
+button.preset-add-field:only-child {
+    width: 100%;
+}
+
+button.preset-add-field:nth-last-child(2),
+button.preset-add-field:nth-last-child(2) ~ button.preset-add-field {
+    width: 50%;
+}
+
+button.preset-add-field:nth-last-child(3),
+button.preset-add-field:nth-last-child(3) ~ button.preset-add-field {
+    width: 33.3333%;
+}
+
 button.preset-add-field:nth-last-child(4),
 button.preset-add-field:nth-last-child(4) ~ button.preset-add-field {
     width: 25%;
@@ -2237,15 +2506,6 @@ 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;
@@ -2254,18 +2514,19 @@ button.preset-add-field:nth-last-child(8) ~ button.preset-add-field {
 
 /* preset form access */
 
-.preset-input-wrap li {
+.preset-input-wrap .label {
+    height: 30px;
+    background: #F6F6F6;
+    padding: 5px 10px;
+}
+
+.form-field-access .preset-input-wrap li {
     border-bottom: 1px solid #CCC;
 }
-.preset-input-wrap li:last-child {
+.form-field-access .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;
@@ -2280,28 +2541,26 @@ button.preset-add-field:nth-last-child(8) ~ button.preset-add-field {
 
 input[type=number] {
     position: relative;
-    padding-right: 65px;
+    padding-right: 20%;
 }
 
 .spin-control {
-    width: 41px;
+    width: 20%;
     height: 29px;
-    border-left: 1px solid #CCC;
     display: inline-block;
-    margin-left: -41px;
+    margin-left: -20%;
     margin-bottom: -11px;
     position: relative;
 }
 
 .spin-control button {
+    right: 1px;
     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);
 }
 
@@ -2334,9 +2593,9 @@ input[type=number] {
 
 .checkselect label:last-of-type {
     display: block;
-    padding: 5px;
-    box-sizing: border-box;
-    color: #999;
+    background: white;
+    padding: 5px 10px;
+    color: #7092FF;
 }
 
 .checkselect label:hover {
@@ -2347,175 +2606,121 @@ input[type=number] {
     color: inherit;
 }
 
-.checkselect input[type="checkbox"] {
-    margin-right: 5px;
-    width: 20px;
-    vertical-align: middle;
-    opacity: 0.5;
-}
-
-.checkselect .set input[type="checkbox"] {
-    opacity: 1;
+.checkselect label:not(.set) input[type="checkbox"] {
+    opacity: .5;
 }
 
 /* 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;
+.toggle-list button.remove {
+    border-radius: 0 0 3px 3px;
 }
 
-.radio-wrap button::before {
-    content: "";
-    display: inline-block;
-    border-radius: 50%;
-    height: 12px;
-    width: 12px;
-    margin-right: 10px;
-    border: 1px solid #CCC;
+.toggle-list button.remove .icon {
     position: absolute;
     left: 5px;
-    top: 8px;
 }
 
-.radio-wrap button:hover::before {
-    box-shadow: inset 0 0 0 2px white;
+.toggle-list button.remove::before {
+    content: none;
 }
 
-.radio-wrap button.active::before {
-    background: #7092ff;
-    box-shadow: inset 0 0 0 2px white;
+.form-field .wiki-lang {
+    border-radius: 0;
 }
 
-.radio-wrap button:last-child {
-    border-bottom: 0;
+.form-field .wiki-title {
+    padding-right: 10%;
 }
 
-.radio-wrap button.active {
-    background-color: #E8EBFF !important;
+.form-field .wiki-title ~ .combobox-carat {
+    margin-left: -18%;
+    margin-right: 9%;
 }
 
-.radio-wrap button.remove {
-    border-radius: 0 0 3px 3px;
-}
-.radio-wrap button.remove .icon {
-    position: absolute;
-    left: 2px;
+.form-field .wiki-link {
+    float: right;
+    background: #fafafa;
+    padding: 5px;
+    text-align: center;
 }
 
-.radio-wrap button.remove::before {
-    content: none;
+.form-field .wiki-link:hover {
+    background: #f1f1f1;
 }
 
-.form-field .localized-main {
-    width: 90%;
+#preset-input-maxspeed {
+    border-right: none;
     border-radius: 0 0 0 4px;
+    width: 80%;
 }
 
-.form-field .localized-add {
-    width: 10%;
-    height: 35px;
+.form-field .maxspeed-unit {
     border-radius: 0 0 4px 0;
-    border-bottom: 1px solid #ccc;
-    border-right: 1px solid #ccc;
-    vertical-align: top;
+    width: 20%;
 }
 
-.form-field .localized-wrap .entry .localized-lang {
-    border-top: none;
-    border-right: none;
-    border-radius: 0;
-    width: 30%;
-}
+/* Name + translate form */
 
-.form-field .localized-wrap .entry .localized-value {
-    border-top: none;
-    border-radius: 0;
-    width: 60%;
+.form-field .localized-main {
+    padding-right: 10%;
 }
 
-.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;
+.form-field .button-input-action {
+    position: relative;
+    right: 1px;
     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 {
+    margin-left: -10%;
+    border: 1px solid #CCC;
+    border-top-width: 0;
+    border-right-width: 0;
     border-radius: 0 0 4px 0;
+    height: 30px;
+    vertical-align: top;
 }
 
-.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;
+.form-field .localized-wrap .entry {
+    position: relative;
+    overflow: hidden;
+    background: #f1f1f1;
     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;
+    border-width: 0;
+    border-radius: 4px;
 }
 
-.form-field .wiki-link:hover {
-    background: #ececec;
+.form-field .localized-wrap .entry::before {
+    content: "";
+    display: block;
+    position: absolute;
+    background:#ccc;
+    height: 11px;
+    width: 1px;
+    left: 0;
+    right: 0;
+    top: -11px;
+    margin: auto;
 }
 
-#preset-input-maxspeed {
-    border-right: none;
-    border-radius: 0 0 0 4px;
-    width: 80%;
+.form-field .localized-wrap .entry .localized-lang {
+    border-radius: 0;
+    border-top-width: 0;
 }
 
-.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;
+.form-field .localized-wrap .entry .localized-value {
+    padding-right: 10%;
+    border-top-width: 0;
+    border-radius: 0 0 4px 4px;
 }
 
 /* Preset form address */
 
 .form-field .addr-housename {
     border: 0;
+    border-radius: 0;
 }
 
 .form-field .addr-number {
-    width: 33%;
+    width: 33.3333%;
     border-left: 0;
     border-right: 0;
     border-bottom: 0;
@@ -2523,14 +2728,14 @@ input[type=number] {
 }
 
 .form-field .addr-street {
-    width: 66%;
+    width: 66.6666%;
     border-right: 0;
     border-bottom: 0;
     border-radius: 0;
 }
 
 .form-field .addr-city {
-    width: 66%;
+    width: 66.6666%;
     border-left: 0;
     border-right: 0;
     border-bottom: 0;
@@ -2538,7 +2743,7 @@ input[type=number] {
 }
 
 .form-field .addr-postcode {
-    width: 33%;
+    width: 33.3333%;
     border-right: 0;
     border-bottom: 0;
     border-radius: 0 0 4px 0;
@@ -2585,14 +2790,10 @@ div.combobox {
     border-right: 5px solid transparent;
 }
 
-/* tag editor */
-
-.inspector-inner.additional-tags {
-    border-top: 1px solid #ccc;
-}
+/* Raw Tag Editor */
 
 .tag-list {
-    margin-right: 40px;
+    padding-top: 10px;
 }
 
 .tag-row {
@@ -2602,13 +2803,26 @@ div.combobox {
 }
 
 .tag-row input {
+    height: 31px;
     border: 0;
     border-radius: 0;
     border-bottom: 1px solid #CCC;
     border-left: 1px solid #CCC;
 }
 
+.tag-row .key-wrap,
+.tag-row .input-wrap-position {
+    width: 40%;
+    float: left;
+    height: 30px;
+    -webkit-transition: width 200ms;
+       -moz-transition: width 200ms;
+         -o-transition: width 200ms;
+            transition: width 200ms;
+}
+
 .tag-row input.key {
+    font-weight: bold;
     background-color: #f6f6f6;
 }
 
@@ -2627,13 +2841,11 @@ div.combobox {
 
 .tag-row button {
     position: absolute;
-    height: 30px;
-    right: -20px;
+    height: 31px;
+    right: 10%;
     border: 1px solid #CCC;
-    border-top-width: 0; border-left-width: 0;
-    border-radius: 0;
-    opacity: 1;
-    background: #fafafa;
+    border-top-width: 0;
+    border-left-width: 0;
 }
 
 .tag-row button:hover {
@@ -2648,23 +2860,22 @@ div.combobox {
     border-top-width: 1px;
 }
 
-.tag-row:first-child button.tag-help-button {
+.tag-row:first-child .tag-reference-button {
     border-top-right-radius: 4px;
 }
 
-.tag-row:last-child button.tag-help-button {
+.tag-row:last-child .tag-reference-button {
     border-bottom-right-radius: 4px;
 }
 
-.tag-row button.tag-help-button {
-    right: -40px;
+.tag-row .tag-reference-button {
+    right: 0;
 }
 
 /* Adding form fields to tag editor */
 
 .inspector-inner .add-tag {
-    width: -webkit-calc(50% - 20px);
-    width: calc(50% - 20px);
+    width: 40%;
     height: 30px;
     border-top: 0;
     background: rgba(0,0,0,.5);
@@ -2681,37 +2892,59 @@ div.combobox {
 
 /* Tag reference */
 
-.preset-inspect {
-    position: relative;
+button.minor.tag-reference-loading {
+    background-color: #f5f5f5;
+}
+
+.tag-reference-loading .icon {
+    background-image: url(<%= asset_path("iD/img/mini-loader.gif") %>);
+    background-position: 0 0;
 }
 
-.tag-help {
+.tag-reference-body {
     overflow: hidden;
 }
 
-.tag-help a {
-    margin-top: 5px;
+.tag-reference-body p,
+.tag-reference-body img {
+    margin-top: 20px;
+}
+
+.tag-reference-body p:last-child {
+    padding-bottom: 10px;
+}
+
+.tag-reference-body a {
     display: block;
+    padding-bottom: 10px;
 }
 
-.grid-pane .tag-reference-wrap {
-    padding: 10px 0 20px 0;
+.preset-list .tag-reference-body {
+    position: relative;
+    width: 100%;
 }
 
-.tag-pane .tag-reference-wrap {
-    padding-top: 20px;
+.preset-list .tag-reference-body a {
+    padding-bottom: 20px;
 }
 
-.additional-tags .tag-reference-wrap {
-    border-bottom: 1px solid #ccc;
-    padding: 20px 0;
+.preset-list .tag-reference-body p,
+.preset-list .tag-reference-body img {
+    margin-top: 10px;
 }
 
-.additional-tags div.tag-help {
+.raw-tag-editor .tag-reference-body {
+    border-bottom: 1px solid #ccc;
     float: left;
     width: 100%;
-    width: -webkit-calc(100% + 40px);
-    width: calc(100% + 40px);
+}
+
+.raw-tag-editor .tag-reference-body p:last-child {
+    padding-bottom: 20px;
+}
+
+.raw-tag-editor .tag-reference-body a {
+    padding-bottom: 20px;
 }
 
 img.wiki-image {
@@ -2723,20 +2956,56 @@ img.wiki-image {
     margin-right: 10px;
     border-radius: 4px;
     max-height: 200px;
+    margin-bottom: 20px;
+}
+
+/* Raw relation membership editor */
+
+.raw-member-editor .member-list li:first-child,
+.raw-membership-editor .member-list li:first-child {
+    padding-top: 10px;
+}
+
+.raw-member-editor .member-row,
+.raw-membership-editor .member-row {
+    position: relative;
+}
+
+.raw-member-editor .member-row .member-entity-name,
+.raw-membership-editor .member-row .member-entity-name {
+    font-weight: normal;
+    padding-left: 10px;
+}
+
+.member-row-new .member-entity-input {
+    border-radius: 4px 4px 0 0;
+    border: 1px solid #cfcfcf;
+}
+
+.add-relation {
+    width: 40%;
+    height: 30px;
+    background: rgba(0,0,0,.5);
+    border-radius: 4px;
+    margin-top: 10px;
+}
+
+.add-relation:hover {
+    background: rgba(0,0,0,.8);
 }
 
 /* Map Controls */
 
 .map-controls {
-    left: 0;
-    top: 80px;
-    width: 30px;
-    position: absolute;
+    right: 0;
+    top: 70px;
+    width: 40px;
+    position: fixed;
     z-index: 100;
 }
 
 .map-control > button {
-    width: 30px;
+    width: 40px;
     background: rgba(0,0,0,.5);
     border-radius: 0;
 }
@@ -2763,19 +3032,34 @@ img.wiki-image {
 /* Zoomer */
 
 .zoombuttons button.zoom-in {
-    border-radius:0 4px 0 0;
+    border-radius: 4px 0 0 0;
 }
 
 /* Background Settings */
 
+
+.toggle-list.layer-list {
+    margin-bottom: 10px;
+}
+
 .background-control button {
-    border-radius:0 4px 0 0;
+    border-radius: 4px 0 0 0;
 }
 
+
 .background-control button.active {
     border-radius: 0;
 }
 
+.background-control {
+    position: relative;
+}
+
+.background-control .map-overlay {
+    position: absolute;
+    top: 0;
+}
+
 .nudge-container {
     border-top: 1px solid #CCC;
     margin: 0 -10px;
@@ -2785,9 +3069,13 @@ img.wiki-image {
     border: 0;
 }
 
+.background-control .hide-toggle  {
+    padding-bottom: 10px;
+}
+
 .hide-toggle {
     display: block;
-    padding: 0 0 10px 12px;
+    padding-left:12px;
     position: relative;
 }
 
@@ -2898,72 +3186,35 @@ img.wiki-image {
 
 .background-control .layer-toggle-gpx .layer-extent {
     border-left: 1px solid #CCC;
+    border-radius: 0 4px 4px 0;
 }
 
 .background-control .layer-toggle-gpx.selected .layer-extent {
     display:inline-block;
 }
 
-/* Geocoder */
-
-.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;
-    width: 100%;
-}
-.geocode-control div.map-overlay span.not-found {
-    line-height: 28px;
-    width: 100%;
-}
+/* Geolocator */
 
-.geocode-control a:focus {
-    text-decoration: underline;
+.geolocate-control {
+    margin-bottom: 10px;
 }
 
-/* Geolocator */
-
 .geolocate-control button {
-    border-radius: 0 0 4px 0;
+    border-radius: 0 0 0 4px;
 }
 
 /* Help */
 
-.help-control {
-    margin-bottom: 20px;
-}
-
 .help-control button {
-    border-radius: 0 0 4px 0;
+    border-radius: 0 0 0 4px;
 }
 
 .help-wrap {
-    position: absolute;
+    position: fixed;
     top:60px;
     bottom: 30px;
-    padding: 20px 20px 20px 50px;
-    left: 0;
+    padding: 20px 50px 20px 20px;
+    right: 0;
     overflow-y: scroll;
 }
 
@@ -3050,13 +3301,9 @@ img.wiki-image {
 ------------------------------------------------------- */
 
 #map {
-    display:block;
-    position:absolute;
+    position:relative;
     overflow:hidden;
-    top:0;
-    left:0;
-    right:0;
-    bottom:0;
+    height:100%;
     background:#000;
 }
 
@@ -3085,11 +3332,8 @@ img.wiki-image {
 
 .about-block {
     position: absolute;
-    right:0px;
-    bottom:0px;
-    overflow: hidden;
-    white-space: nowrap;
-    text-overflow: ellipsis;
+    right:0;
+    bottom:0;
     border-radius: 0;
     opacity: .625;
     -webkit-transition: opacity 200ms;
@@ -3181,8 +3425,10 @@ img.wiki-image {
 .shaded {
     z-index: 2;
     position: absolute;
-    height: 100%;
-    width: 100%;
+    top: 0;
+    bottom: 0;
+    left: 0;
+    right: 0;
     overflow: auto;
 }
 
@@ -3207,32 +3453,24 @@ img.wiki-image {
 }
 
 .modal-actions button,
-.modal-actions a {
-    background-size: white;
+.save-success a {
     font-weight: normal;
     color: #7092FF;
     border-bottom: 1px solid #CCC;
     border-radius: 0;
-    height: 180px;
+    height: 160px;
     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 {
+.modal-actions button:hover
+.save-success a:hover {
     background-color: #ececec;
 }
 
-.modal-actions a:before,
 .modal-actions button:before,
+.save-success a:before,
 .walkthrough a:before {
-    background-size: white;
     display: block;
     content: '';
     height: 100px;
@@ -3260,15 +3498,19 @@ img.wiki-image {
 /* Success Modal
 ------------------------------------------------------- */
 
-.modal-actions .twitter:before {
-    background-position: -100px -220px;
+.save-success a {
+    padding-top: 15px;
 }
 
-.modal-actions .facebook {
-    border-left: 1px solid #ccc;
+.save-success .osm:before {
+    background-position: 0px -220px;
 }
 
-.modal-actions .facebook:before {
+.save-success .twitter:before {
+    background-position: -100px -220px;
+}
+
+.save-success .facebook:before {
     background-position: -200px -220px;
 }
 
@@ -3287,20 +3529,25 @@ img.wiki-image {
 /* Commit Modal
 ------------------------------------------------------- */
 
-.commit-modal a.user-info {
+.mode-save a.user-info {
     display: inline-block;
 }
 
-.commit-modal .commit-info {
-    margin-top: 10px;
-    padding-bottom: 20px;
+.mode-save .commit-form {
+    margin-bottom: 0;
+}
+
+.mode-save button.action {
+    float: none;
+    margin: auto;
+    display: block;
 }
 
-.commit-modal .user-info img {
+.mode-save .user-info img {
     float: left;
 }
 
-.commit-modal h3 small.count {
+.mode-save h3 small.count {
     margin-right: 10px;
     text-align: center;
     float: left;
@@ -3314,7 +3561,7 @@ img.wiki-image {
     color:#fff;
 }
 
-.commit-modal .changeset-list {
+.mode-save .changeset-list {
     overflow: auto;
     border:1px solid #ccc;
     border-radius: 4px;
@@ -3322,11 +3569,11 @@ img.wiki-image {
     max-height: 160px;
 }
 
-.commit-modal .warning-section .changeset-list button {
+.mode-save .warning-section .changeset-list button {
     border-left: 1px solid #CCC;
 }
 
-.commit-modal .changeset-list li {
+.mode-save .changeset-list li {
     position: relative;
     border-top:1px solid #ccc;
     padding:5px 10px;
@@ -3347,18 +3594,21 @@ img.wiki-image {
 ------------------------------------------------------- */
 
 .notice {
-    float:left;
-    width:25%;
-    padding-right: 10px;
-    text-align:center;
+    position: absolute;
+    top: 0;
+    bottom: 0;
+    left: 0;
+    right: 0;
+    text-align: center;
+    background: #fff;
 }
 
 .notice .zoom-to {
-    width:100%;
-    height: 40px;
+    margin: auto;
+    width: 80%;
+    height: 100px;
     border-radius: 5px;
     line-height: 40px;
-    background: #fff;
     color: #000;
     opacity: 0.9;
 }
@@ -3380,7 +3630,6 @@ img.wiki-image {
 ------------------------------------------------------- */
 
 .tooltip {
-    width: 200px;
     position: absolute;
     display: none;
     color:#333;
@@ -3411,10 +3660,13 @@ img.wiki-image {
 
 .tooltip.left {
     margin-left: -20px;
+    text-align: right;
 }
 
 .tooltip-inner {
     display: inline-block;
+    max-width: 200px;
+    min-width: 80px;
     padding: 10px;
     font-weight: normal;
     background-color: white;
@@ -3508,22 +3760,13 @@ img.wiki-image {
     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;
+.keyhint-wrap {
+    background: #F6F6F6;
+    padding: 10px;
+    margin: 10px -10px -10px;
 }
 
 .tooltip-inner .keyhint {
-    color: #222;
     font-size: 10px;
     padding: 0 7px;
     font-weight: bold;
@@ -3533,11 +3776,6 @@ img.wiki-image {
     position: relative;
     z-index: 1;
     text-align: left;
-    clear: both;
-}
-
-.tooltip .keyhint .keyhint-label {
-    display: inline-block;
 }
 
 .tooltip-inner .keyhint::after {
@@ -3553,8 +3791,46 @@ img.wiki-image {
     border-top: 0;
 }
 
+/* Exceptions for tooltip layouts */
+
+/* make tooltips in editor pane dark */
+.entity-editor-pane .tooltip.top .tooltip-arrow {
+    border-top-color: #000;
+}
+
+.entity-editor-pane .tooltip.bottom .tooltip-arrow {
+    border-bottom-color: #000;
+}
+
+.entity-editor-pane .tooltip.left .tooltip-arrow {
+    border-left-color: #000;
+}
+
+.entity-editor-pane .tooltip.right .tooltip-arrow {
+    border-right-color: #000;
+}
+
+.entity-editor-pane .tooltip-inner {
+    background: #000;
+    color: #ccc;
+}
+
+/* Uncramp map-control tooltips */
+.map-control .tooltip {
+    min-width: 160px;
+}
+/* Move over tooltips that are near the edge of screen */
+.add-point .tooltip {
+    left: 33.3333% !important; 
+}
+
+.curtain-tooltip.intro-points-add .tooltip-arrow,
+.add-point .tooltip .tooltip-arrow {
+    left: 60px;
+}
+
 .radial-menu-tooltip {
-    background-color: rgba(255, 255, 255, 0.8);
+    opacity: 0.8;
     display: none;
     position: absolute;
     width: 200px;
@@ -3598,7 +3874,6 @@ img.wiki-image {
 ------------------------------------------------------- */
 
 @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;}
@@ -3703,6 +3978,7 @@ img.wiki-image {
 .intro-points-add .tooltip-inner::before,
 .intro-areas-add .tooltip-inner::before,
 .intro-lines-add .tooltip-inner::before {
+    margin-left: -20px;
     display: block;
     content: "";
     height: 80px;
@@ -3734,6 +4010,7 @@ img.wiki-image {
 
 .preset-icon{background-image:url(<%= asset_path("iD/img/maki-sprite.png") %>);background-repeat:no-repeat;width:24px;height:24px;}
 .preset-icon-line{background-image:url(<%= asset_path("iD/img/line-presets.png") %>);background-repeat:no-repeat;width:60px;height:60px;}
+.preset-icon-relation{background-image:url(<%= asset_path("iD/img/relation-presets.png") %>);background-repeat:no-repeat;width:60px;height:60px;}
 .feature-airfield{background-position:-0px -34px;}
 .feature-airport{background-position:-0px -94px;}
 .feature-alcohol-shop{background-position:-0px -154px;}
@@ -3864,3 +4141,21 @@ img.wiki-image {
 .preset-icon-line.feature-category-rail{background-position:-2060px -25px;}
 .preset-icon-line.feature-category-path{background-position:-2120px -25px;}
 .preset-icon-line.feature-category-water{background-position:-2180px -25px;}
+.preset-icon-line.feature-ferry{background-position:-2240px -25px;}
+.preset-icon-line.feature-pipeline{background-position:-2300px -25px;}
+.preset-icon-relation.feature-relation{background-position:-20px -25px;}
+.preset-icon-relation.feature-restriction{background-position:-80px -25px;}
+.preset-icon-relation.feature-multipolygon{background-position:-140px -25px;}
+.preset-icon-relation.feature-boundary{background-position:-200px -25px;}
+.preset-icon-relation.feature-route{background-position:-260px -25px;}
+.preset-icon-relation.feature-route-road{background-position:-320px -25px;}
+.preset-icon-relation.feature-route-bicycle{background-position:-380px -25px;}
+.preset-icon-relation.feature-route-foot{background-position:-440px -25px;}
+.preset-icon-relation.feature-route-bus{background-position:-500px -25px;}
+.preset-icon-relation.feature-route-train{background-position:-560px -25px;}
+.preset-icon-relation.feature-route-detour{background-position:-620px -25px;}
+.preset-icon-relation.feature-route-tram{background-position:-680px -25px;}
+.preset-icon-relation.feature-route-ferry{background-position:-740px -25px;}
+.preset-icon-relation.feature-route-power{background-position:-800px -25px;}
+.preset-icon-relation.feature-route-pipeline{background-position:-860px -25px;}
+.preset-icon-relation.feature-route-master{background-position:-920px -25px;}
index a663c72997d0caa03a01d6b8d1e2e20e08da9fb5..3cb6da7cca5e96c53ba22e00a79845b122d13887 100644 (file)
 
 })(this);
 d3 = (function(){
-  var d3 = {version: "3.1.5"}; // semver
+  var d3 = {version: "3.2.7"}; // semver
 d3.ascending = function(a, b) {
   return a < b ? -1 : a > b ? 1 : a >= b ? 0 : NaN;
 };
@@ -188,10 +188,10 @@ d3.min = function(array, f) {
       a,
       b;
   if (arguments.length === 1) {
-    while (++i < n && ((a = array[i]) == null || a != a)) a = undefined;
+    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 && !((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;
@@ -202,10 +202,10 @@ d3.max = function(array, f) {
       a,
       b;
   if (arguments.length === 1) {
-    while (++i < n && ((a = array[i]) == null || a != a)) a = undefined;
+    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 && !((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;
@@ -217,13 +217,13 @@ d3.extent = function(array, f) {
       b,
       c;
   if (arguments.length === 1) {
-    while (++i < n && ((a = c = array[i]) == null || a != a)) a = c = undefined;
+    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 && !((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;
@@ -574,6 +574,7 @@ d3_class(d3_Set, {
 });
 d3.behavior = {};
 var d3_document = document,
+    d3_documentElement = d3_document.documentElement,
     d3_window = window;
 // Copies a variable number of methods from source to target.
 d3.rebind = function(target, source) {
@@ -592,6 +593,36 @@ function d3_rebind(target, source, method) {
   };
 }
 
+function d3_vendorSymbol(object, name) {
+  if (name in object) return name;
+  name = name.charAt(0).toUpperCase() + name.substring(1);
+  for (var i = 0, n = d3_vendorPrefixes.length; i < n; ++i) {
+    var prefixName = d3_vendorPrefixes[i] + name;
+    if (prefixName in object) return prefixName;
+  }
+}
+
+var d3_vendorPrefixes = ["webkit", "ms", "moz", "Moz", "o", "O"];
+
+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_documentElement.childNodes)[0].nodeType;
+} catch(e) {
+  d3_array = d3_arrayCopy;
+}
+function d3_noop() {}
+
 d3.dispatch = function() {
   var dispatch = new d3_dispatch,
       i = -1,
@@ -662,9 +693,13 @@ function d3_dispatch_event(dispatch) {
 
 d3.event = null;
 
+function d3_eventPreventDefault() {
+  d3.event.preventDefault();
+}
+
 function d3_eventCancel() {
-  d3.event.stopPropagation();
   d3.event.preventDefault();
+  d3.event.stopPropagation();
 }
 
 function d3_eventSource() {
@@ -673,15 +708,6 @@ function d3_eventSource() {
   return e;
 }
 
-// Registers an event listener for the specified target that cancels the next
-// event for the specified type, but only if it occurs immediately. This is
-// useful to disambiguate dragging from clicking.
-function d3_eventSuppress(target, type) {
-  function off() { target.on(type, null); }
-  target.on(type, function() { d3_eventCancel(); off(); }, true);
-  setTimeout(off, 0); // clear the handler if it doesn't fire
-}
-
 // 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
@@ -718,89 +744,31 @@ function d3_eventDispatch(target) {
 
   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];
+d3.requote = function(s) {
+  return s.replace(d3_requote_re, "\\$&");
 };
 
-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__?
+var d3_requote_re = /[\\\^\$\*\+\?\|\[\]\(\)\.\{\}]/g;
+var d3_subclass = {}.__proto__?
 
 // Until ECMAScript supports array subclassing, prototype injection works well.
-function(array, prototype) {
-  array.__proto__ = prototype;
+function(object, prototype) {
+  object.__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(object, prototype) {
+  for (var property in prototype) object[property] = prototype[property];
 };
 
 function d3_selection(groups) {
-  d3_arraySubclass(groups, d3_selectionPrototype);
+  d3_subclass(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_selectMatcher = d3_documentElement[d3_vendorSymbol(d3_documentElement, "matchesSelector")],
     d3_selectMatches = function(n, s) { return d3_selectMatcher.call(n, s); };
 
 // Prefer Sizzle, if available.
@@ -810,13 +778,11 @@ if (typeof Sizzle === "function") {
   d3_selectMatches = Sizzle.matchesSelector;
 }
 
-var d3_selectionPrototype = [];
-
 d3.selection = function() {
   return d3_selectionRoot;
 };
 
-d3.selection.prototype = d3_selectionPrototype;
+var d3_selectionPrototype = d3.selection.prototype = [];
 
 
 d3_selectionPrototype.select = function(selector) {
@@ -826,14 +792,14 @@ d3_selectionPrototype.select = function(selector) {
       group,
       node;
 
-  if (typeof selector !== "function") selector = d3_selection_selector(selector);
+  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));
+        subgroup.push(subnode = selector.call(node, node.__data__, i, j));
         if (subnode && "__data__" in node) subnode.__data__ = node.__data__;
       } else {
         subgroup.push(null);
@@ -845,7 +811,7 @@ d3_selectionPrototype.select = function(selector) {
 };
 
 function d3_selection_selector(selector) {
-  return function() {
+  return typeof selector === "function" ? selector : function() {
     return d3_select(selector, this);
   };
 }
@@ -855,12 +821,12 @@ d3_selectionPrototype.selectAll = function(selector) {
       subgroup,
       node;
 
-  if (typeof selector !== "function") selector = d3_selection_selectorAll(selector);
+  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)));
+        subgroups.push(subgroup = d3_array(selector.call(node, node.__data__, i, j)));
         subgroup.parentNode = node;
       }
     }
@@ -870,7 +836,7 @@ d3_selectionPrototype.selectAll = function(selector) {
 };
 
 function d3_selection_selectorAll(selector) {
-  return function() {
+  return typeof selector === "function" ? selector : function() {
     return d3_selectAll(selector, this);
   };
 }
@@ -959,11 +925,6 @@ function d3_selection_attr(name, value) {
 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) {
@@ -1134,8 +1095,8 @@ 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; })
+      ? function() { if (this.textContent !== "") this.textContent = ""; }
+      : function() { if (this.textContent !== value) this.textContent = value; })
       : this.node().textContent;
 };
 
@@ -1148,40 +1109,25 @@ d3_selectionPrototype.html = function(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);
+  name = d3_selection_creator(name);
+  return this.select(function() {
+    return this.appendChild(name.apply(this, arguments));
+  });
 };
 
-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));
-  }
+function d3_selection_creator(name) {
+  return typeof name === "function" ? name
+      : (name = d3.ns.qualify(name)).local ? function() { return d3_document.createElementNS(name.space, name.local); }
+      : function() { return d3_document.createElementNS(this.namespaceURI, name); };
+}
 
-  return this.select(name.local ? insertNS : insert);
+d3_selectionPrototype.insert = function(name, before) {
+  name = d3_selection_creator(name);
+  before = d3_selection_selector(before);
+  return this.select(function() {
+    return this.insertBefore(name.apply(this, arguments), before.apply(this, arguments));
+  });
 };
 
 // TODO remove(selector)?
@@ -1367,7 +1313,140 @@ function d3_selection_sortComparator(comparator) {
     return (!a - !b) || comparator(a.__data__, b.__data__);
   };
 }
-function d3_noop() {}
+
+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;
+};
+
+d3_selectionPrototype.size = function() {
+  var n = 0;
+  this.each(function() { ++n; });
+  return n;
+};
+
+function d3_selection_enter(selection) {
+  d3_subclass(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.empty = d3_selectionPrototype.empty;
+d3_selection_enterPrototype.node = d3_selectionPrototype.node;
+d3_selection_enterPrototype.call = d3_selectionPrototype.call;
+d3_selection_enterPrototype.size = d3_selectionPrototype.size;
+
+
+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, j));
+        subnode.__data__ = node.__data__;
+      } else {
+        subgroup.push(null);
+      }
+    }
+  }
+
+  return d3_selection(subgroups);
+};
+
+d3_selection_enterPrototype.insert = function(name, before) {
+  if (arguments.length < 2) before = d3_selection_enterInsertBefore(this);
+  return d3_selectionPrototype.insert.call(this, name, before);
+};
+
+function d3_selection_enterInsertBefore(enter) {
+  var i0, j0;
+  return function(d, i, j) {
+    var group = enter[j].update,
+        n = group.length,
+        node;
+    if (j != j0) j0 = j, i0 = 0;
+    if (i >= i0) i0 = i + 1;
+    while (!(node = group[i0]) && ++i0 < n);
+    return node;
+  };
+}
+
+d3_selectionPrototype.transition = function() {
+  var id = d3_transitionInheritId || ++d3_transitionId,
+      subgroups = [],
+      subgroup,
+      node,
+      transition = d3_transitionInherit || {time: Date.now(), ease: d3_ease_cubicInOut, delay: 0, duration: 250};
+
+  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);
+};
+
+// TODO fast singleton implementation?
+d3.select = function(node) {
+  var group = [typeof node === "string" ? d3_select(node, d3_document) : node];
+  group.parentNode = d3_documentElement;
+  return d3_selection([group]);
+};
+
+d3.selectAll = function(nodes) {
+  var group = d3_array(typeof nodes === "string" ? d3_selectAll(nodes, d3_document) : nodes);
+  group.parentNode = d3_documentElement;
+  return d3_selection([group]);
+};
+
+var d3_selectionRoot = d3.select(d3_documentElement);
 
 d3_selectionPrototype.on = function(type, listener, capture) {
   var n = arguments.length;
@@ -1467,127 +1546,85 @@ function d3_selection_onFilter(listener, argumentz) {
   };
 }
 
-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);
+var d3_event_dragSelect = d3_vendorSymbol(d3_documentElement.style, "userSelect"),
+    d3_event_dragId = 0;
+
+function d3_event_dragSuppress() {
+  var name = ".dragsuppress-" + ++d3_event_dragId,
+      touchmove = "touchmove" + name,
+      selectstart = "selectstart" + name,
+      dragstart = "dragstart" + name,
+      click = "click" + name,
+      w = d3.select(d3_window).on(touchmove, d3_eventPreventDefault).on(selectstart, d3_eventPreventDefault).on(dragstart, d3_eventPreventDefault),
+      style = d3_documentElement.style,
+      select = style[d3_event_dragSelect];
+  style[d3_event_dragSelect] = "none";
+  return function(suppressClick) {
+    w.on(name, null);
+    style[d3_event_dragSelect] = select;
+    if (suppressClick) { // suppress the next click, but only if it’s immediate
+      function off() { w.on(click, null); }
+      w.on(click, function() { d3_eventCancel(); off(); }, true);
+      setTimeout(off, 0);
     }
-  }
-  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;
+d3.mouse = function(container) {
+  return d3_mousePoint(container, d3_eventSource());
 };
 
-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;
+// https://bugs.webkit.org/show_bug.cgi?id=44083
+var d3_mouse_bug44083 = /WebKit/.test(d3_window.navigator.userAgent) ? -1 : 0;
 
-  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);
-      }
+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("body").append("svg").style({
+        position: "absolute",
+        top: 0,
+        left: 0,
+        margin: 0,
+        padding: 0,
+        border: "none"
+      }, "important");
+      var ctm = svg[0][0].getScreenCTM();
+      d3_mouse_bug44083 = !(ctm.f || ctm.e);
+      svg.remove();
     }
-  }
-
-  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);
+    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];
   }
-
-  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
+  var rect = container.getBoundingClientRect();
+  return [e.clientX - rect.left - container.clientLeft, e.clientY - rect.top - container.clientTop];
 };
 
-// TODO selectAll(function)
-d3.selectAll = function(selector) {
-  return typeof selector === "string"
-      ? d3_selectionRoot.selectAll(selector)
-      : d3_selection([d3_array(selector)]); // assume node[]
+d3.touches = function(container, touches) {
+  if (arguments.length < 2) touches = d3_eventSource().touches;
+  return touches ? d3_array(touches).map(function(touch) {
+    var point = d3_mousePoint(container, touch);
+    point.identifier = touch.identifier;
+    return point;
+  }) : [];
 };
 
 d3.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,
+      mousedown = "mousedown.zoom",
+      mousemove = "mousemove.zoom",
+      mouseup = "mouseup.zoom",
       event = d3_eventDispatch(zoom, "zoom"),
       x0,
       x1,
@@ -1596,13 +1633,11 @@ d3.behavior.zoom = function() {
       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);
+    this.on(mousedown, mousedowned)
+        .on(d3_behavior_zoomWheel + ".zoom", mousewheeled)
+        .on(mousemove, mousewheelreset)
+        .on("dblclick.zoom", dblclicked)
+        .on("touchstart.zoom", touchstarted);
   }
 
   zoom.translate = function(x) {
@@ -1668,91 +1703,110 @@ d3.behavior.zoom = function() {
 
   function dispatch(event) {
     rescale();
-    d3.event.preventDefault();
     event({type: "zoom", scale: scale, translate: translate});
   }
 
-  function mousedown() {
+  function mousedowned() {
     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));
+        dragged = 0,
+        w = d3.select(d3_window).on(mousemove, moved).on(mouseup, ended),
+        l = location(d3.mouse(target)),
+        dragRestore = d3_event_dragSuppress();
 
-    d3_window.focus();
-    d3_eventCancel();
+    function moved() {
+      dragged = 1;
+      translateTo(d3.mouse(target), l);
+      dispatch(event_);
+    }
 
-    function mousemove() {
-      if (d3.event.which === 0) {
-        mouseup();
-        return;
+    function ended() {
+      w.on(mousemove, d3_window === target ? mousewheelreset : null).on(mouseup, null);
+      dragRestore(dragged && d3.event.target === eventTarget);
+    }
+  }
+
+  function touchstarted() {
+    var target = this,
+        event_ = event.of(target, arguments),
+        touches = d3.touches(target),
+        locations = {},
+        distance0 = 0, // distance² between initial touches
+        scale0 = scale, // scale when we started touching
+        now = Date.now(),
+        name = "zoom-" + d3.event.changedTouches[0].identifier,
+        touchmove = "touchmove." + name,
+        touchend = "touchend." + name,
+        w = d3.select(d3_window).on(touchmove, moved).on(touchend, ended),
+        t = d3.select(target).on(mousedown, null), // prevent duplicate events
+        dragRestore = d3_event_dragSuppress();
+
+    touches.forEach(function(t) { locations[t.identifier] = location(t); });
+
+    if (touches.length === 1) {
+      if (now - touchtime < 500) { // dbltap
+        var p = touches[0], l = location(touches[0]);
+        scaleTo(scale * 2);
+        translateTo(p, l);
+        d3_eventPreventDefault();
+        dispatch(event_);
       }
-      moved = 1;
-      translateTo(d3.mouse(target), l);
+      touchtime = now;
+    } else if (touches.length > 1) {
+      var p = touches[0], q = touches[1],
+          dx = p[0] - q[0], dy = p[1] - q[1];
+      distance0 = dx * dx + dy * dy;
+    }
+
+    function moved() {
+      var touches = d3.touches(target),
+          p0 = touches[0],
+          l0 = locations[p0.identifier];
+
+      if (p1 = touches[1]) {
+        var p1, l1 = locations[p1.identifier],
+            scale1 = d3.event.scale;
+        if (scale1 == null) {
+          var distance1 = (distance1 = p1[0] - p0[0]) * distance1 + (distance1 = p1[1] - p0[1]) * distance1;
+          scale1 = distance0 && Math.sqrt(distance1 / distance0);
+        }
+        p0 = [(p0[0] + p1[0]) / 2, (p0[1] + p1[1]) / 2];
+        l0 = [(l0[0] + l1[0]) / 2, (l0[1] + l1[1]) / 2];
+        scaleTo(scale1 * scale0);
+      }
+
+      touchtime = null;
+      translateTo(p0, l0);
       dispatch(event_);
     }
 
-    function mouseup() {
-      if (moved) d3_eventCancel();
-      w.on("mousemove.zoom", null).on("mouseup.zoom", null);
-      if (moved && d3.event.target === eventTarget) d3_eventSuppress(w, "click.zoom");
+    function ended() {
+      w.on(touchmove, null).on(touchend, null);
+      t.on(mousedown, mousedowned);
+      dragRestore();
     }
   }
 
-  function mousewheel() {
+  function mousewheeled() {
+    d3_eventPreventDefault();
     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() {
+  function mousewheelreset() {
     translate0 = null;
   }
 
-  function dblclick() {
+  function dblclicked() {
     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");
 };
 
@@ -1769,34 +1823,24 @@ function d3_functor(v) {
 
 d3.functor = d3_functor;
 
-var d3_timer_id = 0,
-    d3_timer_byId = {},
-    d3_timer_queue = null,
+var d3_timer_queueHead,
+    d3_timer_queueTail,
     d3_timer_interval, // is an interval (or frame) active?
-    d3_timer_timeout; // is a timeout active?
+    d3_timer_timeout, // is a timeout active?
+    d3_timer_active, // active timer object
+    d3_timer_frame = d3_window[d3_vendorSymbol(d3_window, "requestAnimationFrame")] || function(callback) { setTimeout(callback, 17); };
 
 // 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();
-  }
+  var n = arguments.length;
+  if (n < 2) delay = 0;
+  if (n < 3) 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
-  };
+  // Add the callback to the tail of the queue.
+  var time = then + delay, timer = {callback: callback, time: time, next: null};
+  if (d3_timer_queueTail) d3_timer_queueTail.next = timer;
+  else d3_timer_queueHead = timer;
+  d3_timer_queueTail = timer;
 
   // Start animatin'!
   if (!d3_timer_interval) {
@@ -1807,17 +1851,8 @@ d3.timer = function(callback, delay, then) {
 };
 
 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;
+  var now = d3_timer_mark(),
+      delay = d3_timer_sweep() - now;
   if (delay > 24) {
     if (isFinite(delay)) {
       clearTimeout(d3_timer_timeout);
@@ -1831,44 +1866,48 @@ function d3_timer_step() {
 }
 
 d3.timer.flush = function() {
-  var elapsed,
-      now = Date.now(),
-      t1 = d3_timer_queue;
+  d3_timer_mark();
+  d3_timer_sweep();
+};
 
-  while (t1) {
-    elapsed = now - t1.then;
-    if (!t1.delay) t1.flush = t1.callback(elapsed);
-    t1 = t1.next;
-  }
+function d3_timer_replace(callback, delay, then) {
+  var n = arguments.length;
+  if (n < 2) delay = 0;
+  if (n < 3) then = Date.now();
+  d3_timer_active.callback = callback;
+  d3_timer_active.time = then + delay;
+}
 
-  d3_timer_flush();
-};
+function d3_timer_mark() {
+  var now = Date.now();
+  d3_timer_active = d3_timer_queueHead;
+  while (d3_timer_active) {
+    if (now >= d3_timer_active.time) d3_timer_active.flush = d3_timer_active.callback(now - d3_timer_active.time);
+    d3_timer_active = d3_timer_active.next;
+  }
+  return now;
+}
 
 // Flush after callbacks to avoid concurrent queue modification.
-function d3_timer_flush() {
-  var t0 = null,
-      t1 = d3_timer_queue,
-      then = Infinity;
+// Returns the time of the earliest active timer, post-sweep.
+function d3_timer_sweep() {
+  var t0,
+      t1 = d3_timer_queueHead,
+      time = Infinity;
   while (t1) {
     if (t1.flush) {
-      delete d3_timer_byId[t1.callback.id];
-      t1 = t0 ? t0.next = t1.next : d3_timer_queue = t1.next;
+      t1 = t0 ? t0.next = t1.next : d3_timer_queueHead = t1.next;
     } else {
-      then = Math.min(then, t1.then + t1.delay);
+      if (t1.time < time) time = t1.time;
       t1 = (t0 = t1).next;
     }
   }
-  return then;
+  d3_timer_queueTail = t0;
+  return time;
 }
-
-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,
+    ε2 = ε * ε,
     d3_radians = π / 180,
     d3_degrees = 180 / π;
 
@@ -1877,7 +1916,7 @@ function d3_sgn(x) {
 }
 
 function d3_acos(x) {
-  return Math.acos(Math.max(-1, Math.min(1, x)));
+  return x > 1 ? 0 : x < -1 ? π : Math.acos(x);
 }
 
 function d3_asin(x) {
@@ -1906,7 +1945,7 @@ function d3_true() {
 function d3_geo_spherical(cartesian) {
   return [
     Math.atan2(cartesian[1], cartesian[0]),
-    Math.asin(Math.max(-1, Math.min(1, cartesian[2])))
+    d3_asin(cartesian[2])
   ];
 }
 
@@ -2006,7 +2045,7 @@ function d3_geo_clipPolygonLinkCircular(array) {
   b.prev = a;
 }
 
-function d3_geo_clip(pointVisible, clipLine, interpolate) {
+function d3_geo_clip(pointVisible, clipLine, interpolate, polygonContains) {
   return function(listener) {
     var line = clipLine(listener);
 
@@ -2018,9 +2057,8 @@ function d3_geo_clip(pointVisible, clipLine, interpolate) {
         clip.point = pointRing;
         clip.lineStart = ringStart;
         clip.lineEnd = ringEnd;
-        invisible = false;
-        invisibleArea = visibleArea = 0;
         segments = [];
+        polygon = [];
         listener.polygonStart();
       },
       polygonEnd: function() {
@@ -2031,13 +2069,13 @@ function d3_geo_clip(pointVisible, clipLine, interpolate) {
         segments = d3.merge(segments);
         if (segments.length) {
           d3_geo_clipPolygon(segments, d3_geo_clipSort, null, interpolate, listener);
-        } else if (visibleArea < -ε || invisible && invisibleArea < -ε) {
+        } else if (polygonContains(polygon)) {
           listener.lineStart();
           interpolate(null, null, 1, listener);
           listener.lineEnd();
         }
         listener.polygonEnd();
-        segments = null;
+        segments = polygon = null;
       },
       sphere: function() {
         listener.polygonStart();
@@ -2053,13 +2091,11 @@ function d3_geo_clip(pointVisible, clipLine, interpolate) {
     function lineStart() { clip.point = pointLine; line.lineStart(); }
     function lineEnd() { clip.point = point; line.lineEnd(); }
 
-    var segments,
-        visibleArea,
-        invisibleArea,
-        invisible;
+    var segments;
 
     var buffer = d3_geo_clipBufferListener(),
         ringListener = clipLine(buffer),
+        polygon,
         ring;
 
     function pointRing(λ, φ) {
@@ -2081,20 +2117,15 @@ function d3_geo_clip(pointVisible, clipLine, interpolate) {
           segment,
           n = ringSegments.length;
 
-      // TODO compute on-the-fly?
-      if (!n) {
-        invisible = true;
-        invisibleArea += d3_geo_clipAreaRing(ring, -1);
-        ring = null;
-        return;
-      }
+      ring.pop();
+      polygon.push(ring);
       ring = null;
 
+      if (!n) return;
+
       // 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;
@@ -2138,67 +2169,298 @@ function d3_geo_clipBufferListener() {
   };
 }
 
-// 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φ;
+// 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]);
+}
+// Adds floating point numbers with twice the normal precision.
+// Reference: J. R. Shewchuk, Adaptive Precision Floating-Point Arithmetic and
+// Fast Robust Geometric Predicates, Discrete & Computational Geometry 18(3)
+// 305–363 (1997).
+// Code adapted from GeographicLib by Charles F. F. Karney,
+// http://geographiclib.sourceforge.net/
+// See lib/geographiclib/LICENSE for details.
+
+function d3_adder() {}
+
+d3_adder.prototype = {
+  s: 0, // rounded value
+  t: 0, // exact error
+  add: function(y) {
+    d3_adderSum(y, this.t, d3_adderTemp);
+    d3_adderSum(d3_adderTemp.s, this.s, this);
+    if (this.s) this.t += d3_adderTemp.t;
+    else this.s = d3_adderTemp.t;
+  },
+  reset: function() {
+    this.s = this.t = 0;
+  },
+  valueOf: function() {
+    return this.s;
+  }
+};
 
-    // 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;
+var d3_adderTemp = new d3_adder;
 
-    // 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) < ε) {}
+function d3_adderSum(a, b, o) {
+  var x = o.s = a + b, // a + b
+      bv = x - a, av = x - bv; // b_virtual & a_virtual
+  o.t = (a - av) + (b - bv); // a_roundoff + b_roundoff
+}
 
-    // 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.
-    }
+d3.geo.stream = function(object, listener) {
+  if (object && d3_geo_streamObjectType.hasOwnProperty(object.type)) {
+    d3_geo_streamObjectType[object.type](object, listener);
+  } else {
+    d3_geo_streamGeometry(object, listener);
+  }
+};
 
-    // If the previous point is at the north pole, then compute lune area.
-    else if (Math.abs(y0 - 2) < ε) area += 4 * (x - x1);
+function d3_geo_streamGeometry(geometry, listener) {
+  if (geometry && d3_geo_streamGeometryType.hasOwnProperty(geometry.type)) {
+    d3_geo_streamGeometryType[geometry.type](geometry, listener);
+  }
+}
 
-    // Otherwise, the spherical triangle area is approximately
-    // δλ * (1 + sinφ0 + 1 + sinφ) / 2.
-    else area += ((3 * π + x - x0) % (2 * π) - π) * (y0 + y);
+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);
+  }
+};
 
-    x1 = x0, x0 = x, y0 = y;
+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);
   }
-  return area;
+};
+
+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();
 }
 
-// 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]);
+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();
+}
+
+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_areaRingSum = new d3_adder;
+
+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_areaRingSum.reset();
+    d3_geo_area.lineStart = d3_geo_areaRingStart;
+  },
+  polygonEnd: function() {
+    var area = 2 * d3_geo_areaRingSum;
+    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 previous point
+
+  // 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φ,
+        u = cosφ0 * cosφ + k * Math.cos(dλ),
+        v = k * Math.sin(dλ);
+    d3_geo_areaRingSum.add(Math.atan2(v, u));
+
+    // 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);
+  };
 }
+// TODO
+// cross and scale return new vectors,
+// whereas add and normalize operate in-place
 
-var d3_geo_clipAntimeridian = d3_geo_clip(d3_true, d3_geo_clipAntimeridianLine, d3_geo_clipAntimeridianInterpolate);
+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_pointInPolygon(point, polygon) {
+  var meridian = point[0],
+      parallel = point[1],
+      meridianNormal = [Math.sin(meridian), -Math.cos(meridian), 0],
+      polarAngle = 0,
+      polar = false,
+      southPole = false,
+      winding = 0;
+  d3_geo_areaRingSum.reset();
+
+  for (var i = 0, n = polygon.length; i < n; ++i) {
+    var ring = polygon[i],
+        m = ring.length;
+    if (!m) continue;
+    var point0 = ring[0],
+        λ0 = point0[0],
+        φ0 = point0[1] / 2 + π / 4,
+        sinφ0 = Math.sin(φ0),
+        cosφ0 = Math.cos(φ0),
+        j = 1;
+
+    while (true) {
+      if (j === m) j = 0;
+      point = ring[j];
+      var λ = point[0],
+          φ = point[1] / 2 + π / 4,
+          sinφ = Math.sin(φ),
+          cosφ = Math.cos(φ),
+          dλ = λ - λ0,
+          antimeridian = Math.abs(dλ) > π,
+          k = sinφ0 * sinφ;
+      d3_geo_areaRingSum.add(Math.atan2(k * Math.sin(dλ), cosφ0 * cosφ + k * Math.cos(dλ)));
+
+      if (Math.abs(φ) < ε) southPole = true;
+      polarAngle += antimeridian ? dλ + (dλ >= 0 ? 2 : -2) * π : dλ;
+
+      // Are the longitudes either side of the point's meridian, and are the
+      // latitudes smaller than the parallel?
+      if (antimeridian ^ λ0 >= meridian ^ λ >= meridian) {
+        var arc = d3_geo_cartesianCross(d3_geo_cartesian(point0), d3_geo_cartesian(point));
+        d3_geo_cartesianNormalize(arc);
+        var intersection = d3_geo_cartesianCross(meridianNormal, arc);
+        d3_geo_cartesianNormalize(intersection);
+        var φarc = (antimeridian ^ dλ >= 0 ? -1 : 1) * d3_asin(intersection[2]);
+        if (parallel > φarc) {
+          winding += antimeridian ^ dλ >= 0 ? 1 : -1;
+        }
+      }
+      if (!j++) break;
+      λ0 = λ, sinφ0 = sinφ, cosφ0 = cosφ, point0 = point;
+    }
+    if (Math.abs(polarAngle) > ε) polar = true;
+  }
+
+  // First, determine whether the South pole is inside or outside:
+  //
+  // It is inside if:
+  // * the polygon doesn't wind around it, and its area is negative (counter-clockwise).
+  // * otherwise, if the polygon winds around it in a clockwise direction.
+  //
+  // Second, count the (signed) number of times a segment crosses a meridian
+  // from the point to the South pole.  If it is zero, then the point is the
+  // same side as the South pole.
+
+  return (!southPole && !polar && d3_geo_areaRingSum < 0 || polarAngle < -ε) ^ (winding & 1);
+}
+
+var d3_geo_clipAntimeridian = d3_geo_clip(
+    d3_true,
+    d3_geo_clipAntimeridianLine,
+    d3_geo_clipAntimeridianInterpolate,
+    d3_geo_clipAntimeridianPolygonContains);
 
 // Takes a line and cuts into visible segments. Return values:
 //   0: there were intersections or the line was empty.
@@ -2284,52 +2546,11 @@ function d3_geo_clipAntimeridianInterpolate(from, to, direction, listener) {
     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];
-}
+var d3_geo_clipAntimeridianPoint = [-π, 0];
 
-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_clipAntimeridianPolygonContains(polygon) {
+  return d3_geo_pointInPolygon(d3_geo_clipAntimeridianPoint, polygon);
 }
 
 function d3_geo_equirectangular(λ, φ) {
@@ -2390,7 +2611,7 @@ function d3_geo_rotationφγ(δφ, δγ) {
         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δγ)))
+      d3_asin(k * cosδγ + y * sinδγ)
     ];
   }
 
@@ -2402,7 +2623,7 @@ function d3_geo_rotationφγ(δφ, δγ) {
         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δφ)))
+      d3_asin(k * cosδφ - x * sinδφ)
     ];
   };
 
@@ -2489,10 +2710,11 @@ function d3_geo_circleAngle(cr, point) {
 function d3_geo_clipCircle(radius) {
   var cr = Math.cos(radius),
       smallRadius = cr > 0,
+      point = [radius, 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);
+  return d3_geo_clip(visible, clipLine, interpolate, polygonContains);
 
   function visible(λ, φ) {
     return Math.cos(λ) * Math.cos(φ) > cr;
@@ -2654,6 +2876,10 @@ function d3_geo_clipCircle(radius) {
     else if (φ > r) code |= 8; // above
     return code;
   }
+
+  function polygonContains(polygon) {
+    return d3_geo_pointInPolygon(point, polygon);
+  }
 }
 
 var d3_geo_clipViewMAX = 1e9;
@@ -2702,7 +2928,7 @@ function d3_geo_clipView(x0, y0, x1, y1) {
           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) {
+        for (var j = 1, v = polygon[i], m = v.length, a = v[0], b; j < m; ++j) {
           b = v[j];
           if (a[1] <= y) {
             if (b[1] >  y && isLeft(a, b, p) > 0) ++wn;
@@ -2876,320 +3102,6 @@ function d3_geo_compose(a, b) {
   return compose;
 }
 
-d3.geo.stream = function(object, listener) {
-  if (object && 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 (geometry && 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,
@@ -3222,7 +3134,7 @@ function d3_geo_conicEqualArea(φ0, φ1) {
     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))
+      d3_asin((C - (x * x + ρ0_y * ρ0_y) * n * n) / (2 * n))
     ];
   };
 
@@ -3233,253 +3145,346 @@ function d3_geo_conicEqualArea(φ0, φ1) {
   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:
+// ESRI:102003
+d3.geo.albers = function() {
+  return d3.geo.conicEqualArea()
+      .rotate([96, 0])
+      .center([-.6, 38.7])
+      .parallels([29.5, 45.5])
+      .scale(1070);
+};
+
+// A composite projection for the United States, configured by default for
+// 960×500. Also works quite well at 960×600 with scale 1285. 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 lower48 = d3.geo.albers();
 
+  // EPSG:3338
   var alaska = d3.geo.conicEqualArea()
-      .rotate([160, 0])
-      .center([0, 60])
+      .rotate([154, 0])
+      .center([-2, 58.5])
       .parallels([55, 65]);
 
+  // ESRI:102007
   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])
+      .rotate([157, 0])
+      .center([-3, 19.9])
       .parallels([8, 18]);
 
-  var alaskaInvert,
-      hawaiiInvert,
-      puertoRicoInvert;
+  var point,
+      pointStream = {point: function(x, y) { point = [x, y]; }},
+      lower48Point,
+      alaskaPoint,
+      hawaiiPoint;
 
   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;
+    var x = coordinates[0], y = coordinates[1];
+    point = null;
+    (lower48Point(x, y), point)
+        || (alaskaPoint(x, y), point)
+        || hawaiiPoint(x, y);
+    return point;
   }
 
   albersUsa.invert = function(coordinates) {
-    return alaskaInvert(coordinates) || hawaiiInvert(coordinates) || puertoRicoInvert(coordinates) || lower48.invert(coordinates);
+    var k = lower48.scale(),
+        t = lower48.translate(),
+        x = (coordinates[0] - t[0]) / k,
+        y = (coordinates[1] - t[1]) / k;
+    return (y >= .120 && y < .234 && x >= -.425 && x < -.214 ? alaska
+        : y >= .166 && y < .234 && x >= -.214 && x < -.115 ? hawaii
+        : 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());
+  // A naïve multi-projection stream.
+  // The projections must have mutually exclusive clip regions on the sphere,
+  // as this will avoid emitting interleaving lines and polygons.
+  albersUsa.stream = function(stream) {
+    var lower48Stream = lower48.stream(stream),
+        alaskaStream = alaska.stream(stream),
+        hawaiiStream = hawaii.stream(stream);
+    return {
+      point: function(x, y) {
+        lower48Stream.point(x, y);
+        alaskaStream.point(x, y);
+        hawaiiStream.point(x, y);
+      },
+      sphere: function() {
+        lower48Stream.sphere();
+        alaskaStream.sphere();
+        hawaiiStream.sphere();
+      },
+      lineStart: function() {
+        lower48Stream.lineStart();
+        alaskaStream.lineStart();
+        hawaiiStream.lineStart();
+      },
+      lineEnd: function() {
+        lower48Stream.lineEnd();
+        alaskaStream.lineEnd();
+        hawaiiStream.lineEnd();
+      },
+      polygonStart: function() {
+        lower48Stream.polygonStart();
+        alaskaStream.polygonStart();
+        hawaiiStream.polygonStart();
+      },
+      polygonEnd: function() {
+        lower48Stream.polygonEnd();
+        alaskaStream.polygonEnd();
+        hawaiiStream.polygonEnd();
+      }
+    };
   };
 
-  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]]);
-
+  albersUsa.precision = function(_) {
+    if (!arguments.length) return lower48.precision();
+    lower48.precision(_);
+    alaska.precision(_);
+    hawaii.precision(_);
     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);
+  albersUsa.scale = function(_) {
+    if (!arguments.length) return lower48.scale();
+    lower48.scale(_);
+    alaska.scale(_ * .35);
+    hawaii.scale(_);
+    return albersUsa.translate(lower48.translate());
   };
-}
-
-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;
+  albersUsa.translate = function(_) {
+    if (!arguments.length) return lower48.translate();
+    var k = lower48.scale(), x = +_[0], y = +_[1];
 
-var d3_geo_area = {
-  sphere: function() { d3_geo_areaSum += 4 * π; },
-  point: d3_noop,
-  lineStart: d3_noop,
-  lineEnd: d3_noop,
+    lower48Point = lower48
+        .translate(_)
+        .clipExtent([[x - .455 * k, y - .238 * k], [x + .455 * k, y + .238 * k]])
+        .stream(pointStream).point;
 
-  // 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;
-  }
-};
+    alaskaPoint = alaska
+        .translate([x - .307 * k, y + .201 * k])
+        .clipExtent([[x - .425 * k + ε, y + .120 * k + ε], [x - .214 * k - ε, y + .234 * k - ε]])
+        .stream(pointStream).point;
 
-function d3_geo_areaRingStart() {
-  var λ00, φ00, λ0, cosφ0, sinφ0; // start point and two previous points
+    hawaiiPoint = hawaii
+        .translate([x - .205 * k, y + .212 * k])
+        .clipExtent([[x - .214 * k + ε, y + .166 * k + ε], [x - .115 * k - ε, y + .234 * k - ε]])
+        .stream(pointStream).point;
 
-  // 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(φ);
+    return albersUsa;
   };
 
-  // For subsequent points, …
-  function nextPoint(λ, φ) {
-    λ *= d3_radians;
-    φ = φ * d3_radians / 2 + π / 4; // half the angular distance from south pole
+  return albersUsa.scale(1070);
+};
 
-    // 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;
+d3.geo.bounds = (function() {
+  var λ0, φ0, λ1, φ1, // bounds
+      λ_, // previous λ-coordinate
+      λ__, φ__, // first point
+      p0, // previous 3D point
+      dλSum,
+      ranges,
+      range;
 
-    // Advance the previous points.
-    λ0 = λ, cosφ0 = cosφ, sinφ0 = sinφ;
-  }
+  var bound = {
+    point: point,
+    lineStart: lineStart,
+    lineEnd: lineEnd,
 
-  // For the last point, return to the start.
-  d3_geo_area.lineEnd = function() {
-    nextPoint(λ00, φ00);
+    polygonStart: function() {
+      bound.point = ringPoint;
+      bound.lineStart = ringStart;
+      bound.lineEnd = ringEnd;
+      dλSum = 0;
+      d3_geo_area.polygonStart();
+    },
+    polygonEnd: function() {
+      d3_geo_area.polygonEnd();
+      bound.point = point;
+      bound.lineStart = lineStart;
+      bound.lineEnd = lineEnd;
+      if (d3_geo_areaRingSum < 0) λ0 = -(λ1 = 180), φ0 = -(φ1 = 90);
+      else if (dλSum > ε) φ1 = 90;
+      else if (dλSum < -ε) φ0 = -90;
+      range[0] = λ0, range[1] = λ1;
+    }
   };
-}
 
-d3.geo.bounds = d3_geo_bounds(d3_identity);
+  function point(λ, φ) {
+    ranges.push(range = [λ0 = λ, λ1 = λ]);
+    if (φ < φ0) φ0 = φ;
+    if (φ > φ1) φ1 = φ;
+  }
+
+  function linePoint(λ, φ) {
+    var p = d3_geo_cartesian([λ * d3_radians, φ * d3_radians]);
+    if (p0) {
+      var normal = d3_geo_cartesianCross(p0, p),
+          equatorial = [normal[1], -normal[0], 0],
+          inflection = d3_geo_cartesianCross(equatorial, normal);
+      d3_geo_cartesianNormalize(inflection);
+      inflection = d3_geo_spherical(inflection);
+      var dλ = λ - λ_,
+          s = dλ > 0 ? 1 : -1,
+          λi = inflection[0] * d3_degrees * s,
+          antimeridian = Math.abs(dλ) > 180;
+      if (antimeridian ^ (s * λ_ < λi && λi < s * λ)) {
+        var φi = inflection[1] * d3_degrees;
+        if (φi > φ1) φ1 = φi;
+      } else if (λi = (λi + 360) % 360 - 180, antimeridian ^ (s * λ_ < λi && λi < s * λ)) {
+        var φi = -inflection[1] * d3_degrees;
+        if (φi < φ0) φ0 = φi;
+      } else {
+        if (φ < φ0) φ0 = φ;
+        if (φ > φ1) φ1 = φ;
+      }
+      if (antimeridian) {
+        if (λ < λ_) {
+          if (angle(λ0, λ) > angle(λ0, λ1)) λ1 = λ;
+        } else {
+          if (angle(λ, λ1) > angle(λ0, λ1)) λ0 = λ;
+        }
+      } else {
+        if (λ1 >= λ0) {
+          if (λ < λ0) λ0 = λ;
+          if (λ > λ1) λ1 = λ;
+        } else {
+          if (λ > λ_) {
+            if (angle(λ0, λ) > angle(λ0, λ1)) λ1 = λ;
+          } else {
+            if (angle(λ, λ1) > angle(λ0, λ1)) λ0 = λ;
+          }
+        }
+      }
+    } else {
+      point(λ, φ);
+    }
+    p0 = p, λ_ = λ;
+  }
 
-function d3_geo_bounds(projectStream) {
-  var x0, y0, x1, y1;
+  function lineStart() { bound.point = linePoint; }
+  function lineEnd() {
+    range[0] = λ0, range[1] = λ1;
+    bound.point = point;
+    p0 = null;
+  }
 
-  var bound = {
-    point: boundPoint,
-    lineStart: d3_noop,
-    lineEnd: d3_noop,
+  function ringPoint(λ, φ) {
+    if (p0) {
+      var dλ = λ - λ_;
+      dλSum += Math.abs(dλ) > 180 ? dλ + (dλ > 0 ? 360 : -360) : dλ;
+    } else λ__ = λ, φ__ = φ;
+    d3_geo_area.point(λ, φ);
+    linePoint(λ, φ);
+  }
 
-    // While inside a polygon, ignore points in holes.
-    polygonStart: function() { bound.lineEnd = boundPolygonLineEnd; },
-    polygonEnd: function() { bound.point = boundPoint; }
-  };
+  function ringStart() {
+    d3_geo_area.lineStart();
+  }
 
-  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 ringEnd() {
+    ringPoint(λ__, φ__);
+    d3_geo_area.lineEnd();
+    if (Math.abs(dλSum) > ε) λ0 = -(λ1 = 180);
+    range[0] = λ0, range[1] = λ1;
+    p0 = null;
   }
 
-  function boundPolygonLineEnd() {
-    bound.point = bound.lineEnd = d3_noop;
+  // Finds the left-right distance between two longitudes.
+  // This is almost the same as (λ1 - λ0 + 360°) % 360°, except that we want
+  // the distance between ±180° to be 360°.
+  function angle(λ0, λ1) { return (λ1 -= λ0) < 0 ? λ1 + 360 : λ1; }
+
+  function compareRanges(a, b) { return a[0] - b[0]; }
+
+  function withinRange(x, range) {
+    return range[0] <= range[1] ? range[0] <= x && x <= range[1] : x < range[0] || range[1] < x;
   }
 
   return function(feature) {
-    y1 = x1 = -(x0 = y0 = Infinity);
-    d3.geo.stream(feature, projectStream(bound));
-    return [[x0, y0], [x1, y1]];
+    φ1 = λ1 = -(λ0 = φ0 = Infinity);
+    ranges = [];
+
+    d3.geo.stream(feature, bound);
+
+    var n = ranges.length;
+    if (n) {
+      // First, sort ranges by their minimum longitudes.
+      ranges.sort(compareRanges);
+
+      // Then, merge any ranges that overlap.
+      for (var i = 1, a = ranges[0], b, merged = [a]; i < n; ++i) {
+        b = ranges[i];
+        if (withinRange(b[0], a) || withinRange(b[1], a)) {
+          if (angle(a[0], b[1]) > angle(a[0], a[1])) a[1] = b[1];
+          if (angle(b[0], a[1]) > angle(a[0], a[1])) a[0] = b[0];
+        } else {
+          merged.push(a = b);
+        }
+      }
+
+      // Finally, find the largest gap between the merged ranges.
+      // The final bounding box will be the inverse of this gap.
+      var best = -Infinity, dλ;
+      for (var n = merged.length - 1, i = 0, a = merged[n], b; i <= n; a = b, ++i) {
+        b = merged[i];
+        if ((dλ = angle(a[1], b[0])) > best) best = dλ, λ0 = b[0], λ1 = a[1];
+      }
+    }
+    ranges = range = null;
+
+    return λ0 === Infinity || φ0 === Infinity
+        ? [[NaN, NaN], [NaN, NaN]]
+        : [[λ0, φ0], [λ1, φ1]];
   };
-}
+})();
 
 d3.geo.centroid = function(object) {
-  d3_geo_centroidDimension = d3_geo_centroidW = d3_geo_centroidX = d3_geo_centroidY = d3_geo_centroidZ = 0;
+  d3_geo_centroidW0 = d3_geo_centroidW1 =
+  d3_geo_centroidX0 = d3_geo_centroidY0 = d3_geo_centroidZ0 =
+  d3_geo_centroidX1 = d3_geo_centroidY1 = d3_geo_centroidZ1 =
+  d3_geo_centroidX2 = d3_geo_centroidY2 = d3_geo_centroidZ2 = 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 x = d3_geo_centroidX2,
+      y = d3_geo_centroidY2,
+      z = d3_geo_centroidZ2,
+      m = x * x + y * y + z * z;
+
+  // If the area-weighted centroid is undefined, fall back to length-weighted centroid.
+  if (m < ε2) {
+    x = d3_geo_centroidX1, y = d3_geo_centroidY1, z = d3_geo_centroidZ1;
+    // If the feature has zero length, fall back to arithmetic mean of point vectors.
+    if (d3_geo_centroidW1 < ε) x = d3_geo_centroidX0, y = d3_geo_centroidY0, z = d3_geo_centroidZ0;
+    m = x * x + y * y + z * z;
+    // If the feature still has an undefined centroid, then return.
+    if (m < ε2) return [NaN, NaN];
   }
+
+  return [Math.atan2(y, x) * d3_degrees, d3_asin(z / Math.sqrt(m)) * d3_degrees];
 };
 
-var d3_geo_centroidDimension,
-    d3_geo_centroidW,
-    d3_geo_centroidX,
-    d3_geo_centroidY,
-    d3_geo_centroidZ;
+var d3_geo_centroidW0,
+    d3_geo_centroidW1,
+    d3_geo_centroidX0,
+    d3_geo_centroidY0,
+    d3_geo_centroidZ0,
+    d3_geo_centroidX1,
+    d3_geo_centroidY1,
+    d3_geo_centroidZ1,
+    d3_geo_centroidX2,
+    d3_geo_centroidY2,
+    d3_geo_centroidZ2;
 
 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;
-    }
-  },
+  sphere: d3_noop,
   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() {
@@ -3489,42 +3494,21 @@ var d3_geo_centroid = {
 
 // 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;
+  d3_geo_centroidPointXYZ(cosφ * Math.cos(λ), cosφ * Math.sin(λ), Math.sin(φ));
 }
 
-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_centroidPointXYZ(x, y, z) {
+  ++d3_geo_centroidW0;
+  d3_geo_centroidX0 += (x - d3_geo_centroidX0) / d3_geo_centroidW0;
+  d3_geo_centroidY0 += (y - d3_geo_centroidY0) / d3_geo_centroidW0;
+  d3_geo_centroidZ0 += (z - d3_geo_centroidZ0) / d3_geo_centroidW0;
 }
 
 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);
@@ -3532,6 +3516,7 @@ function d3_geo_centroidLineStart() {
     y0 = cosφ * Math.sin(λ);
     z0 = Math.sin(φ);
     d3_geo_centroid.point = nextPoint;
+    d3_geo_centroidPointXYZ(x0, y0, z0);
   };
 
   function nextPoint(λ, φ) {
@@ -3543,10 +3528,11 @@ function d3_geo_centroidLineStart() {
         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));
+    d3_geo_centroidW1 += w;
+    d3_geo_centroidX1 += w * (x0 + (x0 = x));
+    d3_geo_centroidY1 += w * (y0 + (y0 = y));
+    d3_geo_centroidZ1 += w * (z0 + (z0 = z));
+    d3_geo_centroidPointXYZ(x0, y0, z0);
   }
 }
 
@@ -3554,6 +3540,53 @@ function d3_geo_centroidLineEnd() {
   d3_geo_centroid.point = d3_geo_centroidPoint;
 }
 
+// See J. E. Brock, The Inertia Tensor for a Spherical Triangle,
+// J. Applied Mechanics 42, 239 (1975).
+function d3_geo_centroidRingStart() {
+  var λ00, φ00, // first point
+      x0, y0, z0; // previous point
+
+  d3_geo_centroid.point = function(λ, φ) {
+    λ00 = λ, φ00 = φ;
+    d3_geo_centroid.point = nextPoint;
+    λ *= d3_radians;
+    var cosφ = Math.cos(φ *= d3_radians);
+    x0 = cosφ * Math.cos(λ);
+    y0 = cosφ * Math.sin(λ);
+    z0 = Math.sin(φ);
+    d3_geo_centroidPointXYZ(x0, y0, z0);
+  };
+
+  d3_geo_centroid.lineEnd = function() {
+    nextPoint(λ00, φ00);
+    d3_geo_centroid.lineEnd = d3_geo_centroidLineEnd;
+    d3_geo_centroid.point = d3_geo_centroidPoint;
+  };
+
+  function nextPoint(λ, φ) {
+    λ *= d3_radians;
+    var cosφ = Math.cos(φ *= d3_radians),
+        x = cosφ * Math.cos(λ),
+        y = cosφ * Math.sin(λ),
+        z = Math.sin(φ),
+        cx = y0 * z - z0 * y,
+        cy = z0 * x - x0 * z,
+        cz = x0 * y - y0 * x,
+        m = Math.sqrt(cx * cx + cy * cy + cz * cz),
+        u = x0 * x + y0 * y + z0 * z,
+        v = m && -d3_acos(u) / m, // area weight
+        w = Math.atan2(m, u); // line weight
+    d3_geo_centroidX2 += v * cx;
+    d3_geo_centroidY2 += v * cy;
+    d3_geo_centroidZ2 += v * cz;
+    d3_geo_centroidW1 += w;
+    d3_geo_centroidX1 += w * (x0 + (x0 = x));
+    d3_geo_centroidY1 += w * (y0 + (y0 = y));
+    d3_geo_centroidZ1 += w * (z0 + (z0 = z));
+    d3_geo_centroidPointXYZ(x0, y0, z0);
+  }
+}
+
 // TODO Unify this code with d3.geom.polygon area?
 
 var d3_geo_pathAreaSum, d3_geo_pathAreaPolygon, d3_geo_pathArea = {
@@ -3592,8 +3625,28 @@ function d3_geo_pathAreaRingStart() {
     nextPoint(x00, y00);
   };
 }
+
+var d3_geo_pathBoundsX0,
+    d3_geo_pathBoundsY0,
+    d3_geo_pathBoundsX1,
+    d3_geo_pathBoundsY1;
+
+var d3_geo_pathBounds = {
+  point: d3_geo_pathBoundsPoint,
+  lineStart: d3_noop,
+  lineEnd: d3_noop,
+  polygonStart: d3_noop,
+  polygonEnd: d3_noop
+};
+
+function d3_geo_pathBoundsPoint(x, y) {
+  if (x < d3_geo_pathBoundsX0) d3_geo_pathBoundsX0 = x;
+  if (x > d3_geo_pathBoundsX1) d3_geo_pathBoundsX1 = x;
+  if (y < d3_geo_pathBoundsY0) d3_geo_pathBoundsY0 = y;
+  if (y > d3_geo_pathBoundsY1) d3_geo_pathBoundsY1 = y;
+}
 function d3_geo_pathBuffer() {
-  var pointCircle = d3_geo_pathCircle(4.5),
+  var pointCircle = d3_geo_pathBufferCircle(4.5),
       buffer = [];
 
   var stream = {
@@ -3608,7 +3661,7 @@ function d3_geo_pathBuffer() {
     polygonEnd: function() { stream.lineEnd = lineEnd; stream.point = point; },
 
     pointRadius: function(_) {
-      pointCircle = d3_geo_pathCircle(_);
+      pointCircle = d3_geo_pathBufferCircle(_);
       return stream;
     },
 
@@ -3645,6 +3698,13 @@ function d3_geo_pathBuffer() {
   return stream;
 }
 
+function d3_geo_pathBufferCircle(radius) {
+  return "m0," + radius
+      + "a" + radius + "," + radius + " 0 1,1 0," + -2 * radius
+      + "a" + radius + "," + radius + " 0 1,1 0," + 2 * radius
+      + "z";
+}
+
 // TODO Unify this code with d3.geom.polygon centroid?
 // TODO Enforce positive area for exterior, negative area for interior?
 
@@ -3667,33 +3727,25 @@ var d3_geo_pathCentroid = {
 };
 
 function d3_geo_pathCentroidPoint(x, y) {
-  if (d3_geo_centroidDimension) return;
-  d3_geo_centroidX += x;
-  d3_geo_centroidY += y;
-  ++d3_geo_centroidZ;
+  d3_geo_centroidX0 += x;
+  d3_geo_centroidY0 += y;
+  ++d3_geo_centroidZ0;
 }
 
 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;
+    d3_geo_pathCentroidPoint(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;
+    d3_geo_centroidX1 += z * (x0 + x) / 2;
+    d3_geo_centroidY1 += z * (y0 + y) / 2;
+    d3_geo_centroidZ1 += z;
+    d3_geo_pathCentroidPoint(x0 = x, y0 = y);
   }
 }
 
@@ -3704,24 +3756,24 @@ function d3_geo_pathCentroidLineEnd() {
 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;
+    d3_geo_pathCentroidPoint(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;
+    var dx = x - x0, dy = y - y0, z = Math.sqrt(dx * dx + dy * dy);
+    d3_geo_centroidX1 += z * (x0 + x) / 2;
+    d3_geo_centroidY1 += z * (y0 + y) / 2;
+    d3_geo_centroidZ1 += z;
+
+    z = y0 * x - x0 * y;
+    d3_geo_centroidX2 += z * (x0 + x);
+    d3_geo_centroidY2 += z * (y0 + y);
+    d3_geo_centroidZ2 += z * 3;
+    d3_geo_pathCentroidPoint(x0 = x, y0 = y);
   }
 
   // For the last point, return to the start.
@@ -3777,65 +3829,168 @@ function d3_geo_pathContext(context) {
   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();
-  }
+function d3_geo_resample(project) {
+  var δ2 = .5, // precision, px²
+      cosMinDistance = Math.cos(30 * d3_radians), // cos(minimum angular distance)
+      maxDepth = 16;
 
-  path.area = function(object) {
-    d3_geo_pathAreaSum = 0;
-    d3.geo.stream(object, projectStream(d3_geo_pathArea));
-    return d3_geo_pathAreaSum;
-  };
+  function resample(stream) {
+    var λ00, φ00, x00, y00, a00, b00, c00, // first point
+        λ0, x0, y0, a0, b0, c0; // previous point
 
-  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;
-  };
+    var resample = {
+      point: point,
+      lineStart: lineStart,
+      lineEnd: lineEnd,
+      polygonStart: function() { stream.polygonStart(); resample.lineStart = ringStart; },
+      polygonEnd: function() { stream.polygonEnd(); resample.lineStart = lineStart; }
+    };
 
-  path.bounds = function(object) {
-    return d3_geo_bounds(projectStream)(object);
-  };
+    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 ringStart() {
+      lineStart();
+      resample.point = ringPoint;
+      resample.lineEnd = ringEnd;
+    }
+
+    function ringPoint(λ, φ) {
+      linePoint(λ00 = λ, φ00 = φ), x00 = x0, y00 = y0, a00 = a0, b00 = b0, c00 = c0;
+      resample.point = linePoint;
+    }
+
+    function ringEnd() {
+      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 // perpendicular projected distance
+          || Math.abs((dx * dx2 + dy * dy2) / d2 - .5) > .3 // midpoint close to an end
+          || a0 * a1 + b0 * b1 + c0 * c1 < cosMinDistance) { // angular distance
+        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.path = function() {
+  var pointRadius = 4.5,
+      projection,
+      context,
+      projectStream,
+      contextStream,
+      cacheStream;
+
+  function path(object) {
+    if (object) {
+      if (typeof pointRadius === "function") contextStream.pointRadius(+pointRadius.apply(this, arguments));
+      if (!cacheStream || !cacheStream.valid) cacheStream = projectStream(contextStream);
+      d3.geo.stream(object, cacheStream);
+    }
+    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_centroidX0 = d3_geo_centroidY0 = d3_geo_centroidZ0 =
+    d3_geo_centroidX1 = d3_geo_centroidY1 = d3_geo_centroidZ1 =
+    d3_geo_centroidX2 = d3_geo_centroidY2 = d3_geo_centroidZ2 = 0;
+    d3.geo.stream(object, projectStream(d3_geo_pathCentroid));
+    return d3_geo_centroidZ2 ? [d3_geo_centroidX2 / d3_geo_centroidZ2, d3_geo_centroidY2 / d3_geo_centroidZ2]
+        : d3_geo_centroidZ1 ? [d3_geo_centroidX1 / d3_geo_centroidZ1, d3_geo_centroidY1 / d3_geo_centroidZ1]
+        : d3_geo_centroidZ0 ? [d3_geo_centroidX0 / d3_geo_centroidZ0, d3_geo_centroidY0 / d3_geo_centroidZ0]
+        : [NaN, NaN];
+  };
+
+  path.bounds = function(object) {
+    d3_geo_pathBoundsX1 = d3_geo_pathBoundsY1 = -(d3_geo_pathBoundsX0 = d3_geo_pathBoundsY0 = Infinity);
+    d3.geo.stream(object, projectStream(d3_geo_pathBounds));
+    return [[d3_geo_pathBoundsX0, d3_geo_pathBoundsY0], [d3_geo_pathBoundsX1, d3_geo_pathBoundsY1]];
+  };
 
   path.projection = function(_) {
     if (!arguments.length) return projection;
     projectStream = (projection = _) ? _.stream || d3_geo_pathProjectStream(_) : d3_identity;
-    return path;
+    return reset();
   };
 
   path.context = function(_) {
     if (!arguments.length) return context;
     contextStream = (context = _) == null ? new d3_geo_pathBuffer : new d3_geo_pathContext(_);
-    return path;
+    if (typeof pointRadius !== "function") contextStream.pointRadius(pointRadius);
+    return reset();
   };
 
   path.pointRadius = function(_) {
     if (!arguments.length) return pointRadius;
-    pointRadius = typeof _ === "function" ? _ : +_;
+    pointRadius = typeof _ === "function" ? _ : (contextStream.pointRadius(+_), +_);
     return path;
   };
 
+  function reset() {
+    cacheStream = null;
+    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) {
@@ -3850,75 +4005,255 @@ function d3_geo_pathProjectStream(project) {
     };
   };
 }
-d3.geom = {};
 
-d3.geom.polygon = function(coordinates) {
+d3.geo.projection = d3_geo_projection;
+d3.geo.projectionMutator = d3_geo_projectionMutator;
 
-  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];
+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,
+      stream;
+
+  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(output) {
+    if (stream) stream.valid = false;
+    stream = d3_geo_projectionRadiansRotate(rotate, preclip(projectResample(postclip(output))));
+    stream.valid = true; // allow caching by d3.geo.path
+    return stream;
+  };
+
+  projection.clipAngle = function(_) {
+    if (!arguments.length) return clipAngle;
+    preclip = _ == null ? (clipAngle = _, d3_geo_clipAntimeridian) : d3_geo_clipCircle((clipAngle = +_) * d3_radians);
+    return invalidate();
+  };
+
+  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 invalidate();
+  };
+
+  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 invalidate();
+  }
+
+  function invalidate() {
+    if (stream) {
+      stream.valid = false;
+      stream = null;
     }
-    return area * .5;
+    return projection;
+  }
+
+  return function() {
+    project = projectAt.apply(this, arguments);
+    projection.invert = project.invert && invert;
+    return reset();
   };
+}
 
-  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;
+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 [x * k, y * k];
+    return v;
   };
 
-  // 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)) {
+  return m.clipExtent(null);
+}
+
+(d3.geo.mercator = function() {
+  return d3_geo_mercatorProjection(d3_geo_mercator);
+}).raw = d3_geo_mercator;
+d3.geom = {};
+
+d3.geom.polygon = function(coordinates) {
+  d3_subclass(coordinates, d3_geom_polygonPrototype);
+  return coordinates;
+};
+
+var d3_geom_polygonPrototype = d3.geom.polygon.prototype = [];
+
+d3_geom_polygonPrototype.area = function() {
+  var i = -1,
+      n = this.length,
+      a,
+      b = this[n - 1],
+      area = 0;
+
+  while (++i < n) {
+    a = b;
+    b = this[i];
+    area += a[1] * b[0] - a[0] * b[1];
+  }
+
+  return area * .5;
+};
+
+d3_geom_polygonPrototype.centroid = function(k) {
+  var i = -1,
+      n = this.length,
+      x = 0,
+      y = 0,
+      a,
+      b = this[n - 1],
+      c;
+
+  if (!arguments.length) k = -1 / (6 * this.area());
+
+  while (++i < n) {
+    a = b;
+    b = this[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.
+d3_geom_polygonPrototype.clip = function(subject) {
+  var input,
+      closed = d3_geom_polygonClosed(subject),
+      i = -1,
+      n = this.length - d3_geom_polygonClosed(this),
+      j,
+      m,
+      a = this[n - 1],
+      b,
+      c,
+      d;
+
+  while (++i < n) {
+    input = subject.slice();
+    subject.length = 0;
+    b = this[i];
+    c = input[(m = input.length - closed) - 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));
         }
-        c = d;
+        subject.push(d);
+      } else if (d3_geom_polygonInside(c, a, b)) {
+        subject.push(d3_geom_polygonIntersect(c, d, a, b));
       }
-      a = b;
+      c = d;
     }
-    return subject;
-  };
+    if (closed) subject.push(subject[0]);
+    a = b;
+  }
 
-  return coordinates;
+  return subject;
 };
 
 function d3_geom_polygonInside(p, a, b) {
@@ -3933,6 +4268,13 @@ function d3_geom_polygonIntersect(c, d, a, b) {
   return [x1 + ua * x21, y1 + ua * y21];
 }
 
+// Returns true if the polygon is closed.
+function d3_geom_polygonClosed(coordinates) {
+  var a = coordinates[0],
+      b = coordinates[coordinates.length - 1];
+  return !(a[0] - b[0] || a[1] - b[1]);
+}
+
 var d3_ease_default = function() { return d3_identity; };
 
 var d3_ease = d3.map({
@@ -4041,7 +4383,7 @@ function d3_ease_bounce(t) {
 }
 
 function d3_transition(groups, id) {
-  d3_arraySubclass(groups, d3_transitionPrototype);
+  d3_subclass(groups, d3_transitionPrototype);
 
   groups.id = id; // Note: read-only!
 
@@ -4051,11 +4393,12 @@ function d3_transition(groups, id) {
 var d3_transitionPrototype = [],
     d3_transitionId = 0,
     d3_transitionInheritId,
-    d3_transitionInherit = {ease: d3_ease_cubicInOut, delay: 0, duration: 250};
+    d3_transitionInherit;
 
 d3_transitionPrototype.call = d3_selectionPrototype.call;
 d3_transitionPrototype.empty = d3_selectionPrototype.empty;
 d3_transitionPrototype.node = d3_selectionPrototype.node;
+d3_transitionPrototype.size = d3_selectionPrototype.size;
 
 d3.transition = function(selection) {
   return arguments.length
@@ -4073,12 +4416,12 @@ d3_transitionPrototype.select = function(selector) {
       subnode,
       node;
 
-  if (typeof selector !== "function") selector = d3_selection_selector(selector);
+  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 ((node = group[i]) && (subnode = selector.call(node, node.__data__, i, j))) {
         if ("__data__" in node) subnode.__data__ = node.__data__;
         d3_transitionNode(subnode, i, id, node.__transition__[id]);
         subgroup.push(subnode);
@@ -4100,16 +4443,16 @@ d3_transitionPrototype.selectAll = function(selector) {
       subnode,
       transition;
 
-  if (typeof selector !== "function") selector = d3_selection_selectorAll(selector);
+  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);
+        subnodes = selector.call(node, node.__data__, i, j);
         subgroups.push(subgroup = []);
         for (var k = -1, o = subnodes.length; ++k < o;) {
-          d3_transitionNode(subnode = subnodes[k], k, id, transition);
+          if (subnode = subnodes[k]) d3_transitionNode(subnode, k, id, transition);
           subgroup.push(subnode);
         }
       }
@@ -4136,7 +4479,7 @@ d3_transitionPrototype.filter = function(filter) {
     }
   }
 
-  return d3_transition(subgroups, this.id, this.time).ease(this.ease());
+  return d3_transition(subgroups, this.id);
 };
 function d3_Color() {}
 
@@ -4182,8 +4525,8 @@ function d3_hsl_rgb(h, s, l) {
       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;
+  h = isNaN(h) ? 0 : (h %= 360) < 0 ? h + 360 : h;
+  s = isNaN(s) ? 0 : s < 0 ? 0 : s > 1 ? 1 : s;
   l = l < 0 ? 0 : l > 1 ? 1 : l;
 
   /* From FvD 13.37, CSS Color Module Level 3 */
@@ -4239,6 +4582,8 @@ d3_hclPrototype.rgb = function() {
 };
 
 function d3_hcl_lab(h, c, l) {
+  if (isNaN(h)) h = 0;
+  if (isNaN(c)) c = 0;
   return d3_lab(l, Math.cos(h *= d3_radians) * c, Math.sin(h) * c);
 }
 
@@ -4297,7 +4642,9 @@ function d3_lab_rgb(l, a, b) {
 }
 
 function d3_lab_hcl(l, a, b) {
-  return d3_hcl(Math.atan2(b, a) * d3_degrees, Math.sqrt(a * a + b * b), l);
+  return l > 0
+      ? d3_hcl(Math.atan2(b, a) * d3_degrees, Math.sqrt(a * a + b * b), l)
+      : d3_hcl(NaN, NaN, l);
 }
 
 function d3_lab_xyz(x) {
@@ -4318,6 +4665,14 @@ d3.rgb = function(r, g, b) {
       : d3_rgb(~~r, ~~g, ~~b);
 };
 
+function d3_rgbNumber(value) {
+  return d3_rgb(value >> 16, value >> 8 & 0xff, value & 0xff);
+}
+
+function d3_rgbString(value) {
+  return d3_rgbNumber(value) + "";
+}
+
 function d3_rgb(r, g, b) {
   return new d3_Rgb(r, g, b);
 }
@@ -4340,18 +4695,12 @@ d3_rgbPrototype.brighter = function(k) {
   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)));
+  return d3_rgb(Math.min(255, ~~(r / k)), Math.min(255, ~~(g / k)), Math.min(255, ~~(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));
+  return d3_rgb(~~(k * this.r), ~~(k * this.g), ~~(k * this.b));
 };
 
 d3_rgbPrototype.hsl = function() {
@@ -4434,7 +4783,8 @@ function d3_rgb_hsl(r, g, b) {
     else h = (r - g) / d + 4;
     h *= 60;
   } else {
-    s = h = 0;
+    h = NaN;
+    s = l > 0 && l < 1 ? 0 : h;
   }
   return d3_hsl(h, s, l);
 }
@@ -4459,157 +4809,157 @@ function d3_rgb_parseNumber(c) { // either integer or percentage
 }
 
 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"
+  aliceblue: 0xf0f8ff,
+  antiquewhite: 0xfaebd7,
+  aqua: 0x00ffff,
+  aquamarine: 0x7fffd4,
+  azure: 0xf0ffff,
+  beige: 0xf5f5dc,
+  bisque: 0xffe4c4,
+  black: 0x000000,
+  blanchedalmond: 0xffebcd,
+  blue: 0x0000ff,
+  blueviolet: 0x8a2be2,
+  brown: 0xa52a2a,
+  burlywood: 0xdeb887,
+  cadetblue: 0x5f9ea0,
+  chartreuse: 0x7fff00,
+  chocolate: 0xd2691e,
+  coral: 0xff7f50,
+  cornflowerblue: 0x6495ed,
+  cornsilk: 0xfff8dc,
+  crimson: 0xdc143c,
+  cyan: 0x00ffff,
+  darkblue: 0x00008b,
+  darkcyan: 0x008b8b,
+  darkgoldenrod: 0xb8860b,
+  darkgray: 0xa9a9a9,
+  darkgreen: 0x006400,
+  darkgrey: 0xa9a9a9,
+  darkkhaki: 0xbdb76b,
+  darkmagenta: 0x8b008b,
+  darkolivegreen: 0x556b2f,
+  darkorange: 0xff8c00,
+  darkorchid: 0x9932cc,
+  darkred: 0x8b0000,
+  darksalmon: 0xe9967a,
+  darkseagreen: 0x8fbc8f,
+  darkslateblue: 0x483d8b,
+  darkslategray: 0x2f4f4f,
+  darkslategrey: 0x2f4f4f,
+  darkturquoise: 0x00ced1,
+  darkviolet: 0x9400d3,
+  deeppink: 0xff1493,
+  deepskyblue: 0x00bfff,
+  dimgray: 0x696969,
+  dimgrey: 0x696969,
+  dodgerblue: 0x1e90ff,
+  firebrick: 0xb22222,
+  floralwhite: 0xfffaf0,
+  forestgreen: 0x228b22,
+  fuchsia: 0xff00ff,
+  gainsboro: 0xdcdcdc,
+  ghostwhite: 0xf8f8ff,
+  gold: 0xffd700,
+  goldenrod: 0xdaa520,
+  gray: 0x808080,
+  green: 0x008000,
+  greenyellow: 0xadff2f,
+  grey: 0x808080,
+  honeydew: 0xf0fff0,
+  hotpink: 0xff69b4,
+  indianred: 0xcd5c5c,
+  indigo: 0x4b0082,
+  ivory: 0xfffff0,
+  khaki: 0xf0e68c,
+  lavender: 0xe6e6fa,
+  lavenderblush: 0xfff0f5,
+  lawngreen: 0x7cfc00,
+  lemonchiffon: 0xfffacd,
+  lightblue: 0xadd8e6,
+  lightcoral: 0xf08080,
+  lightcyan: 0xe0ffff,
+  lightgoldenrodyellow: 0xfafad2,
+  lightgray: 0xd3d3d3,
+  lightgreen: 0x90ee90,
+  lightgrey: 0xd3d3d3,
+  lightpink: 0xffb6c1,
+  lightsalmon: 0xffa07a,
+  lightseagreen: 0x20b2aa,
+  lightskyblue: 0x87cefa,
+  lightslategray: 0x778899,
+  lightslategrey: 0x778899,
+  lightsteelblue: 0xb0c4de,
+  lightyellow: 0xffffe0,
+  lime: 0x00ff00,
+  limegreen: 0x32cd32,
+  linen: 0xfaf0e6,
+  magenta: 0xff00ff,
+  maroon: 0x800000,
+  mediumaquamarine: 0x66cdaa,
+  mediumblue: 0x0000cd,
+  mediumorchid: 0xba55d3,
+  mediumpurple: 0x9370db,
+  mediumseagreen: 0x3cb371,
+  mediumslateblue: 0x7b68ee,
+  mediumspringgreen: 0x00fa9a,
+  mediumturquoise: 0x48d1cc,
+  mediumvioletred: 0xc71585,
+  midnightblue: 0x191970,
+  mintcream: 0xf5fffa,
+  mistyrose: 0xffe4e1,
+  moccasin: 0xffe4b5,
+  navajowhite: 0xffdead,
+  navy: 0x000080,
+  oldlace: 0xfdf5e6,
+  olive: 0x808000,
+  olivedrab: 0x6b8e23,
+  orange: 0xffa500,
+  orangered: 0xff4500,
+  orchid: 0xda70d6,
+  palegoldenrod: 0xeee8aa,
+  palegreen: 0x98fb98,
+  paleturquoise: 0xafeeee,
+  palevioletred: 0xdb7093,
+  papayawhip: 0xffefd5,
+  peachpuff: 0xffdab9,
+  peru: 0xcd853f,
+  pink: 0xffc0cb,
+  plum: 0xdda0dd,
+  powderblue: 0xb0e0e6,
+  purple: 0x800080,
+  red: 0xff0000,
+  rosybrown: 0xbc8f8f,
+  royalblue: 0x4169e1,
+  saddlebrown: 0x8b4513,
+  salmon: 0xfa8072,
+  sandybrown: 0xf4a460,
+  seagreen: 0x2e8b57,
+  seashell: 0xfff5ee,
+  sienna: 0xa0522d,
+  silver: 0xc0c0c0,
+  skyblue: 0x87ceeb,
+  slateblue: 0x6a5acd,
+  slategray: 0x708090,
+  slategrey: 0x708090,
+  snow: 0xfffafa,
+  springgreen: 0x00ff7f,
+  steelblue: 0x4682b4,
+  tan: 0xd2b48c,
+  teal: 0x008080,
+  thistle: 0xd8bfd8,
+  tomato: 0xff6347,
+  turquoise: 0x40e0d0,
+  violet: 0xee82ee,
+  wheat: 0xf5deb3,
+  white: 0xffffff,
+  whitesmoke: 0xf5f5f5,
+  yellow: 0xffff00,
+  yellowgreen: 0x9acd32
 });
 
 d3_rgb_names.forEach(function(key, value) {
-  d3_rgb_names.set(key, d3_rgb_parse(value, d3_rgb, d3_hsl_rgb));
+  d3_rgb_names.set(key, d3_rgbNumber(value));
 });
 
 d3.interpolateRgb = d3_interpolateRgb;
@@ -4631,126 +4981,6 @@ function d3_interpolateRgb(a, b) {
   };
 }
 
-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 = +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) {
@@ -4759,7 +4989,7 @@ function d3_interpolateObject(a, b) {
       k;
   for (k in a) {
     if (k in b) {
-      i[k] = d3_interpolateByName(k)(a[k], b[k]);
+      i[k] = d3_interpolate(a[k], b[k]);
     } else {
       c[k] = a[k];
     }
@@ -4792,6 +5022,12 @@ function d3_interpolateArray(a, b) {
     return c;
   };
 }
+d3.interpolateNumber = d3_interpolateNumber;
+
+function d3_interpolateNumber(a, b) {
+  b -= a = +a;
+  return function(t) { return a + b * t; };
+}
 
 d3.interpolateString = d3_interpolateString;
 
@@ -4866,7 +5102,9 @@ function d3_interpolateString(a, b) {
 
   // Special optimization for only a single match.
   if (s.length === 1) {
-    return s[0] == null ? q[0].x : function() { return b; };
+    return s[0] == null
+        ? (o = q[0].x, function(t) { return o(t) + ""; })
+        : function() { return b; };
   }
 
   // Otherwise, interpolate each of the numbers and rejoin the string.
@@ -4886,22 +5124,132 @@ function d3_interpolate(a, b) {
   return f;
 }
 
-function d3_interpolateByName(name) {
-  return name == "transform"
-      ? d3_interpolateTransform
-      : d3_interpolate;
-}
-
 d3.interpolators = [
   function(a, b) {
     var t = typeof b;
-    return (t === "string" || t !== typeof a ? (d3_rgb_names.has(b) || /^(#|rgb\(|hsl\()/.test(b) ? d3_interpolateRgb : d3_interpolateString)
+    return (t === "string" ? (d3_rgb_names.has(b) || /^(#|rgb\(|hsl\()/.test(b) ? d3_interpolateRgb : d3_interpolateString)
         : b instanceof d3_Color ? d3_interpolateRgb
         : t === "object" ? (Array.isArray(b) ? d3_interpolateArray : d3_interpolateObject)
         : d3_interpolateNumber)(a, b);
   }
 ];
 
+d3.transform = function(string) {
+  var g = d3_document.createElementNS(d3.ns.prefix.svg, "g");
+  return (d3.transform = function(string) {
+    if (string != null) {
+      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.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_transitionPrototype.tween = function(name, tween) {
   var id = this.id;
   if (arguments.length < 2) return this.node().__transition__[id].tween.get(name);
@@ -4927,7 +5275,7 @@ d3_transitionPrototype.attr = function(nameNS, value) {
     return this;
   }
 
-  var interpolate = d3_interpolateByName(nameNS),
+  var interpolate = nameNS == "transform" ? d3_interpolateTransform : d3_interpolate,
       name = d3.ns.qualify(nameNS);
 
   // For attr(string, null), remove the attribute with the specified name.
@@ -4938,21 +5286,21 @@ d3_transitionPrototype.attr = function(nameNS, value) {
     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() {
+  // For attr(string, string), set the attribute with the specified name.
+  function attrTween(b) {
+    return b == null ? attrNull : (b += "", function() {
       var a = this.getAttribute(name), i;
       return a !== b && (i = interpolate(a, b), function(t) { this.setAttribute(name, i(t)); });
-    }
-    function attrStringNS() {
+    });
+  }
+  function attrTweenNS(b) {
+    return b == null ? attrNullNS : (b += "", function() {
       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);
-  });
+  return d3_transition_tween(this, "attr." + nameNS, value, name.local ? attrTweenNS : attrTween);
 };
 
 d3_transitionPrototype.attrTween = function(nameNS, tween) {
@@ -4962,7 +5310,6 @@ d3_transitionPrototype.attrTween = function(nameNS, tween) {
     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)); };
@@ -4990,35 +5337,34 @@ d3_transitionPrototype.style = function(name, value, priority) {
     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);
   }
 
+  // For style(name, string) or style(name, string, priority), set the style
+  // property with the specified name, using the specified priority.
   // 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() {
+  function styleString(b) {
+    return b == null ? styleNull : (b += "", function() {
       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 a !== b && (i = d3_interpolate(a, b), function(t) { this.style.setProperty(name, i(t), priority); });
+    });
+  }
 
-    return b == null ? styleNull
-        : (b += "", styleString);
-  });
+  return d3_transition_tween(this, "style." + name, value, styleString);
 };
 
 d3_transitionPrototype.styleTween = function(name, tween, priority) {
   if (arguments.length < 3) priority = "";
-  return this.tween("style." + name, function(d, i) {
+
+  function styleTween(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); };
-  });
+  }
+
+  return this.tween("style." + name, styleTween);
 };
 
 d3_transitionPrototype.text = function(value) {
@@ -5072,7 +5418,8 @@ d3_transitionPrototype.each = function(type, listener) {
     d3_transitionInheritId = inheritId;
   } else {
     d3_selection_each(this, function(node) {
-      node.__transition__[id].event.on(type, listener);
+      var transition = node.__transition__[id];
+      (transition.event || (transition.event = d3.dispatch("start", "end"))).on(type, listener);
     });
   }
   return this;
@@ -5111,7 +5458,6 @@ function d3_transitionNode(node, i, id, inherit) {
 
     transition = lock[id] = {
       tween: new d3_Map,
-      event: d3.dispatch("start", "end"), // TODO construct lazily?
       time: time,
       ease: inherit.ease,
       delay: inherit.delay,
@@ -5123,19 +5469,17 @@ function d3_transitionNode(node, i, id, inherit) {
     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;
+      if (delay <= elapsed) return start(elapsed);
+      d3_timer_replace(start, delay, time);
 
       function start(elapsed) {
         if (lock.active > id) return stop();
         lock.active = id;
-        event.start.call(node, d, i);
+        transition.event && transition.event.start.call(node, d, i);
 
         transition.tween.forEach(function(key, value) {
           if (value = value.call(node, d, i)) {
@@ -5143,8 +5487,8 @@ function d3_transitionNode(node, i, id, inherit) {
           }
         });
 
-        if (!tick(elapsed)) d3.timer(tick, 0, time);
-        return 1;
+        if (tick(elapsed)) return 1;
+        d3_timer_replace(tick, 0, time);
       }
 
       function tick(elapsed) {
@@ -5160,7 +5504,7 @@ function d3_transitionNode(node, i, id, inherit) {
 
         if (t >= 1) {
           stop();
-          event.end.call(node, d, i);
+          transition.event && transition.event.end.call(node, d, i);
           return 1;
         }
       }
@@ -5171,27 +5515,47 @@ function d3_transitionNode(node, i, id, inherit) {
         return 1;
       }
     }, 0, time);
-
-    return transition;
   }
 }
 
-d3.xhr = function(url, mimeType, callback) {
+d3.xhr = d3_xhrType(d3_identity);
+
+function d3_xhrType(response) {
+  return function(url, mimeType, callback) {
+    if (arguments.length === 2 && typeof mimeType === "function") callback = mimeType, mimeType = null;
+    return d3_xhr(url, mimeType, response, callback);
+  };
+}
+
+function d3_xhr(url, mimeType, response, callback) {
   var xhr = {},
       dispatch = d3.dispatch("progress", "load", "error"),
       headers = {},
-      response = d3_identity,
-      request = new (d3_window.XDomainRequest && /^(http(s)?:)?\/\//.test(url) ? XDomainRequest : XMLHttpRequest);
+      request = new XMLHttpRequest,
+      responseType = null;
+
+  // If IE does not support CORS, use XDomainRequest.
+  if (d3_window.XDomainRequest
+      && !("withCredentials" in request)
+      && /^(http(s)?:)?\/\//.test(url)) request = new XDomainRequest;
 
   "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);
+    var status = request.status, result;
+    if (!status && request.responseText || status >= 200 && status < 300 || status === 304) {
+      try {
+        result = response.call(xhr, request);
+      } catch (e) {
+        dispatch.error.call(xhr, e);
+        return;
+      }
+      dispatch.load.call(xhr, result);
+    } else {
+      dispatch.error.call(xhr, request);
+    }
   }
 
   request.onprogress = function(event) {
@@ -5216,6 +5580,14 @@ d3.xhr = function(url, mimeType, callback) {
     return xhr;
   };
 
+  // Specifies what type the response value should take;
+  // for instance, arraybuffer, blob, document, or text.
+  xhr.responseType = function(value) {
+    if (!arguments.length) return responseType;
+    responseType = 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) {
@@ -5237,6 +5609,7 @@ d3.xhr = function(url, mimeType, callback) {
     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 (responseType != null) request.responseType = responseType;
     if (callback != null) xhr.on("error", callback).on("load", function(request) { callback(null, request); });
     request.send(data == null ? null : data);
     return xhr;
@@ -5249,7 +5622,6 @@ d3.xhr = function(url, mimeType, callback) {
 
   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));
 };
 
@@ -5259,16 +5631,12 @@ function d3_xhr_fixCallback(callback) {
       : callback;
 }
 
-d3.text = function() {
-  return d3.xhr.apply(d3, arguments).response(d3_text);
-};
-
-function d3_text(request) {
+d3.text = d3_xhrType(function(request) {
   return request.responseText;
-}
+});
 
 d3.json = function(url, callback) {
-  return d3.xhr(url, "application/json", callback).response(d3_json);
+  return d3_xhr(url, "application/json", d3_json, callback);
 };
 
 function d3_json(request) {
@@ -5276,7 +5644,7 @@ function d3_json(request) {
 }
 
 d3.html = function(url, callback) {
-  return d3.xhr(url, "text/html", callback).response(d3_html);
+  return d3_xhr(url, "text/html", d3_html, callback);
 };
 
 function d3_html(request) {
@@ -5285,20 +5653,17 @@ function d3_html(request) {
   return range.createContextualFragment(request.responseText);
 }
 
-d3.xml = function() {
-  return d3.xhr.apply(d3, arguments).response(d3_xml);
-};
-
-function d3_xml(request) {
+d3.xml = d3_xhrType(function(request) {
   return request.responseXML;
-}
+});
   return d3;
 })();
 d3.combobox = function() {
     var event = d3.dispatch('accept'),
-        data = [];
+        data = [],
+        suggestions = [];
 
-    var fetcher = function(val, data, cb) {
+    var fetcher = function(val, cb) {
         cb(data.filter(function(d) {
             return d.title
                 .toString()
@@ -5308,44 +5673,53 @@ d3.combobox = function() {
     };
 
     var combobox = function(input) {
-        var idx = -1, container, shown = false;
+        var idx = -1,
+            container = d3.select(document.body)
+                .selectAll('div.combobox')
+                .filter(function(d) { return d === input.node(); }),
+            shown = !container.empty();
 
         input
             .classed('combobox-input', true)
+            .on('focus.typeahead', focus)
+            .on('blur.typeahead', blur)
+            .on('keydown.typeahead', keydown)
+            .on('keyup.typeahead', keyup)
+            .on('input.typeahead', change)
             .each(function() {
                 var parent = this.parentNode,
                     sibling = this.nextSibling;
-                d3.select(parent)
-                    .insert('div', function() { return sibling; })
-                    .attr('class', 'combobox-carat')
+
+                var carat = d3.select(parent).selectAll('.combobox-carat')
+                    .filter(function(d) { return d === input.node(); })
+                    .data([input.node()]);
+
+                carat.enter().insert('div', function() { return sibling; })
+                    .attr('class', 'combobox-carat');
+
+                carat
                     .on('mousedown', function () {
                         // prevent the form element from blurring. it blurs
                         // on mousedown
                         d3.event.stopPropagation();
                         d3.event.preventDefault();
-                        mousedown();
+                        input.node().focus();
                     });
             });
 
-        function updateSize() {
-            var rect = input.node().getBoundingClientRect();
-            container.style({
-                'left': rect.left + 'px',
-                'width': rect.width + 'px',
-                'top': rect.height + rect.top + 'px'
-            });
+        function focus() {
+            fetch(render);
         }
 
         function blur() {
-            // hide the combobox whenever the input element
-            // loses focus
-            slowHide();
+            window.setTimeout(hide, 150);
         }
 
         function show() {
             if (!shown) {
                 container = d3.select(document.body)
                     .insert('div', ':first-child')
+                    .datum(input.node())
                     .attr('class', 'combobox')
                     .style({
                         position: 'absolute',
@@ -5354,7 +5728,7 @@ d3.combobox = function() {
                     });
 
                 d3.select(document.body)
-                    .on('scroll.combobox', updateSize, true);
+                    .on('scroll.combobox', render, true);
 
                 shown = true;
             }
@@ -5372,24 +5746,33 @@ d3.combobox = function() {
             }
         }
 
-        function slowHide() {
-            window.setTimeout(hide, 150);
-        }
         function keydown() {
-           if (!shown) return;
            switch (d3.event.keyCode) {
-               // down arrow
-               case 40:
-                   next();
+               // backspace, delete
+               case 8:
+               case 46:
+                   input.on('input.typeahead', function() {
+                       idx = -1;
+                       render();
+                       input.on('input.typeahead', change);
+                   });
+                   break;
+               // tab
+               case 9:
+                   container.selectAll('a.selected').each(event.accept);
+                   break;
+               // return
+               case 13:
                    d3.event.preventDefault();
                    break;
                // up arrow
                case 38:
-                   prev();
+                   nav(-1);
                    d3.event.preventDefault();
                    break;
-               // escape, tab
-               case 13:
+               // down arrow
+               case 40:
+                   nav(+1);
                    d3.event.preventDefault();
                    break;
            }
@@ -5402,171 +5785,118 @@ d3.combobox = function() {
                 case 27:
                     hide();
                     break;
-                // escape, tab
-                case 9:
+                // return
                 case 13:
-                    if (!shown) return;
-                    accept();
+                    container.selectAll('a.selected').each(event.accept);
+                    hide();
                     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 change() {
+            fetch(function() {
+                autocomplete();
+                render();
+            });
         }
 
-        function prev() {
-            idx = Math.max(idx - 1, 0);
-            highlight();
+        function nav(dir) {
+            idx = Math.max(Math.min(idx + dir, suggestions.length - 1), 0);
+            input.property('value', suggestions[idx].value);
+            render();
+            ensureVisible();
         }
 
-        var prevValue, prevCompletion;
-
-        function autocomplete(e, data) {
-
+        function value() {
             var value = input.property('value'),
-                match;
+                start = input.property('selectionStart'),
+                end = input.property('selectionEnd');
 
-            for (var i = 0; i < data.length; i++) {
-                if (data[i].value.toLowerCase().indexOf(value.toLowerCase()) === 0) {
-                    match = data[i].value;
-                    break;
-                }
+            if (start && end) {
+                value = value.substring(0, start);
             }
 
-            // backspace
-            if (e.keyCode === 8) {
-                prevValue = value;
-                prevCompletion = '';
+            return value;
+        }
 
-            } 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 fetch(cb) {
+            fetcher.call(input, value(), function(_) {
+                suggestions = _;
+                cb();
+            });
         }
 
+        function autocomplete() {
+            var v = value();
 
-        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;
-            }
-        }
+            idx = -1;
 
-        function update(value) {
+            if (!v) return;
 
-            if (typeof value === 'undefined') {
-                value = input.property('value');
+            for (var i = 0; i < suggestions.length; i++) {
+                if (suggestions[i].value.toLowerCase().indexOf(v.toLowerCase()) === 0) {
+                    var completion = v + suggestions[i].value.substr(v.length);
+                    idx = i;
+                    input.property('value', completion);
+                    input.node().setSelectionRange(v.length, completion.length);
+                    return;
+                }
             }
+        }
 
-            var e = d3.event;
-
-            function render(data) {
+        function render() {
+            if (suggestions.length && document.activeElement === input.node()) {
+                show();
+            } else {
+                hide();
+                return;
+            }
 
-                if (data.length &&
-                    document.activeElement === input.node()) show();
-                else return hide();
+            var options = container
+                .selectAll('a.combobox-option')
+                .data(suggestions, function(d) { return d.value; });
 
-                autocomplete(e, data);
+            options.enter().append('a')
+                .attr('class', 'combobox-option')
+                .text(function(d) { return d.value; });
 
-                updateSize();
+            options
+                .attr('title', function(d) { return d.title; })
+                .classed('selected', function(d, i) { return i == idx; })
+                .on('mouseover', select)
+                .on('click', accept)
+                .order();
 
-                var options = container
-                    .selectAll('a.combobox-option')
-                    .data(data, function(d) { return d.value; });
+            options.exit()
+                .remove();
 
-                options.enter()
-                    .append('a')
-                    .text(function(d) { return d.value; })
-                    .attr('class', 'combobox-option')
-                    .attr('title', function(d) { return d.title; })
-                    .on('click', select);
+            var rect = input.node().getBoundingClientRect();
 
-                options.exit().remove();
+            container.style({
+                'left': rect.left + 'px',
+                'width': rect.width + 'px',
+                'top': rect.height + rect.top + 'px'
+            });
+        }
 
-                options
-                    .classed('selected', function(d, i) { return i == idx; })
-                    .order();
-            }
+        function select(d, i) {
+            idx = i;
+            render();
+        }
 
-            fetcher.apply(input, [value, data, render]);
+        function ensureVisible() {
+            var node = container.selectAll('a.selected').node();
+            if (node) node.scrollIntoView();
         }
 
-        // select the choice given as d
-        function select(d) {
+        function accept(d) {
+            if (!shown) return;
             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);
     };
 
     combobox.fetcher = function(_) {
@@ -5583,8 +5913,6 @@ d3.combobox = function() {
 
     return d3.rebind(combobox, event, 'on');
 };
-
-d3.combobox.id = 0;
 d3.geo.tile = function() {
   var size = [960, 500],
       scale = 256,
@@ -5880,13 +6208,13 @@ d3.selection.prototype.one = function (type, listener, capture) {
     target.on(typeOnce, one, capture);
     return this;
 };
-d3.selection.prototype.size = function (size) {
+d3.selection.prototype.dimensions = function (dimensions) {
     if (!arguments.length) {
         var node = this.node();
         return [node.offsetWidth,
                 node.offsetHeight];
     }
-    return this.attr({width: size[0], height: size[1]});
+    return this.attr({width: dimensions[0], height: dimensions[1]});
 };
 d3.selection.prototype.trigger = function (type) {
     this.each(function() {
@@ -6070,10 +6398,10 @@ d3.curtain = function() {
             var html = parts[0] ? '<span>' + parts[0] + '</span>' : '';
             if (parts[1]) html += '<span class="bold">' + parts[1] + '</span>';
 
-            var size = tooltip.classed('in', true)
+            var dimensions = tooltip.classed('in', true)
                 .select('.tooltip-inner')
                     .html(html)
-                    .size();
+                    .dimensions();
 
             var pos;
 
@@ -6082,23 +6410,23 @@ d3.curtain = function() {
 
             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];
+                pos = [box.left + box.width / 2 - dimensions[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];
+                pos = [box.left + box.width, box.top + box.height / 2 - dimensions[1] / 2];
 
             } else if (box.left > 300) {
                 side = 'left';
-                pos = [box.left - 200, box.top + box.height / 2 - size[1] / 2];
+                pos = [box.left - 200, box.top + box.height / 2 - dimensions[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)
+                Math.min(Math.max(10, pos[0]), w - dimensions[0] - 10),
+                Math.min(Math.max(10, pos[1]), h - dimensions[1] - 10)
             ];
 
 
@@ -6142,6 +6470,32 @@ d3.curtain = function() {
 
     return d3.rebind(curtain, event, 'on');
 };
+// Like selection.property('value', ...), but avoids no-op value sets,
+// which can result in layout/repaint thrashing in some situations.
+d3.selection.prototype.value = function(value) {
+    function d3_selection_value(value) {
+      function valueNull() {
+        delete this.value;
+      }
+
+      function valueConstant() {
+        if (this.value !== value) this.value = value;
+      }
+
+      function valueFunction() {
+        var x = value.apply(this, arguments);
+        if (x == null) delete this.value;
+        else if (this.value !== x) this.value = x;
+      }
+
+      return value == null
+          ? valueNull : (typeof value === "function"
+          ? valueFunction : valueConstant);
+    }
+
+    if (!arguments.length) return this.property('value');
+    return this.each(d3_selection_value(value));
+};
 var JXON = new (function () {
   var
     sValueProp = "keyValue", sAttributesProp = "keyAttributes", sAttrPref = "@", /* you can customize these values */
@@ -12915,717 +13269,502 @@ function isObject(obj) {
 });
 ;
 
-/******************************************************************************
-       rtree.js - General-Purpose Non-Recursive Javascript R-Tree Library
-       Version 0.6.2, December 5st 2009
+/*
+ (c) 2013, Vladimir Agafonkin
+ RBush, a JavaScript library for high-performance 2D spatial indexing of points and rectangles.
+ https://github.com/mourner/rbush
+*/
 
-@license Copyright (c) 2009 Jon-Carlos Rivera
+(function () { 'use strict';
 
-  Permission is hereby granted, free of charge, to any person obtaining
-  a copy of this software and associated documentation files (the
-  "Software"), to deal in the Software without restriction, including
-  without limitation the rights to use, copy, modify, merge, publish,
-  distribute, sublicense, and/or sell copies of the Software, and to
-  permit persons to whom the Software is furnished to do so, subject to
-  the following conditions:
+function rbush(maxEntries, format) {
 
-  The above copyright notice and this permission notice shall be
-  included in all copies or substantial portions of the Software.
+    // jshint newcap: false, validthis: true
+    if (!(this instanceof rbush)) { return new rbush(maxEntries, format); }
 
-  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
-  EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
-  MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
-  NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
-  LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
-  OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
-  WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+    this._maxEntries = Math.max(4, maxEntries || 9);
+    this._minEntries = Math.max(2, Math.ceil(this._maxEntries * 0.4));
 
-       Jon-Carlos Rivera - imbcmdth@hotmail.com
-******************************************************************************/
+    this._initFormat(format);
 
-/**
- * RTree - A simple r-tree structure for great results.
- * @constructor
- */
-var RTree = function(width){
-       // Variables to control tree-dimensions
-       var _Min_Width = 3;  // Minimum width of any node before a merge
-       var _Max_Width = 6;  // Maximum width of any node before a split
-       if(!isNaN(width)){ _Min_Width = Math.floor(width/2.0); _Max_Width = width;}
-       // Start with an empty root-tree
-       var _T = {x:0, y:0, w:0, h:0, id:"root", nodes:[] };
-
-       var isArray = function(o) {
-               return Object.prototype.toString.call(o) === '[object Array]';
-       };
-
-       /**@function
-        * @description Function to generate unique strings for element IDs
-        * @param {String} n                    The prefix to use for the IDs generated.
-        * @return {String}                             A guarenteed unique ID.
-        */
-    var _name_to_id = (function() {
-        // hide our idCache inside this closure
-        var idCache = {};
-
-        // return the api: our function that returns a unique string with incrementing number appended to given idPrefix
-        return function(idPrefix) {
-            var idVal = 0;
-            if(idPrefix in idCache) {
-                idVal = idCache[idPrefix]++;
-            } else {
-                idCache[idPrefix] = 0;
+    this.clear();
+}
+
+rbush.prototype = {
+
+    search: function (bbox) {
+
+        var node = this.data,
+            result = [];
+
+        if (!this._intersects(bbox, node.bbox)) { return result; }
+
+        var nodesToSearch = [],
+            i, len, child, childBBox;
+
+        while (node) {
+            for (i = 0, len = node.children.length; i < len; i++) {
+                child = node.children[i];
+                childBBox = node.leaf ? this._toBBox(child) : child.bbox;
+
+                if (this._intersects(bbox, childBBox)) {
+                    (node.leaf ? result : nodesToSearch).push(child);
+                }
             }
-            return idPrefix + "_" + idVal;
-        }
-    })();
-
-       // This is my special addition to the world of r-trees
-       // every other (simple) method I found produced crap trees
-       // this skews insertions to prefering squarer and emptier nodes
-       RTree.Rectangle.squarified_ratio = function(l, w, fill) {
-         // Area of new enlarged rectangle
-         var lperi = (l + w) / 2.0; // Average size of a side of the new rectangle
-         var larea = l * w; // Area of new rectangle
-         // return the ratio of the perimeter to the area - the closer to 1 we are,
-         // the more "square" a rectangle is. conversly, when approaching zero the
-         // more elongated a rectangle is
-         var lgeo = larea / (lperi*lperi);
-         return(larea * fill / lgeo);
-       };
-
-       /**find the best specific node(s) for object to be deleted from
-        * [ leaf node parent ] = _remove_subtree(rectangle, object, root)
-        * @private
-        */
-       var _remove_subtree = function(rect, obj, root) {
-               var hit_stack = []; // Contains the elements that overlap
-               var count_stack = []; // Contains the elements that overlap
-               var ret_array = [];
-               var current_depth = 1;
-
-               if(!rect || !RTree.Rectangle.overlap_rectangle(rect, root))
-                return ret_array;
-
-               var ret_obj = {x:rect.x, y:rect.y, w:rect.w, h:rect.h, target:obj};
-
-               count_stack.push(root.nodes.length);
-               hit_stack.push(root);
-
-               do {
-                       var tree = hit_stack.pop();
-                       var i = count_stack.pop()-1;
-
-                 if("target" in ret_obj) { // We are searching for a target
-                               while(i >= 0)   {
-                                       var ltree = tree.nodes[i];
-                                       if(RTree.Rectangle.overlap_rectangle(ret_obj, ltree)) {
-                                               if( (ret_obj.target && "leaf" in ltree && ltree.leaf === ret_obj.target)
-                                                       ||(!ret_obj.target && ("leaf" in ltree || RTree.Rectangle.contains_rectangle(ltree, ret_obj)))) { // A Match !!
-                                               // Yup we found a match...
-                                               // we can cancel search and start walking up the list
-                                               if("nodes" in ltree) {// If we are deleting a node not a leaf...
-                                                       ret_array = _search_subtree(ltree, true, [], ltree);
-                                                       tree.nodes.splice(i, 1);
-                                               } else {
-                                                               ret_array = tree.nodes.splice(i, 1);
-                                                       }
-                                                       // Resize MBR down...
-                                                       RTree.Rectangle.make_MBR(tree.nodes, tree);
-                                                       delete ret_obj.target;
-                                                       if(tree.nodes.length < _Min_Width) { // Underflow
-                                                               ret_obj.nodes = _search_subtree(tree, true, [], tree);
-                                                       }
-                                                       break;
-                                       }/*     else if("load" in ltree) { // A load
-                                       }*/     else if("nodes" in ltree) { // Not a Leaf
-                                               current_depth += 1;
-                                               count_stack.push(i);
-                                               hit_stack.push(tree);
-                                               tree = ltree;
-                                               i = ltree.nodes.length;
-                                       }
-                                 }
-                                       i -= 1;
-                               }
-                       } else if("nodes" in ret_obj) { // We are unsplitting
-                               tree.nodes.splice(i+1, 1); // Remove unsplit node
-                               // ret_obj.nodes contains a list of elements removed from the tree so far
-                               if(tree.nodes.length > 0)
-                                       RTree.Rectangle.make_MBR(tree.nodes, tree);
-                               for(var t = 0;t<ret_obj.nodes.length;t++)
-                                       _insert_subtree(ret_obj.nodes[t], tree);
-                               ret_obj.nodes.length = 0;
-                               if(hit_stack.length == 0 && tree.nodes.length <= 1) { // Underflow..on root!
-                                       ret_obj.nodes = _search_subtree(tree, true, ret_obj.nodes, tree);
-                                       tree.nodes.length = 0;
-                                       hit_stack.push(tree);
-                                       count_stack.push(1);
-                               } else if(hit_stack.length > 0 && tree.nodes.length < _Min_Width) { // Underflow..AGAIN!
-                                       ret_obj.nodes = _search_subtree(tree, true, ret_obj.nodes, tree);
-                                       tree.nodes.length = 0;
-                               }else {
-                                       delete ret_obj.nodes; // Just start resizing
-                               }
-                       } else { // we are just resizing
-                               RTree.Rectangle.make_MBR(tree.nodes, tree);
-                       }
-                       current_depth -= 1;
-               }while(hit_stack.length > 0);
-
-               return(ret_array);
-       };
-
-       /**choose the best damn node for rectangle to be inserted into
-        * [ leaf node parent ] = _choose_leaf_subtree(rectangle, root to start search at)
-        * @private
-        */
-       var _choose_leaf_subtree = function(rect, root) {
-               var best_choice_index = -1;
-               var best_choice_stack = [];
-               var best_choice_area;
-
-               var load_callback = function(local_tree, local_node){
-                       return(function(data) {
-                               local_tree._attach_data(local_node, data);
-                       });
-               };
-
-               best_choice_stack.push(root);
-               var nodes = root.nodes;
-
-               do {
-                       if(best_choice_index != -1)     {
-                               best_choice_stack.push(nodes[best_choice_index]);
-                               nodes = nodes[best_choice_index].nodes;
-                               best_choice_index = -1;
-                       }
 
-                       for(var i = nodes.length-1; i >= 0; i--) {
-                               var ltree = nodes[i];
-                               if("leaf" in ltree) {
-                                       // Bail out of everything and start inserting
-                                       best_choice_index = -1;
-                                       break;
-                         } /*else if(ltree.load) {
-                               throw( "Can't insert into partially loaded tree ... yet!");
-                               //jQuery.getJSON(ltree.load, load_callback(this, ltree));
-                               //delete ltree.load;
-                       }*/
-                         // Area of new enlarged rectangle
-                         var old_lratio = RTree.Rectangle.squarified_ratio(ltree.w, ltree.h, ltree.nodes.length+1);
-
-                         // Enlarge rectangle to fit new rectangle
-                         var nw = Math.max(ltree.x+ltree.w, rect.x+rect.w) - Math.min(ltree.x, rect.x);
-                         var nh = Math.max(ltree.y+ltree.h, rect.y+rect.h) - Math.min(ltree.y, rect.y);
-
-                         // Area of new enlarged rectangle
-                         var lratio = RTree.Rectangle.squarified_ratio(nw, nh, ltree.nodes.length+2);
-
-                         if(best_choice_index < 0 || Math.abs(lratio - old_lratio) < best_choice_area) {
-                               best_choice_area = Math.abs(lratio - old_lratio); best_choice_index = i;
-                         }
-                       }
-               }while(best_choice_index != -1);
-
-               return(best_choice_stack);
-       };
-
-       /**split a set of nodes into two roughly equally-filled nodes
-        * [ an array of two new arrays of nodes ] = linear_split(array of nodes)
-        * @private
-        */
-       var _linear_split = function(nodes) {
-               var n = _pick_linear(nodes);
-               while(nodes.length > 0) {
-                       _pick_next(nodes, n[0], n[1]);
-               }
-               return(n);
-       };
-
-       /**insert the best source rectangle into the best fitting parent node: a or b
-        * [] = pick_next(array of source nodes, target node array a, target node array b)
-        * @private
-        */
-       var _pick_next = function(nodes, a, b) {
-         // Area of new enlarged rectangle
-               var area_a = RTree.Rectangle.squarified_ratio(a.w, a.h, a.nodes.length+1);
-               var area_b = RTree.Rectangle.squarified_ratio(b.w, b.h, b.nodes.length+1);
-               var high_area_delta;
-               var high_area_node;
-               var lowest_growth_group;
-
-               for(var i = nodes.length-1; i>=0;i--) {
-                       var l = nodes[i];
-                       var new_area_a = {};
-                       new_area_a.x = Math.min(a.x, l.x); new_area_a.y = Math.min(a.y, l.y);
-                       new_area_a.w = Math.max(a.x+a.w, l.x+l.w) - new_area_a.x;       new_area_a.h = Math.max(a.y+a.h, l.y+l.h) - new_area_a.y;
-                       var change_new_area_a = Math.abs(RTree.Rectangle.squarified_ratio(new_area_a.w, new_area_a.h, a.nodes.length+2) - area_a);
-
-                       var new_area_b = {};
-                       new_area_b.x = Math.min(b.x, l.x); new_area_b.y = Math.min(b.y, l.y);
-                       new_area_b.w = Math.max(b.x+b.w, l.x+l.w) - new_area_b.x;       new_area_b.h = Math.max(b.y+b.h, l.y+l.h) - new_area_b.y;
-                       var change_new_area_b = Math.abs(RTree.Rectangle.squarified_ratio(new_area_b.w, new_area_b.h, b.nodes.length+2) - area_b);
-
-                       if( !high_area_node || !high_area_delta || Math.abs( change_new_area_b - change_new_area_a ) < high_area_delta ) {
-                               high_area_node = i;
-                               high_area_delta = Math.abs(change_new_area_b-change_new_area_a);
-                               lowest_growth_group = change_new_area_b < change_new_area_a ? b : a;
-                       }
-               }
-               var temp_node = nodes.splice(high_area_node, 1)[0];
-               if(a.nodes.length + nodes.length + 1 <= _Min_Width)     {
-                       a.nodes.push(temp_node);
-                       RTree.Rectangle.expand_rectangle(a, temp_node);
-               }       else if(b.nodes.length + nodes.length + 1 <= _Min_Width) {
-                       b.nodes.push(temp_node);
-                       RTree.Rectangle.expand_rectangle(b, temp_node);
-               }
-               else {
-                       lowest_growth_group.nodes.push(temp_node);
-                       RTree.Rectangle.expand_rectangle(lowest_growth_group, temp_node);
-               }
-       };
-
-       /**pick the "best" two starter nodes to use as seeds using the "linear" criteria
-        * [ an array of two new arrays of nodes ] = pick_linear(array of source nodes)
-        * @private
-        */
-       var _pick_linear = function(nodes) {
-               var lowest_high_x = nodes.length-1;
-               var highest_low_x = 0;
-               var lowest_high_y = nodes.length-1;
-               var highest_low_y = 0;
-        var t1, t2;
-
-               for(var i = nodes.length-2; i>=0;i--)   {
-                       var l = nodes[i];
-                       if(l.x > nodes[highest_low_x].x ) highest_low_x = i;
-                       else if(l.x+l.w < nodes[lowest_high_x].x+nodes[lowest_high_x].w) lowest_high_x = i;
-                       if(l.y > nodes[highest_low_y].y ) highest_low_y = i;
-                       else if(l.y+l.h < nodes[lowest_high_y].y+nodes[lowest_high_y].h) lowest_high_y = i;
-               }
-               var dx = Math.abs((nodes[lowest_high_x].x+nodes[lowest_high_x].w) - nodes[highest_low_x].x);
-               var dy = Math.abs((nodes[lowest_high_y].y+nodes[lowest_high_y].h) - nodes[highest_low_y].y);
-               if( dx > dy )   {
-                       if(lowest_high_x > highest_low_x)       {
-                               t1 = nodes.splice(lowest_high_x, 1)[0];
-                               t2 = nodes.splice(highest_low_x, 1)[0];
-                       }       else {
-                               t2 = nodes.splice(highest_low_x, 1)[0];
-                               t1 = nodes.splice(lowest_high_x, 1)[0];
-                       }
-               }       else {
-                       if(lowest_high_y > highest_low_y)       {
-                               t1 = nodes.splice(lowest_high_y, 1)[0];
-                               t2 = nodes.splice(highest_low_y, 1)[0];
-                       }       else {
-                               t2 = nodes.splice(highest_low_y, 1)[0];
-                               t1 = nodes.splice(lowest_high_y, 1)[0];
-                       }
-               }
-               return([{x:t1.x, y:t1.y, w:t1.w, h:t1.h, nodes:[t1]},
-                             {x:t2.x, y:t2.y, w:t2.w, h:t2.h, nodes:[t2]} ]);
-       };
-
-       var _attach_data = function(node, more_tree){
-               node.nodes = more_tree.nodes;
-               node.x = more_tree.x; node.y = more_tree.y;
-               node.w = more_tree.w; node.h = more_tree.h;
-               return(node);
-       };
-
-       /**non-recursive internal search function
-        * [ nodes | objects ] = _search_subtree(rectangle, [return node data], [array to fill], root to begin search at)
-        * @private
-        */
-       var _search_subtree = function(rect, return_node, return_array, root) {
-               var hit_stack = []; // Contains the elements that overlap
-
-               if(!RTree.Rectangle.overlap_rectangle(rect, root))
-                return(return_array);
-
-               var load_callback = function(local_tree, local_node){
-                       return(function(data) {
-                               local_tree._attach_data(local_node, data);
-                       });
-               };
-
-               hit_stack.push(root.nodes);
-
-               do {
-                       var nodes = hit_stack.pop();
-
-                       for(var i = nodes.length-1; i >= 0; i--) {
-                               var ltree = nodes[i];
-                         if(RTree.Rectangle.overlap_rectangle(rect, ltree)) {
-                               if("nodes" in ltree) { // Not a Leaf
-                                       hit_stack.push(ltree.nodes);
-                               } else if("leaf" in ltree) { // A Leaf !!
-                                       if(!return_node)
-                                               return_array.push(ltree.leaf);
-                                       else
-                                               return_array.push(ltree);
-                               }/*     else if("load" in ltree) { // We need to fetch a URL for some more tree data
-                                       jQuery.getJSON(ltree.load, load_callback(this, ltree));
-                                       delete ltree.load;
-                               //      i++; // Replay this entry
-                               }*/
-                               }
-                       }
-               }while(hit_stack.length > 0);
-
-               return(return_array);
-       };
-
-       /**non-recursive internal insert function
-        * [] = _insert_subtree(rectangle, object to insert, root to begin insertion at)
-        * @private
-        */
-       var _insert_subtree = function(node, root) {
-               var bc; // Best Current node
-               // Initial insertion is special because we resize the Tree and we don't
-               // care about any overflow (seriously, how can the first object overflow?)
-               if(root.nodes.length == 0) {
-                       root.x = node.x; root.y = node.y;
-                       root.w = node.w; root.h = node.h;
-                       root.nodes.push(node);
-                       return;
-               }
+            node = nodesToSearch.pop();
+        }
 
-               // Find the best fitting leaf node
-               // choose_leaf returns an array of all tree levels (including root)
-               // that were traversed while trying to find the leaf
-               var tree_stack = _choose_leaf_subtree(node, root);
-               var ret_obj = node;//{x:rect.x,y:rect.y,w:rect.w,h:rect.h, leaf:obj};
-
-               // Walk back up the tree resizing and inserting as needed
-               do {
-                       //handle the case of an empty node (from a split)
-                       if(bc && "nodes" in bc && bc.nodes.length == 0) {
-                               var pbc = bc; // Past bc
-                               bc = tree_stack.pop();
-                               for(var t=0;t<bc.nodes.length;t++)
-                                       if(bc.nodes[t] === pbc || bc.nodes[t].nodes.length == 0) {
-                                               bc.nodes.splice(t, 1);
-                                               break;
-                               }
-                       } else {
-                               bc = tree_stack.pop();
-                       }
+        return result;
+    },
 
-                       // If there is data attached to this ret_obj
-                       if("leaf" in ret_obj || "nodes" in ret_obj || isArray(ret_obj)) {
-                               // Do Insert
-                               if(isArray(ret_obj)) {
-                                       for(var ai = 0; ai < ret_obj.length; ai++) {
-                                               RTree.Rectangle.expand_rectangle(bc, ret_obj[ai]);
-                                       }
-                                       bc.nodes = bc.nodes.concat(ret_obj);
-                               } else {
-                                       RTree.Rectangle.expand_rectangle(bc, ret_obj);
-                                       bc.nodes.push(ret_obj); // Do Insert
-                               }
-
-                               if(bc.nodes.length <= _Max_Width)       { // Start Resizeing Up the Tree
-                                       ret_obj = {x:bc.x,y:bc.y,w:bc.w,h:bc.h};
-                               }       else { // Otherwise Split this Node
-                                       // linear_split() returns an array containing two new nodes
-                                       // formed from the split of the previous node's overflow
-                                       var a = _linear_split(bc.nodes);
-                                       ret_obj = a;//[1];
-
-                                       if(tree_stack.length < 1)       { // If are splitting the root..
-                                               bc.nodes.push(a[0]);
-                                               tree_stack.push(bc);     // Reconsider the root element
-                                               ret_obj = a[1];
-                                       } /*else {
-                                               delete bc;
-                                       }*/
-                               }
-                       }       else { // Otherwise Do Resize
-                               //Just keep applying the new bounding rectangle to the parents..
-                               RTree.Rectangle.expand_rectangle(bc, ret_obj);
-                               ret_obj = {x:bc.x,y:bc.y,w:bc.w,h:bc.h};
-                       }
-               } while(tree_stack.length > 0);
-       };
-
-       /**quick 'n' dirty function for plugins or manually drawing the tree
-        * [ tree ] = RTree.get_tree(): returns the raw tree data. useful for adding
-        * @public
-        * !! DEPRECATED !!
-        */
-       this.get_tree = function() {
-               return _T;
-       };
-
-       /**quick 'n' dirty function for plugins or manually loading the tree
-        * [ tree ] = RTree.set_tree(sub-tree, where to attach): returns the raw tree data. useful for adding
-        * @public
-        * !! DEPRECATED !!
-        */
-       this.set_tree = function(new_tree, where) {
-               if(!where)
-                       where = _T;
-               return(_attach_data(where, new_tree));
-       };
-
-       /**non-recursive search function
-        * [ nodes | objects ] = RTree.search(rectangle, [return node data], [array to fill])
-        * @public
-        */
-       this.search = function(rect, return_node, return_array) {
-               if(arguments.length < 1)
-                       throw "Wrong number of arguments. RT.Search requires at least a bounding rectangle."
-
-               switch(arguments.length) {
-                       case 1:
-                               arguments[1] = false;// Add an "return node" flag - may be removed in future
-                       case 2:
-                               arguments[2] = []; // Add an empty array to contain results
-                       case 3:
-                               arguments[3] = _T; // Add root node to end of argument list
-                       default:
-                               arguments.length = 4;
-               }
-               return(_search_subtree.apply(this, arguments));
-       };
-
-       /**partially-recursive toJSON function
-        * [ string ] = RTree.toJSON([rectangle], [tree])
-        * @public
-        */
-       this.toJSON = function(rect, tree) {
-               var hit_stack = []; // Contains the elements that overlap
-               var count_stack = []; // Contains the elements that overlap
-               var return_stack = {}; // Contains the elements that overlap
-               var max_depth = 3;  // This triggers recursion and tree-splitting
-               var current_depth = 1;
-               var return_string = "";
-
-               if(rect && !RTree.Rectangle.overlap_rectangle(rect, _T))
-                return "";
-
-               if(!tree)       {
-                       count_stack.push(_T.nodes.length);
-                       hit_stack.push(_T.nodes);
-                       return_string += "var main_tree = {x:"+_T.x.toFixed()+",y:"+_T.y.toFixed()+",w:"+_T.w.toFixed()+",h:"+_T.h.toFixed()+",nodes:[";
-               }       else {
-                       max_depth += 4;
-                       count_stack.push(tree.nodes.length);
-                       hit_stack.push(tree.nodes);
-                       return_string += "var main_tree = {x:"+tree.x.toFixed()+",y:"+tree.y.toFixed()+",w:"+tree.w.toFixed()+",h:"+tree.h.toFixed()+",nodes:[";
-               }
+    load: function (data) {
+        if (!(data && data.length)) { return this; }
 
-               do {
-                       var nodes = hit_stack.pop();
-                       var i = count_stack.pop()-1;
-
-                       if(i >= 0 && i < nodes.length-1)
-                               return_string += ",";
-
-                       while(i >= 0)   {
-                               var ltree = nodes[i];
-                         if(!rect || RTree.Rectangle.overlap_rectangle(rect, ltree)) {
-                               if(ltree.nodes) { // Not a Leaf
-                                       if(current_depth >= max_depth) {
-                                               var len = return_stack.length;
-                                               var nam = _name_to_id("saved_subtree");
-                                               return_string += "{x:"+ltree.x.toFixed()+",y:"+ltree.y.toFixed()+",w:"+ltree.w.toFixed()+",h:"+ltree.h.toFixed()+",load:'"+nam+".js'}";
-                                               return_stack[nam] = this.toJSON(rect, ltree);
-                                                       if(i > 0)
-                                                               return_string += ","
-                                       }       else {
-                                               return_string += "{x:"+ltree.x.toFixed()+",y:"+ltree.y.toFixed()+",w:"+ltree.w.toFixed()+",h:"+ltree.h.toFixed()+",nodes:[";
-                                               current_depth += 1;
-                                               count_stack.push(i);
-                                               hit_stack.push(nodes);
-                                               nodes = ltree.nodes;
-                                               i = ltree.nodes.length;
-                                       }
-                               }       else if(ltree.leaf) { // A Leaf !!
-                                       var data = ltree.leaf.toJSON ? ltree.leaf.toJSON() : JSON.stringify(ltree.leaf);
-                                       return_string += "{x:"+ltree.x.toFixed()+",y:"+ltree.y.toFixed()+",w:"+ltree.w.toFixed()+",h:"+ltree.h.toFixed()+",leaf:" + data + "}";
-                                               if(i > 0)
-                                                       return_string += ","
-                               }       else if(ltree.load) { // A load
-                                       return_string += "{x:"+ltree.x.toFixed()+",y:"+ltree.y.toFixed()+",w:"+ltree.w.toFixed()+",h:"+ltree.h.toFixed()+",load:'" + ltree.load + "'}";
-                                               if(i > 0)
-                                                       return_string += ","
-                               }
-                               }
-                               i -= 1;
-                       }
-                       if(i < 0)       {
-                                       return_string += "]}"; current_depth -= 1;
-                       }
-               }while(hit_stack.length > 0);
+        if (data.length < this._minEntries) {
+            for (var i = 0, len = data.length; i < len; i++) {
+                this.insert(data[i]);
+            }
+            return this;
+        }
 
-               return_string+=";";
+        // recursively build the tree with the given data from stratch using OMT algorithm
+        var node = this._build(data.slice(), 0);
+        this._calcBBoxes(node, true);
 
-               for(var my_key in return_stack) {
-                       return_string += "\nvar " + my_key + " = function(){" + return_stack[my_key] + " return(main_tree);};";
-               }
-               return(return_string);
-       };
-
-       /**non-recursive function that deletes a specific
-        * [ number ] = RTree.remove(rectangle, obj)
-        */
-       this.remove = function(rect, obj) {
-               if(arguments.length < 1)
-                       throw "Wrong number of arguments. RT.remove requires at least a bounding rectangle."
-
-               switch(arguments.length) {
-                       case 1:
-                               arguments[1] = false; // obj == false for conditionals
-                       case 2:
-                               arguments[2] = _T; // Add root node to end of argument list
-                       default:
-                               arguments.length = 3;
-               }
-               if(arguments[1] === false) { // Do area-wide delete
-                       var numberdeleted = 0;
-                       var ret_array = [];
-                       do {
-                               numberdeleted=ret_array.length;
-                               ret_array = ret_array.concat(_remove_subtree.apply(this, arguments));
-                       }while( numberdeleted !=  ret_array.length);
-                       return ret_array;
-               }
-               else { // Delete a specific item
-                       return(_remove_subtree.apply(this, arguments));
-               }
-       };
+        if (!this.data.children.length) {
+            // save as is if tree is empty
+            this.data = node;
 
-       /**non-recursive insert function
-        * [] = RTree.insert(rectangle, object to insert)
-        */
-       this.insert = function(rect, obj) {
-/*             if(arguments.length < 2)
-                       throw "Wrong number of arguments. RT.Insert requires at least a bounding rectangle and an object."*/
+        } else if (this.data.height === node.height) {
+            // split root if trees have the same height
+            this._splitRoot(this.data, node);
 
-               return(_insert_subtree({x:rect.x,y:rect.y,w:rect.w,h:rect.h,leaf:obj}, _T));
-       };
+        } else {
+            if (this.data.height < node.height) {
+                // swap trees if inserted one is bigger
+                var tmpNode = this.data;
+                this.data = node;
+                node = tmpNode;
+            }
 
-       /**non-recursive delete function
-        * [deleted object] = RTree.remove(rectangle, [object to delete])
-        */
+            // insert the small tree into the large tree at appropriate level
+            this._insert(node, this.data.height - node.height - 1, true);
+        }
 
-//End of RTree
-};
+        return this;
+    },
 
-/**Rectangle - Generic rectangle object - Not yet used */
+    insert: function (item) {
+        if (item) {
+            this._insert(item, this.data.height - 1);
+        }
+        return this;
+    },
 
-RTree.Rectangle = function(ix, iy, iw, ih) { // new Rectangle(bounds) or new Rectangle(x, y, w, h)
-    var x, x2, y, y2, w, h;
+    clear: function () {
+        this.data = {
+            children: [],
+            leaf: true,
+            bbox: this._infinite(),
+            height: 1
+        };
+        return this;
+    },
 
-    if(ix.x) {
-               x = ix.x; y = ix.y;
-                       if(ix.w !== 0 && !ix.w && ix.x2){
-                               w = ix.x2-ix.x; h = ix.y2-ix.y;
-                       }       else {
-                               w = ix.w;       h = ix.h;
-                       }
-               x2 = x + w; y2 = y + h; // For extra fastitude
-       } else {
-               x = ix; y = iy; w = iw; h = ih;
-               x2 = x + w; y2 = y + h; // For extra fastitude
-       }
+    remove: function (item) {
+        if (!item) { return this; }
 
-       this.x1 = this.x = x;
-       this.y1 = this.y = y;
-       this.x2 = x2;
-       this.y2 = y2;
-       this.w = w;
-       this.h = h;
-
-       this.toJSON = function() {
-               return('{"x":'+x.toString()+', "y":'+y.toString()+', "w":'+w.toString()+', "h":'+h.toString()+'}');
-       };
-
-       this.overlap = function(a) {
-               return(this.x() < a.x2() && this.x2() > a.x() && this.y() < a.y2() && this.y2() > a.y());
-       };
-
-       this.expand = function(a) {
-               var nx = Math.min(this.x(), a.x());
-               var ny = Math.min(this.y(), a.y());
-               w = Math.max(this.x2(), a.x2()) - nx;
-               h = Math.max(this.y2(), a.y2()) - ny;
-               x = nx; y = ny;
-               return(this);
-       };
-
-       this.setRect = function(ix, iy, iw, ih) {
-        var x, x2, y, y2, w, h;
-               if(ix.x) {
-                       x = ix.x; y = ix.y;
-                       if(ix.w !== 0 && !ix.w && ix.x2) {
-                               w = ix.x2-ix.x; h = ix.y2-ix.y;
-                       }       else {
-                               w = ix.w;       h = ix.h;
-                       }
-                       x2 = x + w; y2 = y + h; // For extra fastitude
-               } else {
-                       x = ix; y = iy; w = iw; h = ih;
-                       x2 = x + w; y2 = y + h; // For extra fastitude
-               }
-       };
-//End of RTree.Rectangle
-};
+        var node = this.data,
+            bbox = this._toBBox(item),
+            path = [],
+            indexes = [],
+            i, parent, index, goingUp;
 
+        // depth-first iterative tree traversal
+        while (node || path.length) {
 
-/**returns true if rectangle 1 overlaps rectangle 2
- * [ boolean ] = overlap_rectangle(rectangle a, rectangle b)
- * @static function
- */
-RTree.Rectangle.overlap_rectangle = function(a, b) {
-       return(a.x < (b.x+b.w) && (a.x+a.w) > b.x && a.y < (b.y+b.h) && (a.y+a.h) > b.y);
-};
+            if (!node) { // go up
+                node = path.pop();
+                parent = path[path.length - 1];
+                i = indexes.pop();
+                goingUp = true;
+            }
 
-/**returns true if rectangle a is contained in rectangle b
- * [ boolean ] = contains_rectangle(rectangle a, rectangle b)
- * @static function
- */
-RTree.Rectangle.contains_rectangle = function(a, b) {
-       return((a.x+a.w) <= (b.x+b.w) && a.x >= b.x && (a.y+a.h) <= (b.y+b.h) && a.y >= b.y);
-};
+            if (node.leaf) { // check current node
+                index = node.children.indexOf(item);
 
-/**expands rectangle A to include rectangle B, rectangle B is untouched
- * [ rectangle a ] = expand_rectangle(rectangle a, rectangle b)
- * @static function
- */
-RTree.Rectangle.expand_rectangle = function(a, b)      {
-       var nx = Math.min(a.x, b.x);
-       var ny = Math.min(a.y, b.y);
-       a.w = Math.max(a.x+a.w, b.x+b.w) - nx;
-       a.h = Math.max(a.y+a.h, b.y+b.h) - ny;
-       a.x = nx; a.y = ny;
-       return(a);
-};
-
-/**generates a minimally bounding rectangle for all rectangles in
- * array "nodes". If rect is set, it is modified into the MBR. Otherwise,
- * a new rectangle is generated and returned.
- * [ rectangle a ] = make_MBR(rectangle array nodes, rectangle rect)
- * @static function
- */
-RTree.Rectangle.make_MBR = function(nodes, rect) {
-       if(nodes.length < 1)
-               return({x:0, y:0, w:0, h:0});
-               //throw "make_MBR: nodes must contain at least one rectangle!";
-       if(!rect)
-               rect = {x:nodes[0].x, y:nodes[0].y, w:nodes[0].w, h:nodes[0].h};
-       else
-               rect.x = nodes[0].x; rect.y = nodes[0].y; rect.w = nodes[0].w; rect.h = nodes[0].h;
+                if (index !== -1) {
+                    // item found, remove the item and condense tree upwards
+                    node.children.splice(index, 1);
+                    path.push(node);
+                    this._condense(path);
+                    return this;
+                }
+            }
+
+            if (!goingUp && !node.leaf && this._intersects(bbox, node.bbox)) { // go down
+                path.push(node);
+                indexes.push(i);
+                i = 0;
+                parent = node;
+                node = node.children[0];
+
+            } else if (parent) { // go right
+                i++;
+                node = parent.children[i];
+                goingUp = false;
+
+            } else { // nothing found
+                node = null;
+            }
+        }
+
+        return this;
+    },
+
+    toJSON: function () { return this.data; },
+
+    fromJSON: function (data) {
+        this.data = data;
+        return this;
+    },
+
+    _build: function (items, level, height) {
+
+        var N = items.length,
+            M = this._maxEntries;
+
+        if (N <= M) {
+            return {
+                children: items,
+                leaf: true,
+                height: 1
+            };
+        }
+
+        if (!level) {
+            // target height of the bulk-loaded tree
+            height = Math.ceil(Math.log(N) / Math.log(M));
+
+            // target number of root entries to maximize storage utilization
+            M = Math.ceil(N / Math.pow(M, height - 1));
+
+            items.sort(this._compareMinX);
+        }
+
+        // TODO eliminate recursion?
+
+        var node = {
+            children: [],
+            height: height
+        };
+
+        var N1 = Math.ceil(N / M) * Math.ceil(Math.sqrt(M)),
+            N2 = Math.ceil(N / M),
+            compare = level % 2 === 1 ? this._compareMinX : this._compareMinY,
+            i, j, slice, sliceLen, childNode;
+
+        // split the items into M mostly square tiles
+        for (i = 0; i < N; i += N1) {
+            slice = items.slice(i, i + N1).sort(compare);
+
+            for (j = 0, sliceLen = slice.length; j < sliceLen; j += N2) {
+                // pack each entry recursively
+                childNode = this._build(slice.slice(j, j + N2), level + 1, height - 1);
+                node.children.push(childNode);
+            }
+        }
+
+        return node;
+    },
+
+    _chooseSubtree: function (bbox, node, level, path) {
+
+        var i, len, child, targetNode, area, enlargement, minArea, minEnlargement;
+
+        while (true) {
+            path.push(node);
+
+            if (node.leaf || path.length - 1 === level) { break; }
+
+            minArea = minEnlargement = Infinity;
+
+            for (i = 0, len = node.children.length; i < len; i++) {
+                child = node.children[i];
+                area = this._area(child.bbox);
+                enlargement = this._enlargedArea(bbox, child.bbox) - area;
+
+                // choose entry with the least area enlargement
+                if (enlargement < minEnlargement) {
+                    minEnlargement = enlargement;
+                    minArea = area < minArea ? area : minArea;
+                    targetNode = child;
+
+                } else if (enlargement === minEnlargement) {
+                    // otherwise choose one with the smallest area
+                    if (area < minArea) {
+                        minArea = area;
+                        targetNode = child;
+                    }
+                }
+            }
+
+            node = targetNode;
+        }
+
+        return node;
+    },
+
+    _insert: function (item, level, isNode, root) {
+
+        var bbox = isNode ? item.bbox : this._toBBox(item),
+            insertPath = [];
+
+        // find the best node for accommodating the item, saving all nodes along the path too
+        var node = this._chooseSubtree(bbox, root || this.data, level, insertPath),
+            splitOccured;
+
+        // put the item into the node
+        node.children.push(item);
+        this._extend(node.bbox, bbox);
+
+        // split on node overflow; propagate upwards if necessary
+        do {
+            splitOccured = false;
+            if (insertPath[level].children.length > this._maxEntries) {
+                this._split(insertPath, level);
+                splitOccured = true;
+                level--;
+            }
+        } while (level >= 0 && splitOccured);
+
+        // adjust bboxes along the insertion path
+        this._adjustParentBBoxes(bbox, insertPath, level);
+    },
+
+    // split overflowed node into two
+    _split: function (insertPath, level) {
+
+        var node = insertPath[level],
+            M = node.children.length,
+            m = this._minEntries;
+
+        this._chooseSplitAxis(node, m, M);
+
+        var newNode = {
+            children: node.children.splice(this._chooseSplitIndex(node, m, M)),
+            height: node.height
+        };
 
-       for(var i = nodes.length-1; i>0; i--)
-               RTree.Rectangle.expand_rectangle(rect, nodes[i]);
+        if (node.leaf) {
+            newNode.leaf = true;
+        }
+
+        this._calcBBoxes(node);
+        this._calcBBoxes(newNode);
+
+        if (level) {
+            insertPath[level - 1].children.push(newNode);
+        } else {
+            this._splitRoot(node, newNode);
+        }
+    },
+
+    _splitRoot: function (node, newNode) {
+        // split root node
+        this.data = {};
+        this.data.children = [node, newNode];
+        this.data.height = node.height + 1;
+        this._calcBBoxes(this.data);
+    },
+
+    _chooseSplitIndex: function (node, m, M) {
+
+        var i, bbox1, bbox2, overlap, area, minOverlap, minArea, index;
+
+        minOverlap = minArea = Infinity;
 
-       return(rect);
+        for (i = m; i <= M - m; i++) {
+            bbox1 = this._distBBox(node, 0, i);
+            bbox2 = this._distBBox(node, i, M);
+
+            overlap = this._intersectionArea(bbox1, bbox2);
+            area = this._area(bbox1) + this._area(bbox2);
+
+            // choose distribution with minimum overlap
+            if (overlap < minOverlap) {
+                minOverlap = overlap;
+                index = i;
+
+                minArea = area < minArea ? area : minArea;
+
+            } else if (overlap === minOverlap) {
+                // otherwise choose distribution with minimum area
+                if (area < minArea) {
+                    minArea = area;
+                    index = i;
+                }
+            }
+        }
+
+        return index;
+    },
+
+    // sorts node children by the best axis for split
+    _chooseSplitAxis: function (node, m, M) {
+
+        var compareMinX = node.leaf ? this._compareMinX : this._compareNodeMinX,
+            compareMinY = node.leaf ? this._compareMinY : this._compareNodeMinY,
+            xMargin = this._allDistMargin(node, m, M, compareMinX),
+            yMargin = this._allDistMargin(node, m, M, compareMinY);
+
+        // if total distributions margin value is minimal for x, sort by minX,
+        // otherwise it's already sorted by minY
+
+        if (xMargin < yMargin) {
+            node.children.sort(compareMinX);
+        }
+    },
+
+    // total margin of all possible split distributions where each node is at least m full
+    _allDistMargin: function (node, m, M, compare) {
+
+        node.children.sort(compare);
+
+        var leftBBox = this._distBBox(node, 0, m),
+            rightBBox = this._distBBox(node, M - m, M),
+            margin = this._margin(leftBBox) + this._margin(rightBBox),
+            i, child;
+
+        for (i = m; i < M - m; i++) {
+            child = node.children[i];
+            this._extend(leftBBox, node.leaf ? this._toBBox(child) : child.bbox);
+            margin += this._margin(leftBBox);
+        }
+
+        for (i = M - m - 1; i >= 0; i--) {
+            child = node.children[i];
+            this._extend(rightBBox, node.leaf ? this._toBBox(child) : child.bbox);
+            margin += this._margin(rightBBox);
+        }
+
+        return margin;
+    },
+
+    // min bounding rectangle of node children from k to p-1
+    _distBBox: function (node, k, p) {
+        var bbox = this._infinite();
+
+        for (var i = k, child; i < p; i++) {
+            child = node.children[i];
+            this._extend(bbox, node.leaf ? this._toBBox(child) : child.bbox);
+        }
+
+        return bbox;
+    },
+
+    _calcBBoxes: function (node, recursive) {
+        // TODO eliminate recursion
+        node.bbox = this._infinite();
+
+        for (var i = 0, len = node.children.length, child; i < len; i++) {
+            child = node.children[i];
+
+            if (node.leaf) {
+                this._extend(node.bbox, this._toBBox(child));
+            } else {
+                if (recursive) {
+                    this._calcBBoxes(child, recursive);
+                }
+                this._extend(node.bbox, child.bbox);
+            }
+        }
+    },
+
+    _adjustParentBBoxes: function (bbox, path, level) {
+        // adjust bboxes along the given tree path
+        for (var i = level; i >= 0; i--) {
+            this._extend(path[i].bbox, bbox);
+        }
+    },
+
+    _condense: function (path) {
+        // go through the path, removing empty nodes and updating bboxes
+        for (var i = path.length - 1, parent; i >= 0; i--) {
+            if (i > 0 && path[i].children.length === 0) {
+                parent = path[i - 1].children;
+                parent.splice(parent.indexOf(path[i]), 1);
+            } else {
+                this._calcBBoxes(path[i]);
+            }
+        }
+    },
+
+    _intersects: function (a, b) {
+        return b[0] <= a[2] &&
+               b[1] <= a[3] &&
+               b[2] >= a[0] &&
+               b[3] >= a[1];
+    },
+
+    _extend: function (a, b) {
+        a[0] = Math.min(a[0], b[0]);
+        a[1] = Math.min(a[1], b[1]);
+        a[2] = Math.max(a[2], b[2]);
+        a[3] = Math.max(a[3], b[3]);
+        return a;
+    },
+
+    _area:   function (a) { return (a[2] - a[0]) * (a[3] - a[1]); },
+    _margin: function (a) { return (a[2] - a[0]) + (a[3] - a[1]); },
+
+    _enlargedArea: function (a, b) {
+        return (Math.max(b[2], a[2]) - Math.min(b[0], a[0])) *
+               (Math.max(b[3], a[3]) - Math.min(b[1], a[1]));
+    },
+
+    _intersectionArea: function (a, b) {
+        var minX = Math.max(a[0], b[0]),
+            minY = Math.max(a[1], b[1]),
+            maxX = Math.min(a[2], b[2]),
+            maxY = Math.min(a[3], b[3]);
+
+        return Math.max(0, maxX - minX) *
+               Math.max(0, maxY - minY);
+    },
+
+    _infinite: function () { return [Infinity, Infinity, -Infinity, -Infinity]; },
+
+    _compareNodeMinX: function (a, b) { return a.bbox[0] - b.bbox[0]; },
+    _compareNodeMinY: function (a, b) { return a.bbox[1] - b.bbox[1]; },
+
+    _initFormat: function (format) {
+        // data format (minX, minY, maxX, maxY accessors)
+        format = format || ['[0]', '[1]', '[2]', '[3]'];
+
+        // uses eval-type function compilation instead of just accepting a toBBox function
+        // because the algorithms are very sensitive to sorting functions performance,
+        // so they should be dead simple and without inner calls
+
+        // jshint evil: true
+
+        var compareArr = ['return a', ' - b', ';'];
+
+        this._compareMinX = new Function('a', 'b', compareArr.join(format[0]));
+        this._compareMinY = new Function('a', 'b', compareArr.join(format[1]));
+
+        this._toBBox = new Function('a', 'return [a' + format.join(', a') + '];');
+    }
 };
+
+if (typeof module !== 'undefined') {
+    module.exports = rbush;
+} else {
+    window.rbush = rbush;
+}
+
+})();
 toGeoJSON = (function() {
     var removeSpace = (/\s*/g), trimSpace = (/^\s*|\s*$/g), splitSpace = (/\s+/);
     function okhash(x) {
@@ -14894,11 +15033,10 @@ window.iD = function () {
     };
 
     var history = iD.History(context),
-        dispatch = d3.dispatch('enter', 'exit', 'select', 'toggleFullscreen'),
+        dispatch = d3.dispatch('enter', 'exit', 'toggleFullscreen'),
         mode,
         container,
         ui = iD.ui(context),
-        map = iD.Map(context),
         connection = iD.Connection(),
         locale = iD.detect().locale,
         localePath;
@@ -14908,7 +15046,7 @@ window.iD = function () {
     }
 
     connection.on('load.context', function loadContext(err, result) {
-        history.merge(result);
+        history.merge(result.data, result.extent);
     });
 
     context.preauth = function(options) {
@@ -14922,29 +15060,23 @@ window.iD = function () {
         return context;
     };
 
-    context.ui = function() {
-        return function(container) {
-            context.container(container);
-
-            if (locale && locale !== 'en' && iD.data.locales.indexOf(locale) !== -1) {
-                localePath = localePath || context.assetPath() + 'locales/' + locale + '.json';
-                d3.json(localePath, function(err, result) {
-                    window.locale[locale] = result;
-                    window.locale.current(locale);
-                    container.call(ui);
-                });
-            } else {
-                container.call(ui);
-            }
-
-            return ui;
+    context.loadLocale = function(cb) {
+        if (locale && locale !== 'en' && iD.data.locales.indexOf(locale) !== -1) {
+            localePath = localePath || context.assetPath() + 'locales/' + locale + '.json';
+            d3.json(localePath, function(err, result) {
+                window.locale[locale] = result;
+                window.locale.current(locale);
+                cb();
+            });
+        } else {
+            cb();
         }
     };
 
     /* Straight accessors. Avoid using these if you can. */
+    context.ui = function() { return ui; };
     context.connection = function() { return connection; };
     context.history = function() { return history; };
-    context.map = function() { return map; };
 
     /* History */
     context.graph = history.graph;
@@ -14956,6 +15088,12 @@ window.iD = function () {
     context.changes = history.changes;
     context.intersects = history.intersects;
 
+    context.flush = function() {
+        connection.flush();
+        history.reset();
+        return context;
+    };
+
     /* Graph */
     context.hasEntity = function(id) {
         return history.graph().hasEntity(id);
@@ -14975,8 +15113,6 @@ window.iD = function () {
 
     /* Modes */
     context.enter = function(newMode) {
-        var s0 = context.selection();
-
         if (mode) {
             mode.exit();
             dispatch.exit(mode);
@@ -14985,23 +15121,48 @@ window.iD = function () {
         mode = newMode;
         mode.enter();
         dispatch.enter(mode);
-
-        var s1 = context.selection();
-        dispatch.select(s1, s0);
     };
 
     context.mode = function() {
         return mode;
     };
 
-    context.selection = function() {
-        if (mode && mode.selection) {
-            return mode.selection();
+    context.selectedIDs = function() {
+        if (mode && mode.selectedIDs) {
+            return mode.selectedIDs();
         } else {
             return [];
         }
     };
 
+    context.loadEntity = function(id, zoomTo) {
+        if (zoomTo !== false) {
+            connection.loadEntity(id, function(error, entity) {
+                if (entity) {
+                    map.zoomTo(entity);
+                }
+            });
+        }
+
+        map.on('drawn.loadEntity', function() {
+            if (!context.hasEntity(id)) return;
+            map.on('drawn.loadEntity', null);
+            context.on('enter.loadEntity', null);
+            context.enter(iD.modes.Select(context, [id]));
+        });
+
+        context.on('enter.loadEntity', function() {
+            if (mode.id !== 'browse') {
+                map.on('drawn.loadEntity', null);
+                context.on('enter.loadEntity', null);
+            }
+        });
+    };
+
+    context.editable = function() {
+        return map.editable() && mode && mode.id !== 'save';
+    };
+
     /* Behaviors */
     context.install = function(behavior) {
         context.surface().call(behavior);
@@ -15011,33 +15172,28 @@ window.iD = function () {
         context.surface().call(behavior.off);
     };
 
+    /* Projection */
+    context.projection = d3.geo.mercator()
+        .scale(512 / Math.PI)
+        .precision(0);
+
+    /* Background */
+    var background = iD.Background(context);
+    context.background = function() { return background; };
+
     /* Map */
+    var map = iD.Map(context);
+    context.map = function() { return map; };
     context.layers = function() { return map.layers; };
-    context.background = function() { return map.layers[0]; };
     context.surface = function() { return map.surface; };
-    context.projection = map.projection;
-    context.tail = map.tail;
-    context.redraw = map.redraw;
+    context.mouse = map.mouse;
+    context.extent = map.extent;
     context.pan = map.pan;
     context.zoomIn = map.zoomIn;
     context.zoomOut = map.zoomOut;
 
-    /* Background */
-    var backgroundSources = iD.data.imagery.map(function(source) {
-        if (source.sourcetag === 'Bing') {
-            return iD.BackgroundSource.Bing(source, context.background().dispatch);
-        } else {
-            return iD.BackgroundSource.template(source);
-        }
-    });
-    backgroundSources.push(iD.BackgroundSource.Custom);
-
-    context.backgroundSources = function() {
-        return backgroundSources;
-    };
-
     /* Presets */
-    var presets = iD.presets(context)
+    var presets = iD.presets()
         .load(iD.data.presets);
 
     context.presets = function() {
@@ -15051,31 +15207,6 @@ window.iD = function () {
         return context;
     };
 
-    var q = iD.util.stringQs(location.hash.substring(1)), detected = false;
-    if (q.layer && q.layer.indexOf('custom:') === 0) {
-        context.layers()[0]
-           .source(iD.BackgroundSource.template({
-                template: q.layer.replace(/^custom:/, ''),
-                name: 'Custom'
-            }));
-        detected = true;
-    } else if (q.layer) {
-        context.layers()[0]
-           .source(_.find(backgroundSources, function(l) {
-               if (l.data.sourcetag === q.layer) {
-                   detected = true;
-                   return true;
-               }
-           }));
-    }
-
-    if (!detected) {
-        context.background()
-            .source(_.find(backgroundSources, function(l) {
-                return l.data.name === 'Bing aerial imagery';
-            }));
-    }
-
     var embed = false;
     context.embed = function(_) {
         if (!arguments.length) return embed;
@@ -15101,40 +15232,40 @@ window.iD = function () {
     return d3.rebind(context, dispatch, 'on');
 };
 
-iD.version = '1.0.1';
+iD.version = '1.1.0';
 
-iD.detect = function() {
-    var browser = {};
+(function() {
+    var detected = {};
 
     var ua = navigator.userAgent,
         msie = new RegExp("MSIE ([0-9]{1,}[\\.0-9]{0,})");
 
     if (msie.exec(ua) !== null) {
         var rv = parseFloat(RegExp.$1);
-        browser.support = !(rv && rv < 9);
+        detected.support = !(rv && rv < 9);
     } else {
-        browser.support = true;
+        detected.support = true;
     }
 
     // Added due to incomplete svg style support. See #715
-    browser.opera = ua.indexOf('Opera') >= 0;
+    detected.opera = ua.indexOf('Opera') >= 0;
 
-    browser.locale = navigator.language || navigator.userLanguage;
+    detected.locale = navigator.language || navigator.userLanguage;
 
-    browser.filedrop = (window.FileReader && 'ondrop' in window);
+    detected.filedrop = (window.FileReader && 'ondrop' in window);
 
     function nav(x) {
         return navigator.userAgent.indexOf(x) !== -1;
     }
 
-    if (nav('Win')) browser.os = 'win';
-    else if (nav('Mac')) browser.os = 'mac';
-    else if (nav('X11')) browser.os = 'linux';
-    else if (nav('Linux')) browser.os = 'linux';
-    else browser.os = 'win';
+    if (nav('Win')) detected.os = 'win';
+    else if (nav('Mac')) detected.os = 'mac';
+    else if (nav('X11')) detected.os = 'linux';
+    else if (nav('Linux')) detected.os = 'linux';
+    else detected.os = 'win';
 
-    return browser;
-};
+    iD.detect = function() { return detected; };
+})();
 iD.taginfo = function() {
     var taginfo = {},
         endpoint = 'http://taginfo.openstreetmap.org/api/4/',
@@ -15151,7 +15282,11 @@ iD.taginfo = function() {
             line: 'ways'
         };
 
-    var cache = this.cache = {};
+    if (!iD.taginfo.cache) {
+        iD.taginfo.cache = {};
+    }
+
+    var cache = iD.taginfo.cache;
 
     function sets(parameters, n, o) {
         if (parameters.geometry && o[parameters.geometry]) {
@@ -15331,6 +15466,30 @@ iD.util.tagText = function(entity) {
     }).join(', ');
 };
 
+iD.util.entitySelector = function(ids) {
+    return ids.length ? '.' + ids.join(',.') : 'nothing';
+};
+
+iD.util.entityOrMemberSelector = function(ids, graph) {
+    var s = iD.util.entitySelector(ids);
+
+    ids.forEach(function(id) {
+        var entity = graph.hasEntity(id);
+        if (entity && entity.type === 'relation') {
+            entity.members.forEach(function(member) {
+                s += ',.' + member.id
+            });
+        }
+    });
+
+    return s;
+};
+
+iD.util.displayName = function(entity) {
+    var localeName = 'name:' + iD.detect().locale.toLowerCase().split('-')[0];
+    return entity.tags[localeName] || entity.tags.name || entity.tags.ref;
+};
+
 iD.util.stringQs = function(str) {
     return str.split('&').reduce(function(obj, pair){
         var parts = pair.split('=');
@@ -15378,7 +15537,7 @@ iD.util.prefixCSSProperty = function(property) {
 
     while (++i < n)
         if (prefixes[i] + property in s)
-            return '-' + prefixes[i].toLowerCase() + '-' + property.toLowerCase();
+            return '-' + prefixes[i].toLowerCase() + property.replace(/([A-Z])/g, '-$1').toLowerCase();
 
     return false;
 };
@@ -15595,12 +15754,24 @@ _.extend(iD.geo.Extent.prototype, {
                obj[1][1] >= this[0][1];
     },
 
+    intersection: function(obj) {
+        if (!this.intersects(obj)) return new iD.geo.Extent();
+        return new iD.geo.Extent([Math.max(obj[0][0], this[0][0]),
+                                  Math.max(obj[0][1], this[0][1])],
+                                 [Math.min(obj[1][0], this[1][0]),
+                                  Math.min(obj[1][1], this[1][1])]);
+    },
+
     padByMeters: function(meters) {
         var dLat = meters / 111200,
             dLon = meters / 111200 / Math.abs(Math.cos(this.center()[1]));
         return iD.geo.Extent(
                 [this[0][0] - dLon, this[0][1] - dLat],
                 [this[1][0] + dLon, this[1][1] + dLat]);
+    },
+
+    toParam: function() {
+        return [this[0][0], this[0][1], this[1][0], this[1][1]].join(',');
     }
 });
 // For fixing up rendering of multipolygons with tags on the outer member.
@@ -15653,12 +15824,126 @@ iD.geo.simpleMultipolygonOuterMember = function(entity, graph) {
 
     return outerMember && graph.hasEntity(outerMember.id);
 };
+
+// Join `array` into sequences of connecting ways.
+//
+// Segments which share identical start/end nodes will, as much as possible,
+// be connected with each other.
+//
+// The return value is a nested array. Each constituent array contains elements
+// of `array` which have been determined to connect. Each consitituent array
+// also has a `nodes` property whose value is an ordered array of member nodes,
+// with appropriate order reversal and start/end coordinate de-duplication.
+//
+// Members of `array` must have, at minimum, `type` and `id` properties.
+// Thus either an array of `iD.Way`s or a relation member array may be
+// used.
+//
+// If an member has a `tags` property, its tags will be reversed via
+// `iD.actions.Reverse` in the output.
+//
+// Incomplete members (those for which `graph.hasEntity(element.id)` returns
+// false) and non-way members are ignored.
+//
+iD.geo.joinWays = function(array, graph) {
+    var joined = [], member, current, nodes, first, last, i, how, what;
+
+    array = array.filter(function(member) {
+        return member.type === 'way' && graph.hasEntity(member.id);
+    });
+
+    function resolve(member) {
+        return graph.childNodes(graph.entity(member.id));
+    }
+
+    function reverse(member) {
+        return member.tags ? iD.actions.Reverse(member.id)(graph).entity(member.id) : member;
+    }
+
+    while (array.length) {
+        member = array.shift();
+        current = [member];
+        current.nodes = nodes = resolve(member).slice();
+        joined.push(current);
+
+        while (array.length && _.first(nodes) !== _.last(nodes)) {
+            first = _.first(nodes);
+            last  = _.last(nodes);
+
+            for (i = 0; i < array.length; i++) {
+                member = array[i];
+                what = resolve(member);
+
+                if (last === _.first(what)) {
+                    how  = nodes.push;
+                    what = what.slice(1);
+                    break;
+                } else if (last === _.last(what)) {
+                    how  = nodes.push;
+                    what = what.slice(0, -1).reverse();
+                    member = reverse(member);
+                    break;
+                } else if (first === _.last(what)) {
+                    how  = nodes.unshift;
+                    what = what.slice(0, -1);
+                    break;
+                } else if (first === _.first(what)) {
+                    how  = nodes.unshift;
+                    what = what.slice(1).reverse();
+                    member = reverse(member);
+                    break;
+                } else {
+                    what = how = null;
+                }
+            }
+
+            if (!what)
+                break; // No more joinable ways.
+
+            how.apply(current, [member]);
+            how.apply(nodes, what);
+
+            array.splice(i, 1);
+        }
+    }
+
+    return joined;
+};
 iD.actions = {};
 iD.actions.AddEntity = function(way) {
     return function(graph) {
         return graph.replace(way);
     };
 };
+iD.actions.AddMember = function(relationId, member, memberIndex) {
+    return function(graph) {
+        var relation = graph.entity(relationId);
+
+        if (isNaN(memberIndex) && member.type === 'way') {
+            var members = relation.indexedMembers();
+            members.push(member);
+
+            var joined = iD.geo.joinWays(members, graph);
+            for (var i = 0; i < joined.length; i++) {
+                var segment = joined[i];
+                for (var j = 0; j < segment.length && segment.length >= 2; j++) {
+                    if (segment[j] !== member)
+                        continue;
+
+                    if (j === 0) {
+                        memberIndex = segment[j + 1].index;
+                    } else if (j === segment.length - 1) {
+                        memberIndex = segment[j - 1].index + 1;
+                    } else {
+                        memberIndex = Math.min(segment[j - 1].index + 1, segment[j + 1].index + 1);
+                    }
+                }
+            }
+        }
+
+        return graph.replace(relation.addMember(member, memberIndex));
+    }
+};
 iD.actions.AddMidpoint = function(midpoint, node) {
     return function(graph) {
         graph = graph.replace(node.move(midpoint.loc));
@@ -15687,6 +15972,23 @@ iD.actions.AddVertex = function(wayId, nodeId, index) {
         return graph.replace(graph.entity(wayId).addNode(nodeId, index));
     };
 };
+iD.actions.ChangeMember = function(relationId, member, memberIndex) {
+    return function(graph) {
+        return graph.replace(graph.entity(relationId).updateMember(member, memberIndex));
+    }
+};
+iD.actions.ChangePreset = function(entityId, oldPreset, newPreset) {
+    return function(graph) {
+        var entity = graph.entity(entityId),
+            geometry = entity.geometry(graph),
+            tags = entity.tags;
+
+        if (oldPreset) tags = oldPreset.removeTags(tags, geometry);
+        if (newPreset) tags = newPreset.applyTags(tags, geometry);
+
+        return graph.replace(entity.update({tags: tags}));
+    };
+};
 iD.actions.ChangeTags = function(entityId, tags) {
     return function(graph) {
         var entity = graph.entity(entityId);
@@ -15800,14 +16102,19 @@ iD.actions.Connect = function(nodeIds) {
         return graph;
     };
 };
-iD.actions.DeleteMultiple = function(ids) {
+iD.actions.DeleteMember = function(relationId, memberIndex) {
     return function(graph) {
-        var actions = {
-            way: iD.actions.DeleteWay,
-            node: iD.actions.DeleteNode,
-            relation: iD.actions.DeleteRelation
-        };
+        return graph.replace(graph.entity(relationId).removeMember(memberIndex));
+    };
+};
+iD.actions.DeleteMultiple = function(ids) {
+    var actions = {
+        way: iD.actions.DeleteWay,
+        node: iD.actions.DeleteNode,
+        relation: iD.actions.DeleteRelation
+    };
 
+    var action = function(graph) {
         ids.forEach(function(id) {
             if (graph.hasEntity(id)) { // It may have been deleted aready.
                 graph = actions[graph.entity(id).type](id)(graph);
@@ -15816,10 +16123,20 @@ iD.actions.DeleteMultiple = function(ids) {
 
         return graph;
     };
+
+    action.disabled = function(graph) {
+        for (var i = 0; i < ids.length; i++) {
+            var id = ids[i],
+                disabled = actions[graph.entity(id).type](id).disabled(graph);
+            if (disabled) return disabled;
+        }
+    };
+
+    return action;
 };
 // https://github.com/openstreetmap/potlatch2/blob/master/net/systemeD/halcyon/connection/actions/DeleteNodeAction.as
 iD.actions.DeleteNode = function(nodeId) {
-    return function(graph) {
+    var action = function(graph) {
         var node = graph.entity(nodeId);
 
         graph.parentWays(node)
@@ -15834,11 +16151,17 @@ iD.actions.DeleteNode = function(nodeId) {
 
         graph.parentRelations(node)
             .forEach(function(parent) {
-                graph = graph.replace(parent.removeMember(nodeId));
+                graph = graph.replace(parent.removeMembersWithID(nodeId));
             });
 
         return graph.remove(node);
     };
+
+    action.disabled = function() {
+        return false;
+    };
+
+    return action;
 };
 // https://github.com/openstreetmap/potlatch2/blob/master/net/systemeD/halcyon/connection/actions/DeleteRelationAction.as
 iD.actions.DeleteRelation = function(relationId) {
@@ -15848,16 +16171,16 @@ iD.actions.DeleteRelation = function(relationId) {
             !entity.hasInterestingTags();
     }
 
-    return function(graph) {
+    var action = function(graph) {
         var relation = graph.entity(relationId);
 
         graph.parentRelations(relation)
             .forEach(function(parent) {
-                graph = graph.replace(parent.removeMember(relationId));
+                graph = graph.replace(parent.removeMembersWithID(relationId));
             });
 
         _.uniq(_.pluck(relation.members, 'id')).forEach(function(memberId) {
-            graph = graph.replace(relation.removeMember(memberId));
+            graph = graph.replace(relation.removeMembersWithID(memberId));
 
             var entity = graph.entity(memberId);
             if (deleteEntity(entity, graph)) {
@@ -15867,6 +16190,13 @@ iD.actions.DeleteRelation = function(relationId) {
 
         return graph.remove(relation);
     };
+
+    action.disabled = function(graph) {
+        if (!graph.entity(relationId).isComplete(graph))
+            return 'incomplete_relation';
+    };
+
+    return action;
 };
 // https://github.com/openstreetmap/potlatch2/blob/master/net/systemeD/halcyon/connection/actions/DeleteWayAction.as
 iD.actions.DeleteWay = function(wayId) {
@@ -15876,12 +16206,12 @@ iD.actions.DeleteWay = function(wayId) {
             !node.hasInterestingTags();
     }
 
-    return function(graph) {
+    var action = function(graph) {
         var way = graph.entity(wayId);
 
         graph.parentRelations(way)
             .forEach(function(parent) {
-                graph = graph.replace(parent.removeMember(wayId));
+                graph = graph.replace(parent.removeMembersWithID(wayId));
             });
 
         _.uniq(way.nodes).forEach(function(nodeId) {
@@ -15895,6 +16225,12 @@ iD.actions.DeleteWay = function(wayId) {
 
         return graph.remove(way);
     };
+
+    action.disabled = function() {
+        return false;
+    };
+
+    return action;
 };
 iD.actions.DeprecateTags = function(entityId) {
     return function(graph) {
@@ -15932,6 +16268,22 @@ iD.actions.DeprecateTags = function(entityId) {
         }
     };
 };
+iD.actions.DiscardTags = function(difference) {
+    return function(graph) {
+        function discardTags(entity) {
+            if (!_.isEmpty(entity.tags)) {
+                graph = graph.replace(entity.update({
+                    tags: _.omit(entity.tags, iD.data.discarded)
+                }));
+            }
+        }
+
+        difference.modified().forEach(discardTags);
+        difference.created().forEach(discardTags);
+
+        return graph;
+    }
+};
 // Disconect the ways at the given node.
 //
 // Optionally, disconnect only the given ways.
@@ -16006,8 +16358,6 @@ iD.actions.Disconnect = function(nodeId, newNodeId) {
 //   https://github.com/openstreetmap/josm/blob/mirror/src/org/openstreetmap/josm/actions/CombineWayAction.java
 //
 iD.actions.Join = function(ids) {
-    var idA = ids[0],
-        idB = ids[1];
 
     function groupEntitiesByGeometry(graph) {
         var entities = ids.map(function(id) { return graph.entity(id); });
@@ -16015,70 +16365,61 @@ iD.actions.Join = function(ids) {
     }
 
     var action = function(graph) {
-        var a = graph.entity(idA),
-            b = graph.entity(idB),
-            nodes;
+        var ways = ids.map(graph.entity, graph),
+            survivor = ways[0];
 
         // Prefer to keep an existing way.
-        if (a.isNew() && !b.isNew()) {
-            var tmp = a;
-            a = b;
-            b = tmp;
-            idA = a.id;
-            idB = b.id;
-        }
-
-        if (a.first() === b.first()) {
-            // a <-- b ==> c
-            // Expected result:
-            // a <-- b <-- c
-            b = iD.actions.Reverse(idB)(graph).entity(idB);
-            nodes = b.nodes.slice().concat(a.nodes.slice(1));
-
-        } else if (a.first() === b.last()) {
-            // a <-- b <== c
-            // Expected result:
-            // a <-- b <-- c
-            nodes = b.nodes.concat(a.nodes.slice(1));
-
-        } else if (a.last()  === b.first()) {
-            // a --> b ==> c
-            // Expected result:
-            // a --> b --> c
-            nodes = a.nodes.concat(b.nodes.slice(1));
-
-        } else if (a.last()  === b.last()) {
-            // a --> b <== c
-            // Expected result:
-            // a --> b --> c
-            b = iD.actions.Reverse(idB)(graph).entity(idB);
-            nodes = a.nodes.concat(b.nodes.slice().slice(1));
-        }
-
-        graph.parentRelations(b).forEach(function(parent) {
-            graph = graph.replace(parent.replaceMember(b, a));
-        });
+        for (var i = 0; i < ways.length; i++) {
+            if (!ways[i].isNew()) {
+                survivor = ways[i];
+                break;
+            }
+        }
 
-        graph = graph.replace(a.mergeTags(b.tags).update({ nodes: nodes }));
-        graph = iD.actions.DeleteWay(idB)(graph);
+        var joined = iD.geo.joinWays(ways, graph)[0];
+
+        survivor = survivor.update({nodes: _.pluck(joined.nodes, 'id')});
+        graph = graph.replace(survivor);
+
+        joined.forEach(function(way) {
+            if (way.id === survivor.id)
+                return;
+
+            graph.parentRelations(way).forEach(function(parent) {
+                graph = graph.replace(parent.replaceMember(way, survivor));
+            });
+
+            survivor = survivor.mergeTags(way.tags);
+
+            graph = graph.replace(survivor);
+            graph = iD.actions.DeleteWay(way.id)(graph);
+        });
 
         return graph;
     };
 
     action.disabled = function(graph) {
         var geometries = groupEntitiesByGeometry(graph);
-
-        if (ids.length !== 2 || ids.length !== geometries.line.length)
+        if (ids.length < 2 || ids.length !== geometries.line.length)
             return 'not_eligible';
 
-        var a = graph.entity(idA),
-            b = graph.entity(idB);
-
-        if (a.first() !== b.first() &&
-            a.first() !== b.last()  &&
-            a.last()  !== b.first() &&
-            a.last()  !== b.last())
+        var joined = iD.geo.joinWays(ids.map(graph.entity, graph), graph);
+        if (joined.length > 1)
             return 'not_adjacent';
+
+        var nodeIds = _.pluck(joined[0].nodes, 'id').slice(1, -1),
+            relation;
+
+        joined[0].forEach(function(way) {
+            var parents = graph.parentRelations(way);
+            parents.forEach(function(parent) {
+                if (parent.isRestriction() && parent.members.some(function(m) { return nodeIds.indexOf(m.id) >= 0; }))
+                    relation = parent;
+            });
+        });
+
+        if (relation)
+            return 'restriction';
     };
 
     return action;
@@ -16120,6 +16461,115 @@ iD.actions.Merge = function(ids) {
 
     return action;
 };
+iD.actions.MergePolygon = function(ids, newRelationId) {
+
+    function groupEntities(graph) {
+        var entities = ids.map(function (id) { return graph.entity(id); });
+        return _.extend({
+                closedWay: [],
+                multipolygon: [],
+                other: []
+            }, _.groupBy(entities, function(entity) {
+                if (entity.type === 'way' && entity.isClosed()) {
+                    return 'closedWay';
+                } else if (entity.type === 'relation' && entity.isMultipolygon()) {
+                    return 'multipolygon';
+                } else {
+                    return 'other';
+                }
+            }));
+    }
+
+    var action = function(graph) {
+        var entities = groupEntities(graph);
+
+        // An array representing all the polygons that are part of the multipolygon.
+        //
+        // Each element is itself an array of objects with an id property, and has a
+        // locs property which is an array of the locations forming the polygon.
+        var polygons = entities.multipolygon.reduce(function(polygons, m) {
+            return polygons.concat(iD.geo.joinWays(m.members, graph));
+        }, []).concat(entities.closedWay.map(function(d) {
+            var member = [{id: d.id}];
+            member.nodes = graph.childNodes(d);
+            return member;
+        }));
+
+        // contained is an array of arrays of boolean values,
+        // where contained[j][k] is true iff the jth way is
+        // contained by the kth way.
+        var contained = polygons.map(function(w, i) {
+            return polygons.map(function(d, n) {
+                if (i === n) return null;
+                return iD.geo.polygonContainsPolygon(
+                    _.pluck(d.nodes, 'loc'),
+                    _.pluck(w.nodes, 'loc'));
+            });
+        });
+
+        // Sort all polygons as either outer or inner ways
+        var members = [],
+            outer = true;
+
+        while (polygons.length) {
+            extractUncontained(polygons);
+            polygons = polygons.filter(isContained);
+            contained = contained.filter(isContained).map(filterContained);
+        }
+
+        function isContained(d, i) {
+            return _.any(contained[i]);
+        }
+
+        function filterContained(d, i) {
+            return d.filter(isContained);
+        }
+
+        function extractUncontained(polygons) {
+            polygons.forEach(function(d, i) {
+                if (!isContained(d, i)) {
+                    d.forEach(function(member) {
+                        members.push({
+                            type: 'way',
+                            id: member.id,
+                            role: outer ? 'outer' : 'inner'
+                        });
+                    });
+                }
+            });
+            outer = !outer;
+        }
+
+        // Move all tags to one relation
+        var relation = entities.multipolygon[0] ||
+            iD.Relation({ id: newRelationId, tags: { type: 'multipolygon' }});
+
+        entities.multipolygon.slice(1).forEach(function(m) {
+            relation = relation.mergeTags(m.tags);
+            graph = graph.remove(m);
+        });
+
+        members.forEach(function(m) {
+            var entity = graph.entity(m.id);
+            relation = relation.mergeTags(entity.tags);
+            graph = graph.replace(entity.update({ tags: {} }));
+        });
+
+        return graph.replace(relation.update({
+            members: members,
+            tags: _.omit(relation.tags, 'area')
+        }));
+    };
+
+    action.disabled = function(graph) {
+        var entities = groupEntities(graph);
+        if (entities.other.length > 0 ||
+            entities.closedWay.length + entities.multipolygon.length < 2)
+            return 'not_eligible';
+    };
+
+    return action;
+};
 // https://github.com/openstreetmap/josm/blob/mirror/src/org/openstreetmap/josm/command/MoveCommand.java
 // https://github.com/openstreetmap/potlatch2/blob/master/net/systemeD/halcyon/connection/actions/MoveNodeAction.as
 iD.actions.Move = function(ids, delta, projection) {
@@ -16547,18 +16997,6 @@ iD.actions.Split = function(nodeId, newWayIds) {
     return action;
 };
 iD.behavior = {};
-iD.behavior.accept = function() {
-    var event = d3.dispatch('accept'),
-        keybinding = d3.keybinding('accept');
-
-    function accept(selection) {
-        keybinding.on('↩', function() {
-            event.accept();
-        })(selection);
-    }
-
-    return d3.rebind(accept, event, "on");
-};
 iD.behavior.AddWay = function(context) {
     var event = d3.dispatch('start', 'startFromWay', 'startFromNode'),
         draw = iD.behavior.Draw(context);
@@ -16571,22 +17009,16 @@ iD.behavior.AddWay = function(context) {
             .on('finish', addWay.cancel);
 
         context.map()
-            .minzoom(16)
             .dblclickEnable(false);
 
         surface.call(draw);
     };
 
     addWay.off = function(surface) {
-        context.map()
-            .minzoom(0)
-            .tail(false);
-
         surface.call(draw.off);
     };
 
     addWay.cancel = function() {
-
         window.setTimeout(function() {
             context.map().dblclickEnable(true);
         }, 1000);
@@ -16594,6 +17026,11 @@ iD.behavior.AddWay = function(context) {
         context.enter(iD.modes.Browse(context));
     };
 
+    addWay.tail = function(text) {
+        draw.tail(text);
+        return addWay;
+    };
+
     return d3.rebind(addWay, event, 'on');
 };
 /*
@@ -16637,6 +17074,23 @@ iD.behavior.drag = function() {
       };
     };
 
+    var d3_event_userSelectProperty = iD.util.prefixCSSProperty("UserSelect"),
+        d3_event_userSelectSuppress = d3_event_userSelectProperty ?
+            function () {
+                var selection = d3.selection(),
+                    select = selection.style(d3_event_userSelectProperty);
+                selection.style(d3_event_userSelectProperty, 'none');
+                return function () {
+                    selection.style(d3_event_userSelectProperty, select);
+                };
+            } :
+            function (type) {
+                var w = d3.select(window).on("selectstart." + type, d3_eventCancel);
+                return function () {
+                    w.on("selectstart." + type, null);
+                };
+            };
+
     function mousedown() {
         target = this;
         event_ = event.of(target, arguments);
@@ -16644,7 +17098,8 @@ iD.behavior.drag = function() {
             touchId = d3.event.touches ? d3.event.changedTouches[0].identifier : null,
             offset,
             origin_ = point(),
-            moved = 0;
+            moved = 0,
+            selectEnable = d3_event_userSelectSuppress(touchId != null ? "drag-" + touchId : "drag");
 
         var w = d3.select(window)
             .on(touchId !== null ? "touchmove.drag-" + touchId : "mousemove.drag", dragmove)
@@ -16657,7 +17112,7 @@ iD.behavior.drag = function() {
             offset = [0, 0];
         }
 
-        if (touchId === null) d3_eventCancel();
+        if (touchId === null) d3.event.stopPropagation();
 
         function point() {
             var p = target.parentNode || surface;
@@ -16701,6 +17156,7 @@ iD.behavior.drag = function() {
 
             w.on(touchId !== null ? "touchmove.drag-" + touchId : "mousemove.drag", null)
                 .on(touchId !== null ? "touchend.drag-" + touchId : "mouseup.drag", null);
+            selectEnable();
         }
 
         function click() {
@@ -16709,30 +17165,12 @@ iD.behavior.drag = function() {
         }
     }
 
-    var lastPos = [[0, 0], [0, 0]],
-        lastTimes = [0, 0];
-
-    function move() {
-        lastPos.push([d3.event.clientX, d3.event.clientY]);
-        lastTimes.push((new Date()).getTime());
-        lastTimes.shift();
-        lastPos.shift();
-    }
-
     function drag(selection) {
         var matchesSelector = iD.util.prefixDOMProperty('matchesSelector'),
             delegate = mousedown;
 
         if (selector) {
             delegate = function() {
-
-                var velocity = Math.sqrt(
-                        Math.pow(lastPos[0][0] - d3.event.clientX, 2),
-                        Math.pow(lastPos[0][1] - d3.event.clientY, 2)) /
-                    ((new Date()).getTime() - lastTimes[0]);
-
-                if (velocity > 0.05) return;
-
                 var root = this,
                     target = d3.event.target;
                 for (; target && target !== root; target = target.parentNode) {
@@ -16744,16 +17182,12 @@ iD.behavior.drag = function() {
             };
         }
 
-        selection
-            .on("mousemove.drag" + selector, move)
-            .on("mousedown.drag" + selector, delegate)
+        selection.on("mousedown.drag" + selector, delegate)
             .on("touchstart.drag" + selector, delegate);
     }
 
     drag.off = function(selection) {
-        selection
-            .on("mousemove.drag" + selector, null)
-            .on("mousedown.drag" + selector, null)
+        selection.on("mousedown.drag" + selector, null)
             .on("touchstart.drag" + selector, null);
     };
 
@@ -16801,7 +17235,11 @@ iD.behavior.Draw = function(context) {
     var event = d3.dispatch('move', 'click', 'clickWay',
         'clickNode', 'undo', 'cancel', 'finish'),
         keybinding = d3.keybinding('draw'),
-        hover = iD.behavior.Hover().altDisables(true),
+        hover = iD.behavior.Hover(context)
+            .altDisables(true)
+            .on('hover', context.ui().sidebar.hover),
+        tail = iD.behavior.Tail(),
+        edit = iD.behavior.Edit(context),
         closeTolerance = 4,
         tolerance = 12;
 
@@ -16857,7 +17295,7 @@ iD.behavior.Draw = function(context) {
     function click() {
         var d = datum();
         if (d.type === 'way') {
-            var choice = iD.geo.chooseEdge(context.childNodes(d), d3.mouse(context.surface().node()),