diff --git a/ARCHITECTURE.md b/ARCHITECTURE.md index c0ff6b217..6a31b0161 100644 --- a/ARCHITECTURE.md +++ b/ARCHITECTURE.md @@ -19,35 +19,41 @@ which implements map panning and zooming. ## Core -The iD *core* implements the OSM data types, a graph of OSM object's -relationships to each other, and an undo/redo history for changes made during -editing. It aims to be generic enough to be used by other JavaScript-based +The iD *core* implements OSM data types, a graph of OSM objects' +relationships to one another, an undo/redo history for changes made during +editing, and a couple of important auxiliary classes. It eventually aims +to be a reusable, modular library to kickstart other JavaScript-based tools for OpenStreetMap. -Briefly, the OSM data model includes three basic data types: nodes, ways, and -relations. A _node_ is a point type, having a single geographic coordinate. A -_way_ is an ordered list of nodes. And a _relation_ groups together nodes, -ways, and other relations to provide free-form higher-level structures. Each -of these three types has _tags_: an associative array of key-value pairs which +The OSM data model includes three basic data types: nodes, ways, and +relations. + +* A _node_ is a point type, having a single geographic coordinate. +* A _way_ is an ordered list of nodes. +* A _relation_ groups together nodes, ways, and other relations to provide + free-form higher-level structures. + +Each of these three types has _tags_: an associative array of key-value pairs which describe the object. In iD, these three types are implemented by `iD.Node`, `iD.Way` and -`iD.Relation`. These three classes inherit from a common base, `iD.Entity` -(the only use of classical inheritance in iD). Generically, we refer to a +`iD.Relation`. These three classes inherit from a common base, `iD.Entity`. +This is the only use of classical inheritance in iD, but it's justified +by the common functionality of the types. Generically, we refer to a node, way or relation as an _entity_. -Every entity has an _ID_ either assigned by the OSM database, or, for an -entity that is newly created, constructed as a proxy consisting of a negative -numeral. IDs from the OSM database as treated as opaque strings; no +Every entity has an _ID_ either assigned by the OSM database or +a negative, local identifier assigned by iD for newly-created objects. +IDs from the OSM database as treated as opaque strings; no [assumptions](http://lists.openstreetmap.org/pipermail/dev/2013-February/026495.html) are made of them other than that they can be compared for identity and do not -begin with a minus sign (and thus will not conflict with proxy IDs). In fact, -in the OSM database the three types of entities have separate ID spaces; a -node can have the same ID as a way, for instance. Because it is useful to -store heterogeneous entities in the same datastructure, iD ensures that every -entity has a fully-unique ID by prefixing each OSM ID with the first letter of -the entity type. For example, a way with OSM ID 123456 is represented as -'w123456' within iD. +begin with a minus sign (and thus will not conflict with proxy IDs). The three +types of entities have separate ID spaces: a +node can have the same numeric ID as a way or a relation. Instead of segregating +ways, nodes, and other entities into different datastructures, +iD internally uses fully-unique IDs generated by prefixing +each OSM ID with the first letter of the entity type. For example, a way +with OSM ID 123456 is represented as 'w123456' within iD. iD entities are *immutable*: once constructed, an `Entity` object cannot change. Tags cannot be updated; nodes cannot be added or removed from ways, @@ -56,8 +62,9 @@ entity: if your code has a reference to one, it is safe to store it and use it later, knowing that it cannot have been changed outside of your control. It also makes it possible to implement the entity graph (described below) as an efficient [persistent data -structure](http://en.wikipedia.org/wiki/Persistent_data_structure). But -obviously, iD is an editor, and must allow entities to change somehow. The +structure](http://en.wikipedia.org/wiki/Persistent_data_structure). + +Since iD is an editor, it must allow for new versions of entities. The solution is that all edits produce new copies of anything that changes. At the entity level, this takes the form of methods such as `iD.Node#move`, which returns a new node object that has the same ID and tags as the original, but a @@ -65,39 +72,82 @@ different coordinate. More generically, `iD.Entity#update` returns a new entity of the same type and ID as the original but with specified properties such as `nodes`, `tags`, or `members` replaced. +![](http://farm9.staticflickr.com/8087/8508309757_ccf5b6f09b_o.png) + Entities are related to one another: ways have many nodes and relations have -many members. In order to render a map of a certain area, iD needs a +many members. To render a map of a certain area, iD needs a datastructure to hold all the entities in that area and traverse these relationships. `iD.Graph` provides this functionality. The core of a graph is a map between IDs and the associated entities; given an ID, the graph can give you the entity. Like entities, a graph is immutable: adding, replacing, or removing an entity produces a new graph, and the original is unchanged. -Because entities are immutable, the original and new graphs can share -references to entities that have not changed, keeping memory use to a minimum. -If you are familiar with how git works internally, this persistent data -structure approach is very similar. +Because entities are immutable, the original and new graphs can minimize +memory use by sharing references to entities that have not changed instead of +copying the entire graph. +This persistent data structure approach is similar to the internals of +the [git](http://git-scm.com/) revision control system. -The final component of the core is comprised of `iD.History` and -`iD.Difference`, which track the changes made in an editing session and -provide undo/redo capabilities. Here, the immutable nature of the core types -really pays off: the history is a simple stack of graphs, each representing -the state of the data at a particular point in editing. The graph at the top -of the stack is the current state, off which all rendering is based. To undo -the last change, this graph is popped off the stack, and the map is -re-rendered based on the new top of the stack. Contrast this to a mutable -graph as used in JOSM and Potlatch: every command that changes the graph must -implement an equal and opposite undo command that restores the graph to the -previous state. +The final major component of the core is `iD.History`, which tracks the changes +made in an editing session and provides undo/redo capabilities. Here, the +immutable nature of the core types really pays off: the history is a simple +stack of graphs, each representing the state of the data at a particular point +in editing. The graph at the top of the stack is the current state, off which +all rendering is based. To undo the last change, this graph is popped off the +stack, and the map is re-rendered based on the new top of the stack. + +This approach constitutes one of the main differences between iD's approach +to data and that of [JOSM](http://josm.openstreetmap.de/) and +[Potlatch 2](http://wiki.openstreetmap.org/wiki/Potlatch_2). +Instead of changing a single copy of local data and having to implement +an 'undo' for each specific action, actions in iD do not need to be aware +of history and the undo system. + +Finally, we have the auxiliary classes `iD.Difference` and `iD.Tree`. + +`iD.Difference` encapsulates the difference between two graphs, and knows how to calculate the +set of entities that were created, modified, or deleted, and need to be redrawn. + +```js +var a = iD.Graph(), b = iD.Graph(); +// (fill a & b with data) +var difference = iD.Difference(a, b); + +// returns entities created between and b +difference.created(); +``` + +`iD.Tree` calculates the set of downloaded entities that are visible in the +current map view. To calculate this quickly during map +interaction, it uses an [R-tree](http://en.wikipedia.org/wiki/R-tree). + +```js +var graph = iD.Graph(); +// (load OSM data into graph) + +// this tree indexes the contents of the graph +var tree = iD.Tree(graph); + +// quickly pull all features that intersect with an extent +var features = tree.intersects( + iD.geo.Extent([0, 0], [2, 2]), tree.graph()); +``` ## Actions In iD, an _action_ is a function that accepts a graph as input and returns a -modified graph as output. Actions typically need other inputs as well; for +new, modified graph as output. Actions typically need other inputs as well; for example, `iD.actions.DeleteNode` also requires the ID of a node to delete. The additional input is passed to the action's constructor: -``` var action = iD.actions.DeleteNode('n123456'); // construct the action var -newGraph = action(oldGraph); // apply the action ``` +```js +// construct the action: this returns a function that remembers the +// value `n123456` in a closure so that when it's called, it runs +// the specified action on the graph +var action = iD.actions.DeleteNode('n123456'); + +// apply the action, yielding a new graph. oldGraph is untouched. +newGraph = action(oldGraph); +``` iD provides actions for all the typical things an editor needs to do: add a new entity, split a way in two, connect the vertices of two ways together, and @@ -112,9 +162,8 @@ is a member. As you can imagine, implementing all these details requires an expert knowledge of the OpenStreetMap data model. It is our hope that JavaScript -based tools for OpenStreetMap can reuse the implementations provided by iD in -other contexts, significantly reducing the work necessary to create a robust -tool. +based tools for OpenStreetMap can reuse the iD's core implementation, +significantly reducing the work necessary to create a robust tool. ## Modes @@ -200,4 +249,101 @@ the history, and then enter the appropriate mode. For example, `iD.operations.Split` performs `iD.actions.Split`, then enters `iD.modes.Select` with the resulting ways selected. -## Rendering and other UI +## Map Rendering + +Finally, we get to the parts of iD that actually draw and manipulate the map +entities on screen. The rendering is coordinated by `iD.Map`, which takes care +of setting up a [Spherical Mercator](http://bl.ocks.org/mbostock/3757132) +projection and the [zoom +behavior](https://github.com/mbostock/d3/wiki/Zoom-Behavior), and provides +accessors for such things as the current zoom level and map center. + +For rendering entities on screen, we found it convenient to adopt a geometric +vocabulary that provides a slightly higher-level representation than the basic +entity types of the OSM data model: + +* A _point_ is a node that is not a member of any way. +* A _vertex_ is a node that is a member of one or more ways. +* A _line_ is a way that is not an area. +* An _area_ is a way that is circular and has certain tags, or a series of one + or more ways grouped in a multipolygon relation. + +For each of these geometric types, `iD.svg` has a corresponding module: +`iD.svg.Points`, `iD.svg.Vertices`, `iD.svg.Lines`, and `iD.svg.Areas`. To +render entities on screen, `iD.Map` delegates to these modules. Internally, +they make heavy use of [d3 joins](http://bost.ocks.org/mike/join/) to +manipulate the SVG elements that visually represent the map entities. When an +entity is rendered for the first time, it is part of the _enter_ selection, +and the SVG elements needed to represent it are created. When an entity is +modified, it is part of the _update_ selection, and the appropriate attributes +of the SVG element (for example, those that specify the location on screen) +are updated. And when an entity is deleted (or simply moves offscreen), the +corresponding SVG element is in the _exit_ selection, and will be removed. + +The `iD.svg` modules apply classes to the SVG elements based on the entity +tags, via `iD.svg.TagClasses`. For example, an entity tagged with +`highway=residential` gets two classes: `tag-highway` and +`tag-highway-residential`. This allows distinct visual styles to be applied +via CSS at either the key or key-value levels. SVG elements also receive a +class corresponding to their entity type (`node`, `way`, or `relation`) and +one corresponding to their geometry type (`point`, `line`, or `area`). + +The `iD.svg` namespace has a few other modules that don't have a one-to-one +correspondence with entities: + +* `iD.svg.Midpoints` renders the small "virtual node" at the midpoint between + two vertices. +* `iD.svg.Labels` renders the textual + [labels](http://mapbox.com/osmdev/2013/02/12/labeling-id/). +* `iD.svg.Surface` sets up a number of layers that ensure that map elements + appear in an appropriate z-order. + +## Other UI + +iD provides a lot of user interface elements other than the core map component: +the page footer, the interface for saving changes, the splash screen you see +the first time you use iD, the geocoding and background layer controls, and the +tag/preset editor, for example. + +![Geocoder UI](img/geocoder.png) + +The implementations for all non-map UI components live in the `iD.ui` namespace. +Many of the modules in this namespace follow a pattern for reusable d3 +components [originally suggested](http://bost.ocks.org/mike/chart/) by Mike +Bostock in the context of charts. The entry point to a UI element is a +constructor function, e.g. `iD.ui.Geocoder()`. The constructor function may +require a set of mandatory arguments; for most UI components exactly one +argument is required, a `context` object produced by the top-level `iD()` +function. + +A component needs some way to be rendered on screen by creating new DOM +elements or manipulating existing elements. This is done by calling the +component as a function, and passing a d3 selection where the component should +render itself: + +``` +var container = d3.select('body').append('div') + .attr('class', 'map-control geocode-control'); + +var geocoder = iD.ui.Geocoder(context)(container); +``` + +Alternatively, and more commonly, the same result is accomplished with +[d3.selection#call](https://github.com/mbostock/d3/wiki/Selections#wiki-call): + +``` +d3.select('body').append('div') + .attr('class', 'map-control geocode-control') + .call(iD.ui.Geocoder(context)); +``` + +Some components are reconfigurable, and some provide functionality beyond +basic rendering. Both reconfiguration and extended functionality are exposed +via module functions: + +``` +var inspector = iD.ui.Inspector(); +inspector(container); // render the inspector +inspector.tags(); // retrieve the current tags +inspector.on('change', callback); // get notified when a tag change is made +``` diff --git a/Makefile b/Makefile index 69eebd1e4..85de984aa 100644 --- a/Makefile +++ b/Makefile @@ -18,7 +18,6 @@ all: \ js/lib/d3.keybinding.js \ js/lib/d3.one.js \ js/lib/d3.size.js \ - js/lib/d3.tail.js \ js/lib/d3.trigger.js \ js/lib/d3.typeahead.js \ js/lib/jxon.js \ @@ -51,6 +50,7 @@ all: \ js/id/svg/*.js \ js/id/ui.js \ js/id/ui/*.js \ + js/id/ui/preset/*.js \ js/id/presetdata.js \ js/id/validate.js \ js/id/end.js \ @@ -63,7 +63,7 @@ iD.js: Makefile %.min.js: %.js Makefile @rm -f $@ - $(JS_COMPILER) $< -o $@ + $(JS_COMPILER) $< -c -m -o $@ clean: rm -f iD*.js diff --git a/combobox.html b/combobox.html index 6eaa944a9..663355663 100644 --- a/combobox.html +++ b/combobox.html @@ -23,7 +23,6 @@ - @@ -70,6 +69,7 @@ + @@ -81,7 +81,7 @@ - + @@ -103,7 +103,7 @@ - + diff --git a/css/app.css b/css/app.css index 380ee0cdd..5948a52c5 100644 --- a/css/app.css +++ b/css/app.css @@ -278,6 +278,7 @@ button { transition: background 100ms; } +button:focus, button:hover { background-color: #ececec; } @@ -641,6 +642,26 @@ div.combobox { border-right: 5px solid transparent; } +.rowselect .item { + cursor: pointer; +} + +.rowselect .item label:hover{ + background-color: #ececec; +} + +.rowselect .item label { + padding: 5px; + width: 100%; + cursor: pointer; + display: block; + text-align: center; +} + +.rowselect .item div { + text-align: center; +} + /* Address input */ .preset-input .addr-housename { @@ -653,7 +674,7 @@ div.combobox { border-right: none; } -.preset-input .addr-streetname { +.preset-input .addr-street { width: 80%; } @@ -756,17 +777,25 @@ div.combobox { margin-bottom: 20px; } -.preset-grid.filtered .grid-entry:first-child, -.preset-grid .grid-entry .grid-inner:hover { +.preset-grid.filtered .grid-entry:first-child { background: #ececec; } -.preset-grid .grid-entry .grid-inner:hover { - background: inherit; +.grid-entry .preset-help { + position: absolute; + bottom: 0px; + right: 0px; + width: 20px; + height: 20px; + background: black; } -.grid-entry .grid-inner { - position: absolute; +.grid-entry .preset-help:hover { + background: grey; +} + +.preset-inspect { + position: relative; } .grid-entry .preset-icon-fill.area { @@ -1099,6 +1128,10 @@ img.tile { -webkit-transform-origin:0 0; -moz-transform-origin:0 0; -o-transform-origin:0 0; + -moz-user-select: none; + -webkit-user-select: none; + -ms-user-select: none; + user-select: none; } #surface { diff --git a/css/map.css b/css/map.css index 910050010..3733327c4 100644 --- a/css/map.css +++ b/css/map.css @@ -229,6 +229,11 @@ path.area.fill { fill-rule: evenodd; } +path.line.stroke { + stroke: white; + stroke-width: 2; +} + path.stroke.tag-natural { stroke: #b6e199; stroke-width:1; @@ -507,28 +512,28 @@ svg[data-zoom="16"] path.casing.tag-highway-construction { /* railways */ -path.stroke.tag-railway { +.line.stroke.tag-railway { stroke: #eee; stroke-width: 2; stroke-linecap: butt; stroke-dasharray: 12,12; } -path.casing.tag-railway { +.line.casing.tag-railway { stroke: #555; stroke-width: 4; } -path.stroke.tag-railway-abandoned { +.line.stroke.tag-railway-abandoned { stroke: #eee; } -path.casing.tag-railway-abandoned { +.line.casing.tag-railway-abandoned { stroke: #999; } -path.stroke.tag-railway-subway { +.line.stroke.tag-railway-subway { stroke: #666; } -path.casing.tag-railway-subway { +.line.casing.tag-railway-subway { stroke: #222; } diff --git a/docs/coding_standards.txt b/docs/coding_standards.txt index 5c5e8d6d1..2c509d515 100644 --- a/docs/coding_standards.txt +++ b/docs/coding_standards.txt @@ -15,16 +15,6 @@ In order to unify the construction interface for these two styles, classical cla [instanceof trick](http://ejohn.org/blog/simple-class-instantiation/). This allows instantiation of both module pattern classes and classical classes to be done without using `new`. -Function names --------------- -Anything that creates and calls an Action should be prefixed with do: - doSetLatLon(lat,lon) - -Anything that is called by an Action, to do the actual work, should be prefixed with an underscore: - _setLatLon(lat,lon) - -and commented as such. Underscores are also used to prefix private methods. - File naming ----------- The filename should be the name of the base class. You can add subclasses within diff --git a/img/geocoder.png b/img/geocoder.png new file mode 100644 index 000000000..9284ee67f Binary files /dev/null and b/img/geocoder.png differ diff --git a/index.html b/index.html index 6701f2b82..b25a34bca 100644 --- a/index.html +++ b/index.html @@ -20,11 +20,11 @@ + - @@ -91,7 +91,8 @@ - + + @@ -107,7 +108,7 @@ - + diff --git a/js/id/actions/delete_relation.js b/js/id/actions/delete_relation.js index 48c62f1e1..783f1533c 100644 --- a/js/id/actions/delete_relation.js +++ b/js/id/actions/delete_relation.js @@ -1,5 +1,11 @@ // https://github.com/openstreetmap/potlatch2/blob/master/net/systemeD/halcyon/connection/actions/DeleteRelationAction.as iD.actions.DeleteRelation = function(relationId) { + function deleteEntity(entity, graph) { + return !graph.parentWays(entity).length && + !graph.parentRelations(entity).length && + !entity.hasInterestingTags(); + } + return function(graph) { var relation = graph.entity(relationId); @@ -8,6 +14,15 @@ iD.actions.DeleteRelation = function(relationId) { graph = graph.replace(parent.removeMember(relationId)); }); + _.uniq(_.pluck(relation.members, 'id')).forEach(function(memberId) { + graph = graph.replace(relation.removeMember(memberId)); + + var entity = graph.entity(memberId); + if (deleteEntity(entity, graph)) { + graph = iD.actions.DeleteMultiple([memberId])(graph); + } + }); + return graph.remove(relation); }; }; diff --git a/js/id/actions/delete_way.js b/js/id/actions/delete_way.js index 95888470e..9d98a697b 100644 --- a/js/id/actions/delete_way.js +++ b/js/id/actions/delete_way.js @@ -1,5 +1,11 @@ // https://github.com/openstreetmap/potlatch2/blob/master/net/systemeD/halcyon/connection/actions/DeleteWayAction.as iD.actions.DeleteWay = function(wayId) { + function deleteNode(node, graph) { + return !graph.parentWays(node).length && + !graph.parentRelations(node).length && + !node.hasInterestingTags(); + } + return function(graph) { var way = graph.entity(wayId); @@ -8,20 +14,12 @@ iD.actions.DeleteWay = function(wayId) { graph = graph.replace(parent.removeMember(wayId)); }); - way.nodes.forEach(function(nodeId) { - var node = graph.entity(nodeId); - - // Circular ways include nodes more than once, so they - // can be deleted on earlier iterations of this loop. - if (!node) return; - + _.uniq(way.nodes).forEach(function(nodeId) { graph = graph.replace(way.removeNode(nodeId)); - if (!graph.parentWays(node).length && - !graph.parentRelations(node).length) { - if (!node.hasInterestingTags()) { - graph = graph.remove(node); - } + var node = graph.entity(nodeId); + if (deleteNode(node, graph)) { + graph = graph.remove(node); } }); diff --git a/js/id/actions/move.js b/js/id/actions/move.js new file mode 100644 index 000000000..09a69f09c --- /dev/null +++ b/js/id/actions/move.js @@ -0,0 +1,31 @@ +// 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) { + function addNodes(ids, nodes, graph) { + ids.forEach(function(id) { + var entity = graph.entity(id); + if (entity.type === 'node') { + nodes.push(id); + } else if (entity.type === 'way') { + nodes.push.apply(nodes, entity.nodes); + } else { + addNodes(_.pluck(entity.members, 'id'), nodes, graph); + } + }); + } + + return function(graph) { + var nodes = []; + + addNodes(ids, nodes, graph); + + _.uniq(nodes).forEach(function(id) { + var node = graph.entity(id), + start = projection(node.loc), + end = projection.invert([start[0] + delta[0], start[1] + delta[1]]); + graph = graph.replace(node.move(end)); + }); + + return graph; + }; +}; diff --git a/js/id/actions/move_way.js b/js/id/actions/move_way.js deleted file mode 100644 index 376e31d6e..000000000 --- a/js/id/actions/move_way.js +++ /dev/null @@ -1,14 +0,0 @@ -iD.actions.MoveWay = function(wayId, delta, projection) { - return function(graph) { - return graph.update(function(graph) { - var way = graph.entity(wayId); - - _.uniq(way.nodes).forEach(function(id) { - var node = graph.entity(id), - start = projection(node.loc), - end = projection.invert([start[0] + delta[0], start[1] + delta[1]]); - graph = graph.replace(node.move(end)); - }); - }); - }; -}; diff --git a/js/id/behavior/drag_node.js b/js/id/behavior/drag_node.js index c16ede85f..e8749927f 100644 --- a/js/id/behavior/drag_node.js +++ b/js/id/behavior/drag_node.js @@ -98,7 +98,8 @@ iD.behavior.DragNode = function(context) { } } - context.replace(iD.actions.MoveNode(entity.id, loc)); + context.replace(iD.actions.MoveNode(entity.id, loc), + t('operations.move.annotation.' + entity.geometry(context.graph()))); } function end(entity) { diff --git a/js/id/core/history.js b/js/id/core/history.js index 3ae02b42d..cce3f3330 100644 --- a/js/id/core/history.js +++ b/js/id/core/history.js @@ -240,7 +240,7 @@ iD.History = function(context) { context.storage(getKey('nextIDs', null)); context.storage(getKey('index', null)); - stack = JSON.parse(json).map(function(d, i) { + stack = JSON.parse(json).map(function(d) { d.graph = iD.Graph(stack[0].graph).load(d.entities); return d; }); diff --git a/js/id/core/tree.js b/js/id/core/tree.js index 94b174230..10ea4e1f2 100644 --- a/js/id/core/tree.js +++ b/js/id/core/tree.js @@ -1,5 +1,3 @@ - - iD.Tree = function(graph) { var rtree = new RTree(), @@ -58,7 +56,10 @@ iD.Tree = function(graph) { }); var created = diff.created().concat(queuedCreated); - modified = d3.values(diff.addParents(modified)).concat(queuedModified); + modified = d3.values(diff.addParents(modified)) + // some parents might be created, not modified + .filter(function(d) { return !!graph.entity(d.id); }) + .concat(queuedModified); queuedCreated = []; queuedModified = []; diff --git a/js/id/id.js b/js/id/id.js index 5dc8fb127..74d0b6d82 100644 --- a/js/id/id.js +++ b/js/id/id.js @@ -4,7 +4,7 @@ window.iD = function () { // https://github.com/systemed/iD/issues/772 // http://mathiasbynens.be/notes/localstorage-pattern#comment-9 - try { storage = localStorage } catch (e) {} + try { storage = localStorage; } catch (e) {} storage = storage || {}; context.storage = function(k, v) { @@ -106,7 +106,8 @@ window.iD = function () { context.background() .source(_.find(iD.layers, function(l) { if (l.data.sourcetag === q.layer) { - return (detected = true); + detected = true; + return true; } })); } diff --git a/js/id/modes/add_line.js b/js/id/modes/add_line.js index ae1d03cef..ef5a43c0d 100644 --- a/js/id/modes/add_line.js +++ b/js/id/modes/add_line.js @@ -10,13 +10,12 @@ iD.modes.AddLine = function(context) { var behavior = iD.behavior.AddWay(context) .on('start', start) .on('startFromWay', startFromWay) - .on('startFromNode', startFromNode), - defaultTags = {highway: 'residential'}; + .on('startFromNode', startFromNode); function start(loc) { var graph = context.graph(), node = iD.Node({loc: loc}), - way = iD.Way({tags: defaultTags}); + way = iD.Way(); context.perform( iD.actions.AddEntity(node), @@ -29,7 +28,7 @@ iD.modes.AddLine = function(context) { function startFromWay(other, loc, index) { var graph = context.graph(), node = iD.Node({loc: loc}), - way = iD.Way({tags: defaultTags}); + way = iD.Way(); context.perform( iD.actions.AddEntity(node), @@ -52,7 +51,7 @@ iD.modes.AddLine = function(context) { context.enter(iD.modes.DrawLine(context, parent.id, 'forward', graph)); } else { - var way = iD.Way({tags: defaultTags}); + var way = iD.Way(); context.perform( iD.actions.AddEntity(way), diff --git a/js/id/modes/move.js b/js/id/modes/move.js index d6bdde104..d2089a2ad 100644 --- a/js/id/modes/move.js +++ b/js/id/modes/move.js @@ -4,14 +4,13 @@ iD.modes.Move = function(context, entityIDs) { button: 'browse' }; - var keybinding = d3.keybinding('move'), - entities = entityIDs.map(context.entity); + var keybinding = d3.keybinding('move'); mode.enter = function() { var origin, nudgeInterval, - annotation = entities.length === 1 ? - t('operations.move.annotation.' + context.geometry(entities[0].id)) : + annotation = entityIDs.length === 1 ? + t('operations.move.annotation.' + context.geometry(entityIDs[0])) : t('operations.move.annotation.multiple'); context.perform( @@ -57,29 +56,19 @@ iD.modes.Move = function(context, entityIDs) { origin = context.map().mouseCoordinates(); - entities.forEach(function(entity) { - if (entity.type === 'way') { - context.replace( - iD.actions.MoveWay(entity.id, delta, context.projection)); - } else if (entity.type === 'node') { - var start = context.projection(context.entity(entity.id).loc), - end = [start[0] + delta[0], start[1] + delta[1]], - loc = context.projection.invert(end); - context.replace(iD.actions.MoveNode(entity.id, loc)); - } - }); - - context.replace(iD.actions.Noop(), annotation); + context.replace( + iD.actions.Move(entityIDs, delta, context.projection), + annotation); } function finish() { d3.event.stopPropagation(); - context.enter(iD.modes.Select(context, entityIDs, true)); + context.enter(iD.modes.Select(context, entityIDs)); } function cancel() { context.pop(); - context.enter(iD.modes.Select(context, entityIDs, true)); + context.enter(iD.modes.Select(context, entityIDs)); } function undone() { diff --git a/js/id/modes/select.js b/js/id/modes/select.js index 93bb991c1..ee218c743 100644 --- a/js/id/modes/select.js +++ b/js/id/modes/select.js @@ -4,7 +4,7 @@ iD.modes.Select = function(context, selection, initial) { button: 'browse' }; - var inspector = iD.ui.Inspector().initial(!!initial), + var inspector = iD.ui.Inspector(context).initial(!!initial), keybinding = d3.keybinding('select'), timeout = null, behaviors = [ @@ -84,7 +84,6 @@ iD.modes.Select = function(context, selection, initial) { if (entity) { inspector - .context(context) .presetData(context.connection().presetData()); context.container() diff --git a/js/id/oauth.js b/js/id/oauth.js index ec6a12668..632dc60cd 100644 --- a/js/id/oauth.js +++ b/js/id/oauth.js @@ -4,8 +4,6 @@ iD.OAuth = function(context) { keys, oauth = {}; - function keyclean(x) { return x.replace(/\W/g, ''); } - function timenonce(o) { o.oauth_timestamp = ohauth.timestamp(); o.oauth_nonce = ohauth.nonce(); @@ -73,17 +71,13 @@ iD.OAuth = function(context) { ['top', screen.height / 2 - h / 2]].map(function(x) { return x.join('='); }).join(','), - popup = window.open("about:blank", 'oauth_window', settings), - locationCheck = window.setInterval(function() { - if (popup.closed) return window.clearInterval(locationCheck); - if (popup.location.search) { - var search = popup.location.search, - oauth_token = ohauth.stringQs(search.slice(1)); - popup.close(); - get_access_token(oauth_token); - window.clearInterval(locationCheck); - } - }, 100); + popup = window.open("about:blank", 'oauth_window', settings); + + window.authComplete = function(token) { + var oauth_token = ohauth.stringQs(token); + get_access_token(oauth_token); + delete window.authComplete; + }; function reqTokenDone(err, xhr) { if (err) callback(err); diff --git a/js/id/operations/delete.js b/js/id/operations/delete.js index 2c9765b62..45d349c07 100644 --- a/js/id/operations/delete.js +++ b/js/id/operations/delete.js @@ -11,6 +11,8 @@ iD.operations.Delete = function(selection, context) { context.perform( iD.actions.DeleteMultiple(selection), annotation); + + context.enter(iD.modes.Browse(context)); }; operation.available = function() { diff --git a/js/id/operations/move.js b/js/id/operations/move.js index dd001be8d..22d913ce0 100644 --- a/js/id/operations/move.js +++ b/js/id/operations/move.js @@ -6,7 +6,7 @@ iD.operations.Move = function(selection, context) { operation.available = function() { return selection.length > 1 || - context.entity(selection[0]).type === 'way'; + context.entity(selection[0]).type !== 'node'; }; operation.enabled = function() { diff --git a/js/id/presetdata.js b/js/id/presetdata.js index aec3e6b4d..8ccee1176 100644 --- a/js/id/presetdata.js +++ b/js/id/presetdata.js @@ -1,7 +1,19 @@ iD.presetData = function() { + + var other = { + name: 'other', + title: 'Other', + icon: 'marker-stroked', + match: { + tags: {}, + type: ['node', 'line', 'area'] + }, + form: [] + }; + var presets = {}, - data = [], - categories = {}, + data = [other], + categories = [], defaults = { node: [], area: [], @@ -16,7 +28,7 @@ iD.presetData = function() { presets.data = function(_) { if (!arguments.length) return data; - data = _.presets; + data = _.presets.concat([other]); categories = _.categories; defaults = _.defaults; return presets; diff --git a/js/id/renderer/map.js b/js/id/renderer/map.js index be4366355..f672c55ed 100644 --- a/js/id/renderer/map.js +++ b/js/id/renderer/map.js @@ -21,7 +21,7 @@ iD.Map = function(context) { areas = iD.svg.Areas(roundedProjection), midpoints = iD.svg.Midpoints(roundedProjection), labels = iD.svg.Labels(roundedProjection), - tail = d3.tail(), + tail = iD.ui.Tail(), surface, tilegroup; function map(selection) { diff --git a/js/id/services/taginfo.js b/js/id/services/taginfo.js index 850de9efd..b950952eb 100644 --- a/js/id/services/taginfo.js +++ b/js/id/services/taginfo.js @@ -14,6 +14,8 @@ iD.taginfo = function() { line: 'ways' }; + var cache = this.cache = {}; + function sets(parameters, n, o) { if (parameters.geometry && o[parameters.geometry]) { parameters[n] = o[parameters.geometry]; @@ -30,16 +32,25 @@ iD.taginfo = function() { } function clean(parameters) { - return _.omit(parameters, 'geometry'); + return _.omit(parameters, 'geometry', 'debounce'); + } + + function shorten(parameters) { + if (!parameters.query) { + delete parameters.query; + } else { + parameters.query = parameters.query.slice(0, 3); + } + return parameters; } function popularKeys(parameters) { - var pop_field = 'count_all_fraction'; - if (parameters.filter) pop_field = 'count_' + parameters.filter + '_fraction'; - return function(d) { return parseFloat(d[pop_field]) > 0.01; }; + var pop_field = 'count_all'; + if (parameters.filter) pop_field = 'count_' + parameters.filter; + return function(d) { return parseFloat(d[pop_field]) > 10000; }; } - function popularValues(parameters) { + function popularValues() { return function(d) { return parseFloat(d.fraction) > 0.01; }; } @@ -52,38 +63,58 @@ iD.taginfo = function() { }; } + var debounced = _.debounce(d3.json, 100, true); + + function request(url, debounce, callback) { + if (cache[url]) { + callback(null, cache[url]); + } else if (debounce) { + debounced(url, done); + } else { + d3.json(url, done); + } + + function done(err, data) { + if (!err) cache[url] = data; + callback(err, data); + } + } + taginfo.keys = function(parameters, callback) { - parameters = clean(setSort(setFilter(parameters))); - d3.json(endpoint + 'keys/all?' + + var debounce = parameters.debounce; + parameters = clean(shorten(setSort(setFilter(parameters)))); + request(endpoint + 'keys/all?' + iD.util.qsString(_.extend({ - rp: 6, + rp: 10, sortname: 'count_all', sortorder: 'desc', page: 1 - }, parameters)), function(err, d) { + }, parameters)), debounce, function(err, d) { if (err) return callback(err); callback(null, d.data.filter(popularKeys(parameters)).map(valKey)); }); }; taginfo.values = function(parameters, callback) { - parameters = clean(setSort(setFilter(parameters))); - d3.json(endpoint + 'key/values?' + + var debounce = parameters.debounce; + parameters = clean(shorten(setSort(setFilter(parameters)))); + request(endpoint + 'key/values?' + iD.util.qsString(_.extend({ rp: 20, sortname: 'count_all', sortorder: 'desc', page: 1 - }, parameters)), function(err, d) { + }, parameters)), debounce, function(err, d) { if (err) return callback(err); callback(null, d.data.filter(popularValues()).map(valKeyDescription), parameters); }); }; taginfo.docs = function(parameters, callback) { + var debounce = parameters.debounce; parameters = clean(setSort(parameters)); - d3.json(endpoint + 'tag/wiki_pages?' + - iD.util.qsString(parameters), callback); + request(endpoint + (parameters.value ? 'tag/wiki_pages?' : 'key/wiki_pages?') + + iD.util.qsString(parameters), debounce, callback); }; taginfo.endpoint = function(_) { diff --git a/js/id/svg/labels.js b/js/id/svg/labels.js index 507f4cbdd..0636c49d6 100644 --- a/js/id/svg/labels.js +++ b/js/id/svg/labels.js @@ -80,9 +80,9 @@ iD.svg.Labels = function(projection) { .data(entities, iD.Entity.key) .attr({ 'startOffset': '50%', - 'xlink:href': function(d, i) { return '#halo-' + d.id; } + 'xlink:href': function(d) { return '#halo-' + d.id; } }) - .text(function(d, i) { return name(d); }); + .text(function(d) { return name(d); }); texts.exit().remove(); @@ -97,7 +97,7 @@ iD.svg.Labels = function(projection) { halos.enter() .append('path') .style('stroke-width', get(labels, 'font-size')) - .attr('id', function(d, i) { return 'halo-' + d.id; }) + .attr('id', function(d) { return 'halo-' + d.id; }) .attr('class', classes); halos.attr('d', get(labels, 'lineString')); diff --git a/js/id/svg/lines.js b/js/id/svg/lines.js index 59fec46e1..a94ad3a25 100644 --- a/js/id/svg/lines.js +++ b/js/id/svg/lines.js @@ -127,7 +127,7 @@ iD.svg.Lines = function(projection) { text.selectAll('.textpath') .filter(filter) - .attr('xlink:href', function(d, i) { return '#shadow-' + d.id; }) + .attr('xlink:href', function(d) { return '#shadow-' + d.id; }) .text(function(d) { // adding longer text than necessary, since overflow is hidden return (new Array(Math.floor(lengths[d.id] * 1.1))).join(arrowtext); diff --git a/js/id/svg/member_classes.js b/js/id/svg/member_classes.js index 713cc898e..1b4adf673 100644 --- a/js/id/svg/member_classes.js +++ b/js/id/svg/member_classes.js @@ -2,7 +2,7 @@ iD.svg.MemberClasses = function(graph) { var tagClassRe = /^member-?/; return function memberClassesSelection(selection) { - selection.each(function memberClassesEach(d, i) { + selection.each(function memberClassesEach(d) { var classes, value = this.className; if (value.baseVal !== undefined) value = value.baseVal; diff --git a/js/id/ui/attribution.js b/js/id/ui/attribution.js index 282833e7e..73a769b06 100644 --- a/js/id/ui/attribution.js +++ b/js/id/ui/attribution.js @@ -6,5 +6,5 @@ iD.ui.Attribution = function(context) { selection .append('span') .attr('class', 'provided-by'); - } + }; }; diff --git a/js/id/ui/commit.js b/js/id/ui/commit.js index da6fe5c02..efdc1c190 100644 --- a/js/id/ui/commit.js +++ b/js/id/ui/commit.js @@ -46,18 +46,18 @@ iD.ui.Commit = function(context) { var userLink = d3.select(document.createElement('div')); - userLink.append('a') - .attr('class','user-info') - .text(user.display_name) - .attr('href', connection.url() + '/user/' + user.display_name) - .attr('target', '_blank'); - if (user.image_url) { userLink.append('img') .attr('src', user.image_url) .attr('class', 'icon icon-pre-text user-icon'); } + userLink.append('a') + .attr('class','user-info') + .text(user.display_name) + .attr('href', connection.url() + '/user/' + user.display_name) + .attr('target', '_blank'); + commentSection.append('p') .attr('class', 'commit-info') .html(t('commit.upload_explanation', {user: userLink.html()})); @@ -108,7 +108,9 @@ iD.ui.Commit = function(context) { .enter() .append('li'); - warningLi.append('button') + // only show the fix icon when an entity is given + warningLi.filter(function(d) { return d.entity; }) + .append('button') .attr('class', 'minor') .on('click', event.fix) .append('span') diff --git a/js/id/ui/contributors.js b/js/id/ui/contributors.js index bd9e43503..2cf291886 100644 --- a/js/id/ui/contributors.js +++ b/js/id/ui/contributors.js @@ -4,9 +4,9 @@ iD.ui.Contributors = function(context) { limit = 4, entities = context.intersects(context.map().extent()); - for (var i in entities) { - if (entities[i].user) users[entities[i].user] = true; - } + entities.forEach(function(entity) { + if (entity && entity.user) users[entity.user] = true; + }); var u = Object.keys(users), subset = u.slice(0, u.length > limit ? limit - 1 : limit); diff --git a/js/id/ui/geocoder.js b/js/id/ui/geocoder.js index cc0ce205c..c8774fc6e 100644 --- a/js/id/ui/geocoder.js +++ b/js/id/ui/geocoder.js @@ -59,7 +59,7 @@ iD.ui.Geocoder = function(context) { } function hide() { setVisible(false); } - function toggle() { setVisible(gcForm.classed('hide')); } + function toggle() { tooltip.hide(button); setVisible(gcForm.classed('hide')); } function setVisible(show) { if (show !== shown) { @@ -71,13 +71,13 @@ iD.ui.Geocoder = function(context) { shown = show; } } + var tooltip = bootstrap.tooltip().placement('right'); var button = selection.append('button') .attr('tabindex', -1) .attr('title', t('geocoder.title')) .on('click', toggle) - .call(bootstrap.tooltip() - .placement('right')); + .call(tooltip); button.append('span') .attr('class', 'icon geocode'); diff --git a/js/id/ui/inspector.js b/js/id/ui/inspector.js index ddd7211b8..e89e7bd02 100644 --- a/js/id/ui/inspector.js +++ b/js/id/ui/inspector.js @@ -1,15 +1,11 @@ -iD.ui.Inspector = function() { +iD.ui.Inspector = function(context) { var event = d3.dispatch('changeTags', 'close', 'change'), - taginfo = iD.taginfo(), presetData = iD.presetData(), initial = false, inspectorbody, entity, - presetUI, presetGrid, - tagList, - tagEditor, - context; + tagEditor; function inspector(selection) { @@ -21,29 +17,27 @@ iD.ui.Inspector = function() { inspectorbody = selection.append('div') .attr('class', 'fillL'), - selection.append('div') - .attr('class', 'inspector-actions pad1 fillD col12') - .call(drawButtons); - presetGrid = iD.ui.PresetGrid() + presetGrid = iD.ui.PresetGrid(context) .presetData(presetData) .entity(entity) - .context(context) .on('message', changeMessage) .on('choose', function(preset) { inspectorbody.call(tagEditor, preset); }); - tagEditor = iD.ui.TagEditor() + tagEditor = iD.ui.TagEditor(context) .presetData(presetData) .tags(entity.tags) - .context(context) .on('message', changeMessage) - .on('change', function() { + .on('changeTags', function() { event.changeTags(entity, inspector.tags()); }) + .on('close', function() { + event.close(entity); + }) .on('choose', function() { - inspectorbody.call(presetGrid); + inspectorbody.call(presetGrid, true); }); function changeMessage(msg) { message.text(msg);} @@ -58,32 +52,7 @@ iD.ui.Inspector = function() { selection.call(iD.ui.Toggle(true)); } - function drawButtons(selection) { - var entity = selection.datum(); - - var inspectorButton = selection.append('button') - .attr('class', 'apply action') - .on('click', apply); - - inspectorButton.append('span') - .attr('class','label') - .text(t('inspector.okay')); - - var minorButtons = selection.append('div') - .attr('class','minor-buttons fl'); - - minorButtons.append('a') - .attr('href', 'http://www.openstreetmap.org/browse/' + entity.type + '/' + entity.osmId()) - .attr('target', '_blank') - .text(t('inspector.view_on_osm')); - } - - function apply(entity) { - event.changeTags(entity, inspector.tags()); - event.close(entity); - } - - inspector.tags = function(tags) { + inspector.tags = function() { if (!arguments.length) { return tagEditor.tags(); } else { @@ -102,10 +71,5 @@ iD.ui.Inspector = function() { return inspector; }; - inspector.context = function(_) { - context = _; - return inspector; - }; - return d3.rebind(inspector, event, 'on'); }; diff --git a/js/id/ui/key_reference.js b/js/id/ui/key_reference.js index 1c1515150..c5e2b206f 100644 --- a/js/id/ui/key_reference.js +++ b/js/id/ui/key_reference.js @@ -27,7 +27,7 @@ iD.ui.keyReference = function(selection) { .append('tr'); var cols = rows.selectAll('td') - .data(function(d, i) { + .data(function(d) { return [d.value, d.description || "", d.count]; }) .enter() diff --git a/js/id/ui/lasso.js b/js/id/ui/lasso.js index 3cfe02ac1..1af0e31f8 100644 --- a/js/id/ui/lasso.js +++ b/js/id/ui/lasso.js @@ -1,7 +1,6 @@ iD.ui.Lasso = function() { - var center, box, - group, + var box, group, a = [0, 0], b = [0, 0]; @@ -50,7 +49,7 @@ iD.ui.Lasso = function() { return lasso; }; - lasso.close = function(selection) { + lasso.close = function() { if (group) { group.call(iD.ui.Toggle(false, function() { d3.select(this).remove(); diff --git a/js/id/ui/layerswitcher.js b/js/id/ui/layerswitcher.js index e0c66d9fb..5c6c57ff4 100644 --- a/js/id/ui/layerswitcher.js +++ b/js/id/ui/layerswitcher.js @@ -18,20 +18,21 @@ iD.ui.LayerSwitcher = function(context) { .append('div').attr('class', 'content fillD map-overlay hide'), shown = false; + var tooltip = bootstrap.tooltip().placement('right'); + var button = selection .append('button') .attr('tabindex', -1) .attr('class', 'fillD') .attr('title', t('layerswitcher.description')) .on('click.layerswitcher-toggle', toggle) - .call(bootstrap.tooltip() - .placement('right')); + .call(tooltip); button.append('span') .attr('class', 'layers icon'); function hide() { setVisible(false); } - function toggle() { setVisible(content.classed('hide')); } + function toggle() { tooltip.hide(button); setVisible(content.classed('hide')); } function setVisible(show) { if (show !== shown) { diff --git a/js/id/ui/preset.js b/js/id/ui/preset.js index a1553a38c..50205f1fe 100644 --- a/js/id/ui/preset.js +++ b/js/id/ui/preset.js @@ -1,7 +1,6 @@ -iD.ui.preset = function() { - var event = d3.dispatch('change'), +iD.ui.preset = function(context) { + var event = d3.dispatch('change', 'setTags'), taginfo = iD.taginfo(), - context, entity, type, hidden, @@ -13,7 +12,11 @@ iD.ui.preset = function() { var tags = _.clone(preset.match.tags); sections.selectAll('input,select') .each(function(d) { - tags[d.key] = this.value; + if (d && d.key) { + tags[d.key] = d.type === 'combo' || d.type === 'select' ? + this.value.replace(' ', '_') : + this.value; + } }); return tags; } @@ -22,8 +25,15 @@ iD.ui.preset = function() { if (!sections) return; sections.selectAll('input,select') .each(function(d) { - this.value = tags[d.key] || ''; + if (d && d.key) { + this.value = tags[d.key] || ''; + if (d.type === 'combo' || d.type === 'select') { + this.value = this.value.replace('_', ' '); + } + } }); + + event.setTags(); } function clean(o) { @@ -66,20 +76,25 @@ iD.ui.preset = function() { .attr('id', 'input-' + d.key) .attr('placeholder', 'http://example.com/'); break; - case 'check': - i = this.append('input') - .attr('type', 'checkbox') - .attr('id', 'input-' + d.key); - break; case 'select': wrap = this.append('span').attr('class', 'input-wrap-position'), i = wrap.append('input').attr('type', 'text'); - wrap.call(d3.combobox().data(d.options.map(function(d) { - return { - title: d, - value: d - }; - }))); + + if (d.options.length <= 5) { + var select = d3.rowselect() + .data(d.options) + .on('change', key); + i.datum(d); + wrap.call(select); + event.on('setTags.' + d.key, select.update); + + } else { + wrap.call(d3.combobox().data(d.options.map(function(d) { + var o = {}; + o.title = o.value = d.replace('_', ' '); + return o; + }))); + } break; case 'combo': var combobox = d3.combobox(); @@ -90,7 +105,7 @@ iD.ui.preset = function() { key: d.key }, function(err, data) { if (!err) combobox.data(data.map(function(d) { - d.title = d.value; + d.title = d.value = d.value.replace('_', ' '); return d; })); }); @@ -130,8 +145,7 @@ iD.ui.preset = function() { if (d.type === 'address') { wrap.append('div') .attr('class', 'col9 preset-input', d) - .call(iD.ui.preset.address() - .context(context) + .call(iD.ui.preset.address(context) .on('change', key) .entity(entity)); } @@ -157,12 +171,6 @@ iD.ui.preset = function() { return clean(getTags()); }; - presets.context = function(_) { - if (!arguments.length) return context; - context = _; - return presets; - }; - presets.entity = function(_) { if (!arguments.length) return entity; entity = _; diff --git a/js/id/ui/address.js b/js/id/ui/preset/address.js similarity index 84% rename from js/id/ui/address.js rename to js/id/ui/preset/address.js index 4164330e9..237694e38 100644 --- a/js/id/ui/address.js +++ b/js/id/ui/preset/address.js @@ -1,14 +1,13 @@ -iD.ui.preset.address = function() { +iD.ui.preset.address = function(context) { var event = d3.dispatch('change'), - context, entity; function getStreets() { - var l = entity.loc || context.entity(entity.nodes[0]).loc, + var extent = entity.extent(context.graph()), + l = extent.center(), dist = iD.geo.metresToCoordinates(l, [200, 200]), - extent = entity.extent(context.graph()), box = iD.geo.Extent( [extent[0][0] - dist[0], extent[0][1] - dist[1]], [extent[1][0] + dist[0], extent[1][1] + dist[1]]); @@ -56,12 +55,12 @@ iD.ui.preset.address = function() { var streetwrap = selection.append('span') .attr('class', 'input-wrap-position') - .datum({ 'key': 'addr:streetname' }); + .datum({ 'key': 'addr:street' }); streetwrap.append('input') .property('type', 'text') .attr('placeholder', 'Oak Street') - .attr('class', 'addr-streetname') + .attr('class', 'addr-street') .on('blur', change) .on('change', change); @@ -74,11 +73,5 @@ iD.ui.preset.address = function() { return address; }; - address.context = function(_) { - if (!arguments.length) return context; - context = _; - return address; - }; - return d3.rebind(address, event, 'on'); }; diff --git a/js/id/ui/presetgrid.js b/js/id/ui/presetgrid.js index 3c577df53..bc8f3dfd6 100644 --- a/js/id/ui/presetgrid.js +++ b/js/id/ui/presetgrid.js @@ -1,15 +1,15 @@ -iD.ui.PresetGrid = function() { +iD.ui.PresetGrid = function(context) { var event = d3.dispatch('choose', 'message'), entity, - context, - presetData; + presetData, + taginfo = iD.taginfo(); - function presetgrid(selection) { + function presetgrid(selection, preset) { selection.html(''); var viable = presetData.match(entity); - event.message('What kind of ' + entity.geometry(context.graph()) + ' are you adding?'); + event.message(t('inspector.choose')); var searchwrap = selection.append('div') .attr('class', 'preset-grid-search-wrap inspector-inner'); @@ -24,8 +24,7 @@ iD.ui.PresetGrid = function() { .on('keyup', function() { // enter if (d3.event.keyCode === 13) { - var chosen = grid.selectAll('.grid-entry:first-child').datum(); - if (chosen) event.choose(chosen); + choose(grid.selectAll('.grid-entry:first-child').datum()); } else { var value = search.property('value'), presets = filter(value); @@ -36,6 +35,11 @@ iD.ui.PresetGrid = function() { }); search.node().focus(); + if (preset) { + selection.append('div') + .attr('class', 'inspector-actions pad1 fillD col12') + .call(drawButtons); + } function filter(value) { if (!value) return presetData.defaults(entity); @@ -56,54 +60,128 @@ iD.ui.PresetGrid = function() { return iD.util.editDistance(value, a.name) - iD.util.editDistance(value, b.name); }).filter(function(d) { - return iD.util.editDistance(value, d.name) - d.name.length + value.length < 3; + return iD.util.editDistance(value, d.name) - d.name.length + value.length < 3 || + d.name === 'other'; }); } + + function choose(d) { + // Category + if (d.members) { + search.property('value', ''); + viable = presetData.categories(d.name); + drawGrid(selection, viable); + + // Preset + } else { + event.choose(d); + } + } + + function name(d) { return d.name; } + + function drawGrid(selection, presets) { + + var entries = selection + .selectAll('button.grid-entry') + .data(presets.slice(0, 12), name); + + var entered = entries.enter() + .append('button') + .attr('class', 'grid-entry col3') + .on('click', choose); + + entered.append('div') + .attr('class', function(d) { + var s = 'preset-icon-fill ' + entity.geometry(context.graph()); + if (d.members) { + s += 'category'; + } else { + for (var i in d.match.tags) { + s += ' tag-' + i + ' tag-' + i + '-' + d.match.tags[i]; + } + } + return s; + }); + + entered.append('div') + .attr('class', function(d) { return 'preset-' + d.icon + ' icon'; }); + + var presetinspect; + + entered.append('span').attr('class','label').text(name); + + entered.append('div') + .attr('tabindex', -1) + .attr('class', 'preset-help') + .on('click', function(d) { + + // Display description box inline + + d3.event.stopPropagation(); + + var entry = this.parentNode, + index, + entries = selection.selectAll('button.grid-entry'); + + if (presetinspect && presetinspect.remove().datum() === d) { + presetinspect = null; + return; + } + + entries.each(function(d, i) { + if (this === entry) index = i; + }); + + var selector = '.grid-entry:nth-child(' + (Math.floor(index/4) * 4 + 5 ) + ')'; + + presetinspect = selection.insert('div', selector) + .attr('class', 'preset-inspect col12') + .datum(d); + + presetinspect.append('h2').text(d.title || d.name); + + var description = presetinspect.append('p'); + var link = presetinspect.append('a'); + + var params = {}, + locale = iD.detect().locale.split('-')[0] || 'en'; + + params.key = Object.keys(d.match.tags)[0]; + if (d.match.tags[params.key] !== '*') { + params.value = d.match.tags[params.key]; + } + + taginfo.docs(params, function(err, data) { + var doc = _.find(data, function(d) { return d.lang === locale; }) || + _.find(data, function(d) { return d.lang === 'en'; }); + description.text(doc.description); + link.attr('href', 'http://wiki.openstreetmap.org/wiki/' + encodeURIComponent(doc.title)); + link.text(doc.title); + }); + }) + .append('span') + .attr('class', 'icon inspect'); + + entries.exit().remove(); + entries.order(); + } } - function name(d) { return d.name; } + function cancel() { + event.choose(); + } - function drawGrid(selection, presets) { + function drawButtons(selection) { - var entries = selection - .selectAll('button.grid-entry') - .data(presets.slice(0, 12), name); + var inspectorButton = selection.append('button') + .attr('class', 'apply action') + .on('click', cancel); - var entered = entries.enter() - .append('button') - .attr('class', 'grid-entry col3') - .on('click', function(d) { - // Category - if (d.members) { - drawGrid(selection, presetData.categories(d.name)); - - // Preset - } else { - event.choose(d); - } - }); - - entered.append('div') - .attr('class', function(d) { - var s = 'preset-icon-fill ' + entity.geometry(context.graph()); - if (d.members) { - s += 'category'; - } else { - for (var i in d.match.tags) { - s += ' tag-' + i + ' tag-' + i + '-' + d.match.tags[i]; - } - } - return s; - }); - - entered.append('div') - .attr('class', function(d) { return 'preset-' + d.icon + ' icon'; }); - - entered.append('span').attr('class','label').text(name); - - entries.exit().remove(); - entries.order(); + inspectorButton.append('span') + .attr('class','label') + .text(t('commit.cancel')); } presetgrid.presetData = function(_) { @@ -112,19 +190,11 @@ iD.ui.PresetGrid = function() { return presetgrid; }; - presetgrid.context = function(_) { - if (!arguments.length) return context; - context = _; - return presetgrid; - }; - presetgrid.entity = function(_) { if (!arguments.length) return entity; entity = _; return presetgrid; }; - - return d3.rebind(presetgrid, event, 'on'); }; diff --git a/js/id/ui/radial_menu.js b/js/id/ui/radial_menu.js index b9757d6ac..eb2cf1974 100644 --- a/js/id/ui/radial_menu.js +++ b/js/id/ui/radial_menu.js @@ -6,6 +6,8 @@ iD.ui.RadialMenu = function(operations) { if (!operations.length) return; + selection.node().focus(); + function click(operation) { d3.event.stopPropagation(); operation(); diff --git a/js/id/ui/splash.js b/js/id/ui/splash.js index c0e79261c..271278940 100644 --- a/js/id/ui/splash.js +++ b/js/id/ui/splash.js @@ -28,5 +28,5 @@ iD.ui.Splash = function(context) { website: 'ideditor.com', github: 'github.com' })); - } + }; }; diff --git a/js/id/ui/success.js b/js/id/ui/success.js index 17a577fb3..4d4799ffd 100644 --- a/js/id/ui/success.js +++ b/js/id/ui/success.js @@ -19,7 +19,7 @@ iD.ui.Success = function(connection) { connection.changesetUrl(changeset.id); header.append('a') - .attr('href', function(d) { + .attr('href', function() { return connection.changesetUrl(changeset.id); }) .attr('target', '_blank') @@ -28,7 +28,7 @@ iD.ui.Success = function(connection) { header.append('a') .attr('target', '_blank') - .attr('href', function(d) { + .attr('href', function() { return 'https://twitter.com/intent/tweet?source=webclient&text=' + encodeURIComponent(message); }) diff --git a/js/id/ui/tageditor.js b/js/id/ui/tageditor.js index 3b5dba71c..639e4434e 100644 --- a/js/id/ui/tageditor.js +++ b/js/id/ui/tageditor.js @@ -1,20 +1,18 @@ -iD.ui.TagEditor = function() { - var event = d3.dispatch('changeTags', 'choose', 'close', 'change', 'message'), - taginfo = iD.taginfo(), +iD.ui.TagEditor = function(context) { + var event = d3.dispatch('changeTags', 'choose', 'close', 'message'), presetData = iD.presetData(), - inspectorbody, entity, tags, name, presetMatch, + selection_, presetUI, - presetGrid, - tagList, - context; + tagList; function tageditor(selection, preset) { entity = selection.datum(); + selection_ = selection; var type = entity.type === 'node' ? entity.type : entity.geometry(); // preset was explicitly chosen @@ -60,44 +58,44 @@ iD.ui.TagEditor = function() { typewrap.append('h4').text('Type'); - var typelabel = typewrap.append('button') + var typebutton = typewrap.append('button') .attr('class','col12') .on('click', function() { event.choose(); }); - typelabel.append('div') + typebutton.append('div') .attr('class', 'icon icon-pre-text' + (presetMatch ? ' preset-' + presetMatch.icon : '')); + typebutton.node().focus(); + var namewrap = headerwrap.append('div') - .attr('class', 'name col9 inspector-inner'); + .attr('class', 'name col9 inspector-inner'); - typelabel.append('span') - .attr('class','label') - .text(presetMatch ? presetMatch.name : 'Other'); + typebutton.append('span') + .attr('class','label') + .text(presetMatch.name); - namewrap.append('h4').text('Name'); + namewrap.append('h4').text(t('inspector.name')); name = namewrap.append('input') .attr('placeholder', 'unknown') .attr('class', 'major') .attr('type', 'text') - .property('value', entity.tags.name || 'this') + .property('value', entity.tags.name || '') .on('blur', function() { - event.change(); + event.changeTags(); }); - presetUI = iD.ui.preset() - .context(context) + presetUI = iD.ui.preset(context) .entity(entity) - .on('change', function(tags) { - event.change(tags); + .on('change', function() { + event.changeTags(); }); - tagList = iD.ui.Taglist() - .context(context) - .on('change', function(tags) { - event.change(tags); + tagList = iD.ui.Taglist(context) + .on('change', function() { + event.changeTags(); }); var tageditorpreset = editorwrap.append('div') @@ -105,26 +103,45 @@ iD.ui.TagEditor = function() { if (presetMatch) { tageditorpreset.call(presetUI - .preset(presetMatch)); + .preset(presetMatch)); } - event.message('Edit ' + (presetMatch && presetMatch.name || '')); + event.message(t('inspector.editing', { type: presetMatch.name })); - var taglistwrap = editorwrap.append('div') - .attr('class','inspector-inner col12 fillL2').call(tagList); + editorwrap.append('div') + .attr('class','inspector-inner col12 fillL2').call(tagList, presetMatch.name === 'other'); + + selection.append('div') + .attr('class', 'inspector-actions pad1 fillD col12') + .call(drawButtons); tageditor.tags(tags); - event.change(tags); + + event.changeTags(); } - function drawHead(selection) { - var h2 = selection.append('h2'); + function apply() { + event.changeTags(); + event.close(); + } - h2.append('span') - .attr('class', 'icon big icon-pre-text big-' + entity.geometry(context.graph())); + function drawButtons(selection) { - h2.append('span') - .text(entity.friendlyName()); + var inspectorButton = selection.append('button') + .attr('class', 'apply action') + .on('click', apply); + + inspectorButton.append('span') + .attr('class','label') + .text(t('inspector.okay')); + + var minorButtons = selection.append('div') + .attr('class','minor-buttons fl'); + + minorButtons.append('a') + .attr('href', 'http://www.openstreetmap.org/browse/' + entity.type + '/' + entity.osmId()) + .attr('target', '_blank') + .text(t('inspector.view_on_osm')); } tageditor.tags = function(newtags) { @@ -135,6 +152,13 @@ iD.ui.TagEditor = function() { } else { tags = _.clone(newtags); if (presetUI && tagList) { + + // change preset if necessary (undos/redos) + var newmatch = presetData.matchTags(entity.update({ tags: tags })); + if (newmatch !== presetMatch) { + return tageditor(selection_, newmatch); + } + name.property('value', tags.name || ''); presetUI.change(tags); tagList.tags(_.omit(tags, _.keys(presetUI.tags() || {}).concat(['name']))); @@ -148,10 +172,5 @@ iD.ui.TagEditor = function() { return tageditor; }; - tageditor.context = function(_) { - context = _; - return tageditor; - }; - return d3.rebind(tageditor, event, 'on'); }; diff --git a/js/id/ui/taglist.js b/js/id/ui/taglist.js index 9adcab563..2ac6dc9af 100644 --- a/js/id/ui/taglist.js +++ b/js/id/ui/taglist.js @@ -1,24 +1,25 @@ -iD.ui.Taglist = function() { +iD.ui.Taglist = function(context) { var event = d3.dispatch('change'), taginfo = iD.taginfo(), initial = false, - list, - context; + collapsebutton, + list; - function taglist(selection) { + function taglist(selection, expanded) { - var collapsebutton = selection.append('a') + collapsebutton = selection.append('a') .attr('href','#') .attr('class','hide-toggle') - .text('Additional tags') + .text(t('inspector.additional')) .on('click', function() { collapsebutton.classed('expanded', wrap.classed('hide')); wrap.call(iD.ui.Toggle(wrap.classed('hide'))); selection.node().parentNode.scrollTop += 200; - }); + }) + .classed('expanded', expanded); var wrap = selection.append('div') - .attr('class', 'hide'); + .classed('hide', !expanded); list = wrap.append('ul') .attr('class', 'tag-list'); @@ -42,6 +43,8 @@ iD.ui.Taglist = function() { function drawTags(tags) { var entity = list.datum(); + collapsebutton.text(t('inspector.additional') + ' (' + Object.keys(tags).length + ')'); + tags = d3.entries(tags); if (!tags.length) { @@ -212,26 +215,28 @@ iD.ui.Taglist = function() { var keyinput = key.select('input'); key.call(d3.combobox() - .fetcher(_.debounce(function(_, __, callback) { + .fetcher(function(_, __, callback) { taginfo.keys({ + debounce: true, geometry: geometry, query: keyinput.property('value') }, function(err, data) { if (!err) callback(sort(keyinput.property('value'), data)); }); - }, 500))); + })); var valueinput = value.select('input'); value.call(d3.combobox() - .fetcher(_.debounce(function(_, __, callback) { + .fetcher(function(_, __, callback) { taginfo.values({ + debounce: true, key: keyinput.property('value'), geometry: geometry, query: valueinput.property('value') }, function(err, data) { if (!err) callback(sort(valueinput.property('value'), data)); }); - }, 500))); + })); } function focusNewKey() { @@ -265,10 +270,5 @@ iD.ui.Taglist = function() { } }; - taglist.context = function(_) { - context = _; - return taglist; - }; - return d3.rebind(taglist, event, 'on'); }; diff --git a/js/lib/d3.tail.js b/js/id/ui/tail.js similarity index 79% rename from js/lib/d3.tail.js rename to js/id/ui/tail.js index 08381c3f9..6ea83ed87 100644 --- a/js/lib/d3.tail.js +++ b/js/id/ui/tail.js @@ -1,21 +1,24 @@ -d3.tail = function() { +iD.ui.Tail = function() { var text = false, container, + inner, xmargin = 25, tooltip_size = [0, 0], selection_size = [0, 0], transformProp = iD.util.prefixCSSProperty('Transform'); - var tail = function(selection) { - + function tail(selection) { d3.select(window).on('resize.tail-size', function() { selection_size = selection.size(); }); function setup() { - container = d3.select(document.body) - .append('div').attr('class', 'tail tooltip-inner'); + .append('div') + .style('display', 'none') + .attr('class', 'tail tooltip-inner'); + + inner = container.append('div'); selection .on('mousemove.tail', mousemove) @@ -26,11 +29,16 @@ d3.tail = function() { .on('mousemove.tail', mousemove); selection_size = selection.size(); + } + function show() { + container.style('display', 'block'); + tooltip_size = container.size(); } function mousemove() { if (text === false) return; + if (container.style('display') === 'none') show(); var xoffset = ((d3.event.clientX + tooltip_size[0] + xmargin) > selection_size[0]) ? -tooltip_size[0] - xmargin : xmargin; container.classed('left', xoffset > 0); @@ -46,12 +54,11 @@ d3.tail = function() { function mouseover() { if (d3.event.relatedTarget !== container.node() && - text !== false) container.style('display', 'block'); + text !== false) show(); } if (!container) setup(); - - }; + } tail.text = function(_) { if (!arguments.length) return text; @@ -59,11 +66,9 @@ d3.tail = function() { text = _; container.style('display', 'none'); return tail; - } else if (container.style('display') == 'none') { - container.style('display', 'block'); } text = _; - container.text(text); + inner.text(text); tooltip_size = container.size(); return tail; }; diff --git a/js/id/ui/zoom.js b/js/id/ui/zoom.js index 823bc120f..5412a76ce 100644 --- a/js/id/ui/zoom.js +++ b/js/id/ui/zoom.js @@ -36,5 +36,5 @@ iD.ui.Zoom = function(context) { d3.select(document) .call(keybinding); - } + }; }; diff --git a/js/id/validate.js b/js/id/validate.js index dacff7889..30568f652 100644 --- a/js/id/validate.js +++ b/js/id/validate.js @@ -15,6 +15,12 @@ iD.validate = function(changes, graph) { if (tags.building && tags.building === 'yes') return 'building=yes'; } + if (changes.deleted.length > 100) { + warnings.push({ + message: t('validations.many_deletions', { n: changes.deleted.length }) + }); + } + for (var i = 0; i < changes.created.length; i++) { change = changes.created[i]; diff --git a/js/lib/d3.combobox.js b/js/lib/d3.combobox.js index 815256045..1e2ae0a28 100644 --- a/js/lib/d3.combobox.js +++ b/js/lib/d3.combobox.js @@ -15,15 +15,6 @@ d3.combobox = function() { var idx = -1; input = selection.select('input'); - container = d3.select(document.body) - .insert('div', ':first-child') - .attr('class', 'combobox') - .style({ - position: 'absolute', - display: 'none', - left: '0px' - }); - selection.append('a', selection.select('input')) .attr('class', 'combobox-carat') .on('mousedown', stop) @@ -64,14 +55,26 @@ d3.combobox = function() { } function show() { - container.style('display', 'block'); - shown = true; + if (!shown) { + container = d3.select(document.body) + .insert('div', ':first-child') + .attr('class', 'combobox') + .style({ + position: 'absolute', + display: 'block', + left: '0px' + }); + + shown = true; + } } function hide() { - idx = -1; - container.style('display', 'none'); - shown = false; + if (shown) { + idx = -1; + container.remove(); + shown = false; + } } function slowHide() { @@ -189,7 +192,7 @@ d3.combobox = function() { if (data.length && document.activeElement === input.node()) show(); - else hide(); + else return hide(); autocomplete(e, data); @@ -230,6 +233,8 @@ d3.combobox = function() { input.node().focus(); update(''); + if (!container) return; + var entries = container.selectAll('a'), height = container.node().scrollHeight / entries[0].length, w = d3.select(window); diff --git a/js/lib/d3.geo.tile.js b/js/lib/d3.geo.tile.js index 7e47134a6..2761deb61 100644 --- a/js/lib/d3.geo.tile.js +++ b/js/lib/d3.geo.tile.js @@ -2,7 +2,8 @@ d3.geo.tile = function() { var size = [960, 500], scale = 256, scaleExtent = [0, 20], - translate = [size[0] / 2, size[1] / 2]; + translate = [size[0] / 2, size[1] / 2], + zoomDelta = 0; function bound(_) { return Math.min(scaleExtent[1], Math.max(scaleExtent[0], _)); @@ -10,7 +11,7 @@ d3.geo.tile = function() { function tile() { var z = Math.max(Math.log(scale) / Math.LN2 - 8, 0), - z0 = bound(z | 0), + z0 = bound(Math.round(z + zoomDelta)), k = Math.pow(2, z - z0 + 8), origin = [(translate[0] - scale / 2) / k, (translate[1] - scale / 2) / k], tiles = [], @@ -53,5 +54,11 @@ d3.geo.tile = function() { return tile; }; + tile.zoomDelta = function(_) { + if (!arguments.length) return zoomDelta; + zoomDelta = +_; + return tile; + }; + return tile; }; diff --git a/js/lib/d3.rowselect.js b/js/lib/d3.rowselect.js new file mode 100644 index 000000000..478e104fa --- /dev/null +++ b/js/lib/d3.rowselect.js @@ -0,0 +1,63 @@ +d3.rowselect = function() { + + var input, data, wrap, + event = d3.dispatch('change'); + + var select = function(selection) { + + input = selection.select('input') + .style('display', 'none'); + + wrap = selection.append('div') + .attr('class', 'rowselect'); + + var labels = wrap.selectAll('div') + .data(data) + .enter() + .append('div') + .style('display', 'inline-block') + .style('width', ~~(100 / data.length) + '%') + .attr('class', 'item') + .append('label') + .on('click', function() { + var checkbox = d3.select(this).select('input'), + val = !!checkbox.property('checked'); + wrap.selectAll('input').property('checked', false); + checkbox.property('checked', val); + input.property('value', val ? checkbox.datum() : ''); + + event.change(); + d3.event.stopPropagation(); + }); + + var value = input.property('value'); + + labels.append('div') + .append('input') + .attr('type', 'checkbox'); + + labels.append('span').text(function(d) { return d; }); + + input.on('change.select', update); + + }; + + function update() { + var value = input.property('value'); + + wrap.selectAll('input') + .property('checked', function(d) { + return d === value; + }); + } + + select.data = function(_) { + if (!arguments.length) return data; + data = _; + return select; + }; + + select.update = update; + + return d3.rebind(select, event, 'on'); +}; diff --git a/js/lib/d3.v3.js b/js/lib/d3.v3.js index 818bab9dc..77d662c10 100644 --- a/js/lib/d3.v3.js +++ b/js/lib/d3.v3.js @@ -1,26 +1,25 @@ -(function() { - var d3_format_decimalPoint = ".", d3_format_thousandsSeparator = ",", d3_format_grouping = [ 3, 3 ]; - if (!Date.now) Date.now = function() { - return +new Date(); - }; - try { - document.createElement("div").style.setProperty("opacity", 0, ""); - } catch (error) { - var d3_style_prototype = CSSStyleDeclaration.prototype, d3_style_setProperty = d3_style_prototype.setProperty; - d3_style_prototype.setProperty = function(name, value, priority) { - d3_style_setProperty.call(this, name, value + "", priority); - }; - } - d3 = { - version: "3.0.5" - }; - var π = Math.PI, ε = 1e-6, d3_radians = π / 180, d3_degrees = 180 / π; +d3 = function() { + var π = Math.PI, ε = 1e-6, d3 = { + version: "3.0.6" + }, d3_radians = π / 180, d3_degrees = 180 / π, d3_document = document, d3_window = window; function d3_target(d) { return d.target; } function d3_source(d) { return d.source; } + var d3_format_decimalPoint = ".", d3_format_thousandsSeparator = ",", d3_format_grouping = [ 3, 3 ]; + if (!Date.now) Date.now = function() { + return +new Date(); + }; + try { + d3_document.createElement("div").style.setProperty("opacity", 0, ""); + } catch (error) { + var d3_style_prototype = d3_window.CSSStyleDeclaration.prototype, d3_style_setProperty = d3_style_prototype.setProperty; + d3_style_prototype.setProperty = function(name, value, priority) { + d3_style_setProperty.call(this, name, value + "", priority); + }; + } function d3_class(ctor, properties) { try { for (var key in properties) { @@ -43,7 +42,7 @@ return Array.prototype.slice.call(pseudoarray); } try { - d3_array(document.documentElement.childNodes)[0].nodeType; + d3_array(d3_document.documentElement.childNodes)[0].nodeType; } catch (e) { d3_array = d3_arrayCopy; } @@ -202,13 +201,10 @@ return µ + σ * x * Math.sqrt(-2 * Math.log(r) / r); }; }, - logNormal: function(µ, σ) { - var n = arguments.length; - if (n < 2) σ = 1; - if (n < 1) µ = 0; - var random = d3.random.normal(); + logNormal: function() { + var random = d3.random.normal.apply(d3, arguments); return function() { - return Math.exp(µ + σ * random()); + return Math.exp(random()); }; }, irwinHall: function(m) { @@ -395,7 +391,7 @@ return n ? Math.round(x * (n = Math.pow(10, n))) / n : Math.round(x); }; d3.xhr = function(url, mimeType, callback) { - var xhr = {}, dispatch = d3.dispatch("progress", "load", "error"), headers = {}, response = d3_identity, request = new (window.XDomainRequest && /^(http(s)?:)?\/\//.test(url) ? XDomainRequest : XMLHttpRequest)(); + var xhr = {}, dispatch = d3.dispatch("progress", "load", "error"), headers = {}, response = d3_identity, request = new (d3_window.XDomainRequest && /^(http(s)?:)?\/\//.test(url) ? XDomainRequest : XMLHttpRequest)(); "onload" in request ? request.onload = request.onerror = respond : request.onreadystatechange = function() { request.readyState > 3 && respond(); }; @@ -474,8 +470,8 @@ return d3.xhr(url, "text/html", callback).response(d3_html); }; function d3_html(request) { - var range = document.createRange(); - range.selectNode(document.body); + var range = d3_document.createRange(); + range.selectNode(d3_document.body); return range.createContextualFragment(request.responseText); } d3.xml = function() { @@ -634,11 +630,11 @@ return x.toFixed(p); }, r: function(x, p) { - return d3.round(x, p = d3_format_precision(x, p)).toFixed(Math.max(0, Math.min(20, p))); + return (x = d3.round(x, d3_format_precision(x, p))).toFixed(Math.max(0, Math.min(20, d3_format_precision(x * (1 + 1e-15), p)))); } }); function d3_format_precision(x, p) { - return p - (x ? 1 + Math.floor(Math.log(x + Math.pow(10, 1 + Math.floor(Math.log(x) / Math.LN10) - p)) / Math.LN10) : 1); + return p - (x ? Math.ceil(Math.log(x) / Math.LN10) : 1); } function d3_format_typeDefault(x) { return x + ""; @@ -656,7 +652,7 @@ return t.reverse().join(d3_format_thousandsSeparator || "") + f; }; } - var d3_formatPrefixes = [ "y", "z", "a", "f", "p", "n", "μ", "m", "", "k", "M", "G", "T", "P", "E", "Z", "Y" ].map(d3_formatPrefix); + var d3_formatPrefixes = [ "y", "z", "a", "f", "p", "n", "µ", "m", "", "k", "M", "G", "T", "P", "E", "Z", "Y" ].map(d3_formatPrefix); d3.formatPrefix = function(value, precision) { var i = 0; if (value) { @@ -805,7 +801,7 @@ return dispatch; } d3.transform = function(string) { - var g = document.createElementNS(d3.ns.prefix.svg, "g"); + 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(); @@ -1440,7 +1436,7 @@ return n.querySelector(s); }, d3_selectAll = function(s, n) { return n.querySelectorAll(s); - }, d3_selectRoot = document.documentElement, d3_selectMatcher = d3_selectRoot.matchesSelector || d3_selectRoot.webkitMatchesSelector || d3_selectRoot.mozMatchesSelector || d3_selectRoot.msMatchesSelector || d3_selectRoot.oMatchesSelector, d3_selectMatches = function(n, s) { + }, d3_selectRoot = d3_document.documentElement, d3_selectMatcher = d3_selectRoot.matchesSelector || d3_selectRoot.webkitMatchesSelector || d3_selectRoot.mozMatchesSelector || d3_selectRoot.msMatchesSelector || d3_selectRoot.oMatchesSelector, d3_selectMatches = function(n, s) { return d3_selectMatcher.call(n, s); }; if (typeof Sizzle === "function") { @@ -1592,7 +1588,7 @@ for (priority in name) this.each(d3_selection_style(priority, name[priority], value)); return this; } - if (n < 2) return getComputedStyle(this.node(), null).getPropertyValue(name); + if (n < 2) return d3_window.getComputedStyle(this.node(), null).getPropertyValue(name); priority = ""; } return this.each(d3_selection_style(name, value, priority)); @@ -1654,20 +1650,20 @@ d3_selectionPrototype.append = function(name) { name = d3.ns.qualify(name); function append() { - return this.appendChild(document.createElementNS(this.namespaceURI, name)); + return this.appendChild(d3_document.createElementNS(this.namespaceURI, name)); } function appendNS() { - return this.appendChild(document.createElementNS(name.space, name.local)); + return this.appendChild(d3_document.createElementNS(name.space, name.local)); } return this.select(name.local ? appendNS : append); }; d3_selectionPrototype.insert = function(name, before) { name = d3.ns.qualify(name); function insert() { - return this.insertBefore(document.createElementNS(this.namespaceURI, name), d3_select(before, this)); + return this.insertBefore(d3_document.createElementNS(this.namespaceURI, name), d3_select(before, this)); } function insertNS() { - return this.insertBefore(document.createElementNS(name.space, name.local), d3_select(before, this)); + return this.insertBefore(d3_document.createElementNS(name.space, name.local), d3_select(before, this)); } return this.select(name.local ? insertNS : insert); }; @@ -1805,7 +1801,7 @@ function d3_selection_sortComparator(comparator) { if (!arguments.length) comparator = d3.ascending; return function(a, b) { - return comparator(a && a.__data__, b && b.__data__); + return !a - !b || comparator(a.__data__, b.__data__); }; } d3_selectionPrototype.on = function(type, listener, capture) { @@ -1891,7 +1887,7 @@ } return d3_transition(subgroups, id); }; - var d3_selectionRoot = d3_selection([ [ document ] ]); + var d3_selectionRoot = d3_selection([ [ d3_document ] ]); d3_selectionRoot[0].parentNode = d3_selectRoot; d3.select = function(selector) { return typeof selector === "string" ? d3_selectionRoot.select(selector) : d3_selection([ [ selector ] ]); @@ -2103,7 +2099,7 @@ } return d3_transition_tween(this, "style." + name, value, function(b) { function styleString() { - var a = getComputedStyle(this, null).getPropertyValue(name), i; + 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); }); @@ -2114,7 +2110,7 @@ d3_transitionPrototype.styleTween = function(name, tween, priority) { if (arguments.length < 3) priority = ""; return this.tween("style." + name, function(d, i) { - var f = tween.call(this, d, i, getComputedStyle(this, null).getPropertyValue(name)); + 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); }; @@ -2272,19 +2268,19 @@ } return then; } - var d3_timer_frame = window.requestAnimationFrame || window.webkitRequestAnimationFrame || window.mozRequestAnimationFrame || window.oRequestAnimationFrame || window.msRequestAnimationFrame || function(callback) { + var d3_timer_frame = d3_window.requestAnimationFrame || d3_window.webkitRequestAnimationFrame || d3_window.mozRequestAnimationFrame || d3_window.oRequestAnimationFrame || d3_window.msRequestAnimationFrame || function(callback) { setTimeout(callback, 17); }; d3.mouse = function(container) { return d3_mousePoint(container, d3_eventSource()); }; - var d3_mouse_bug44083 = /WebKit/.test(navigator.userAgent) ? -1 : 0; + 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 && (window.scrollX || window.scrollY)) { - svg = d3.select(document.body).append("svg").style("position", "absolute").style("top", 0).style("left", 0); + 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(); @@ -3317,18 +3313,18 @@ d3.svg.symbolTypes = d3_svg_symbols.keys(); var d3_svg_symbolSqrt3 = Math.sqrt(3), d3_svg_symbolTan30 = Math.tan(30 * d3_radians); d3.svg.axis = function() { - var scale = d3.scale.linear(), orient = "bottom", tickMajorSize = 6, tickMinorSize = 6, tickEndSize = 6, tickPadding = 3, tickArguments_ = [ 10 ], tickValues = null, tickFormat_, tickSubdivide = 0; + var scale = d3.scale.linear(), orient = d3_svg_axisDefaultOrient, tickMajorSize = 6, tickMinorSize = 6, tickEndSize = 6, tickPadding = 3, tickArguments_ = [ 10 ], tickValues = null, tickFormat_, tickSubdivide = 0; function axis(g) { g.each(function() { var g = d3.select(this); var ticks = tickValues == null ? scale.ticks ? scale.ticks.apply(scale, tickArguments_) : scale.domain() : tickValues, tickFormat = tickFormat_ == null ? scale.tickFormat ? scale.tickFormat.apply(scale, tickArguments_) : String : tickFormat_; - var subticks = d3_svg_axisSubdivide(scale, ticks, tickSubdivide), subtick = g.selectAll(".minor").data(subticks, String), subtickEnter = subtick.enter().insert("line", "g").attr("class", "tick minor").style("opacity", 1e-6), subtickExit = d3.transition(subtick.exit()).style("opacity", 1e-6).remove(), subtickUpdate = d3.transition(subtick).style("opacity", 1); - var tick = g.selectAll("g").data(ticks, String), tickEnter = tick.enter().insert("g", "path").style("opacity", 1e-6), tickExit = d3.transition(tick.exit()).style("opacity", 1e-6).remove(), tickUpdate = d3.transition(tick).style("opacity", 1), tickTransform; - var range = d3_scaleRange(scale), path = g.selectAll(".domain").data([ 0 ]), pathUpdate = d3.transition(path); + var subticks = d3_svg_axisSubdivide(scale, ticks, tickSubdivide), subtick = g.selectAll(".tick.minor").data(subticks, String), subtickEnter = subtick.enter().insert("line", ".tick").attr("class", "tick minor").style("opacity", 1e-6), subtickExit = d3.transition(subtick.exit()).style("opacity", 1e-6).remove(), subtickUpdate = d3.transition(subtick).style("opacity", 1); + var tick = g.selectAll(".tick.major").data(ticks, String), tickEnter = tick.enter().insert("g", "path").attr("class", "tick major").style("opacity", 1e-6), tickExit = d3.transition(tick.exit()).style("opacity", 1e-6).remove(), tickUpdate = d3.transition(tick).style("opacity", 1), tickTransform; + var range = d3_scaleRange(scale), path = g.selectAll(".domain").data([ 0 ]), pathUpdate = (path.enter().append("path").attr("class", "domain"), + d3.transition(path)); var scale1 = scale.copy(), scale0 = this.__chart__ || scale1; this.__chart__ = scale1; - path.enter().append("path").attr("class", "domain"); - tickEnter.append("line").attr("class", "tick"); + tickEnter.append("line"); tickEnter.append("text"); var lineEnter = tickEnter.select("line"), lineUpdate = tickUpdate.select("line"), text = tick.select("text").text(tickFormat), textEnter = tickEnter.select("text"), textUpdate = tickUpdate.select("text"); switch (orient) { @@ -3411,7 +3407,7 @@ }; axis.orient = function(x) { if (!arguments.length) return orient; - orient = x; + orient = x in d3_svg_axisOrients ? x + "" : d3_svg_axisDefaultOrient; return axis; }; axis.ticks = function() { @@ -3449,6 +3445,12 @@ }; return axis; }; + var d3_svg_axisDefaultOrient = "bottom", d3_svg_axisOrients = { + top: 1, + right: 1, + bottom: 1, + left: 1 + }; function d3_svg_axisX(selection, x) { selection.attr("transform", function(d) { return "translate(" + x(d) + ",0)"; @@ -3523,7 +3525,7 @@ } function brushstart() { var target = this, eventTarget = d3.select(d3.event.target), event_ = event.of(target, arguments), g = d3.select(target), resizing = eventTarget.datum(), resizingX = !/^(n|s)$/.test(resizing) && x, resizingY = !/^(e|w)$/.test(resizing) && y, dragging = eventTarget.classed("extent"), center, origin = mouse(), offset; - var w = d3.select(window).on("mousemove.brush", brushmove).on("mouseup.brush", brushend).on("touchmove.brush", brushmove).on("touchend.brush", brushend).on("keydown.brush", keydown).on("keyup.brush", keyup); + var w = d3.select(d3_window).on("mousemove.brush", brushmove).on("mouseup.brush", brushend).on("touchmove.brush", brushmove).on("touchend.brush", brushend).on("keydown.brush", keydown).on("keyup.brush", keyup); if (dragging) { origin[0] = extent[0][0] - origin[0]; origin[1] = extent[0][1] - origin[1]; @@ -3710,7 +3712,7 @@ } function mousedown() { var target = this, event_ = event.of(target, arguments), eventTarget = d3.event.target, touchId = d3.event.touches ? d3.event.changedTouches[0].identifier : null, offset, origin_ = point(), moved = 0; - var w = d3.select(window).on(touchId != null ? "touchmove.drag-" + touchId : "mousemove.drag", dragmove).on(touchId != null ? "touchend.drag-" + touchId : "mouseup.drag", dragend, true); + var w = d3.select(d3_window).on(touchId != null ? "touchmove.drag-" + touchId : "mousemove.drag", dragmove).on(touchId != null ? "touchend.drag-" + touchId : "mouseup.drag", dragend, true); if (origin) { offset = origin.apply(target, arguments); offset = [ offset.x - origin_[0], offset.y - origin_[1] ]; @@ -3833,8 +3835,8 @@ }); } function mousedown() { - var target = this, event_ = event.of(target, arguments), eventTarget = d3.event.target, moved = 0, w = d3.select(window).on("mousemove.zoom", mousemove).on("mouseup.zoom", mouseup), l = location(d3.mouse(target)); - window.focus(); + var target = this, event_ = event.of(target, arguments), eventTarget = d3.event.target, moved = 0, w = d3.select(d3_window).on("mousemove.zoom", mousemove).on("mouseup.zoom", mouseup), l = location(d3.mouse(target)); + d3_window.focus(); d3_eventCancel(); function mousemove() { moved = 1; @@ -3906,1416 +3908,6 @@ }, "mousewheel") : (d3_behavior_zoomDelta = function() { return -d3.event.detail; }, "MozMousePixelScroll"); - d3.layout = {}; - d3.layout.bundle = function() { - return function(links) { - var paths = [], i = -1, n = links.length; - while (++i < n) paths.push(d3_layout_bundlePath(links[i])); - return paths; - }; - }; - function d3_layout_bundlePath(link) { - var start = link.source, end = link.target, lca = d3_layout_bundleLeastCommonAncestor(start, end), points = [ start ]; - while (start !== lca) { - start = start.parent; - points.push(start); - } - var k = points.length; - while (end !== lca) { - points.splice(k, 0, end); - end = end.parent; - } - return points; - } - function d3_layout_bundleAncestors(node) { - var ancestors = [], parent = node.parent; - while (parent != null) { - ancestors.push(node); - node = parent; - parent = parent.parent; - } - ancestors.push(node); - return ancestors; - } - function d3_layout_bundleLeastCommonAncestor(a, b) { - if (a === b) return a; - var aNodes = d3_layout_bundleAncestors(a), bNodes = d3_layout_bundleAncestors(b), aNode = aNodes.pop(), bNode = bNodes.pop(), sharedNode = null; - while (aNode === bNode) { - sharedNode = aNode; - aNode = aNodes.pop(); - bNode = bNodes.pop(); - } - return sharedNode; - } - d3.layout.chord = function() { - var chord = {}, chords, groups, matrix, n, padding = 0, sortGroups, sortSubgroups, sortChords; - function relayout() { - var subgroups = {}, groupSums = [], groupIndex = d3.range(n), subgroupIndex = [], k, x, x0, i, j; - chords = []; - groups = []; - k = 0, i = -1; - while (++i < n) { - x = 0, j = -1; - while (++j < n) { - x += matrix[i][j]; - } - groupSums.push(x); - subgroupIndex.push(d3.range(n)); - k += x; - } - if (sortGroups) { - groupIndex.sort(function(a, b) { - return sortGroups(groupSums[a], groupSums[b]); - }); - } - if (sortSubgroups) { - subgroupIndex.forEach(function(d, i) { - d.sort(function(a, b) { - return sortSubgroups(matrix[i][a], matrix[i][b]); - }); - }); - } - k = (2 * π - padding * n) / k; - x = 0, i = -1; - while (++i < n) { - x0 = x, j = -1; - while (++j < n) { - var di = groupIndex[i], dj = subgroupIndex[di][j], v = matrix[di][dj], a0 = x, a1 = x += v * k; - subgroups[di + "-" + dj] = { - index: di, - subindex: dj, - startAngle: a0, - endAngle: a1, - value: v - }; - } - groups[di] = { - index: di, - startAngle: x0, - endAngle: x, - value: (x - x0) / k - }; - x += padding; - } - i = -1; - while (++i < n) { - j = i - 1; - while (++j < n) { - var source = subgroups[i + "-" + j], target = subgroups[j + "-" + i]; - if (source.value || target.value) { - chords.push(source.value < target.value ? { - source: target, - target: source - } : { - source: source, - target: target - }); - } - } - } - if (sortChords) resort(); - } - function resort() { - chords.sort(function(a, b) { - return sortChords((a.source.value + a.target.value) / 2, (b.source.value + b.target.value) / 2); - }); - } - chord.matrix = function(x) { - if (!arguments.length) return matrix; - n = (matrix = x) && matrix.length; - chords = groups = null; - return chord; - }; - chord.padding = function(x) { - if (!arguments.length) return padding; - padding = x; - chords = groups = null; - return chord; - }; - chord.sortGroups = function(x) { - if (!arguments.length) return sortGroups; - sortGroups = x; - chords = groups = null; - return chord; - }; - chord.sortSubgroups = function(x) { - if (!arguments.length) return sortSubgroups; - sortSubgroups = x; - chords = null; - return chord; - }; - chord.sortChords = function(x) { - if (!arguments.length) return sortChords; - sortChords = x; - if (chords) resort(); - return chord; - }; - chord.chords = function() { - if (!chords) relayout(); - return chords; - }; - chord.groups = function() { - if (!groups) relayout(); - return groups; - }; - return chord; - }; - d3.layout.force = function() { - var force = {}, event = d3.dispatch("start", "tick", "end"), size = [ 1, 1 ], drag, alpha, friction = .9, linkDistance = d3_layout_forceLinkDistance, linkStrength = d3_layout_forceLinkStrength, charge = -30, gravity = .1, theta = .8, nodes = [], links = [], distances, strengths, charges; - function repulse(node) { - return function(quad, x1, _, x2) { - if (quad.point !== node) { - var dx = quad.cx - node.x, dy = quad.cy - node.y, dn = 1 / Math.sqrt(dx * dx + dy * dy); - if ((x2 - x1) * dn < theta) { - var k = quad.charge * dn * dn; - node.px -= dx * k; - node.py -= dy * k; - return true; - } - if (quad.point && isFinite(dn)) { - var k = quad.pointCharge * dn * dn; - node.px -= dx * k; - node.py -= dy * k; - } - } - return !quad.charge; - }; - } - force.tick = function() { - if ((alpha *= .99) < .005) { - event.end({ - type: "end", - alpha: alpha = 0 - }); - return true; - } - var n = nodes.length, m = links.length, q, i, o, s, t, l, k, x, y; - for (i = 0; i < m; ++i) { - o = links[i]; - s = o.source; - t = o.target; - x = t.x - s.x; - y = t.y - s.y; - if (l = x * x + y * y) { - l = alpha * strengths[i] * ((l = Math.sqrt(l)) - distances[i]) / l; - x *= l; - y *= l; - t.x -= x * (k = s.weight / (t.weight + s.weight)); - t.y -= y * k; - s.x += x * (k = 1 - k); - s.y += y * k; - } - } - if (k = alpha * gravity) { - x = size[0] / 2; - y = size[1] / 2; - i = -1; - if (k) while (++i < n) { - o = nodes[i]; - o.x += (x - o.x) * k; - o.y += (y - o.y) * k; - } - } - if (charge) { - d3_layout_forceAccumulate(q = d3.geom.quadtree(nodes), alpha, charges); - i = -1; - while (++i < n) { - if (!(o = nodes[i]).fixed) { - q.visit(repulse(o)); - } - } - } - i = -1; - while (++i < n) { - o = nodes[i]; - if (o.fixed) { - o.x = o.px; - o.y = o.py; - } else { - o.x -= (o.px - (o.px = o.x)) * friction; - o.y -= (o.py - (o.py = o.y)) * friction; - } - } - event.tick({ - type: "tick", - alpha: alpha - }); - }; - force.nodes = function(x) { - if (!arguments.length) return nodes; - nodes = x; - return force; - }; - force.links = function(x) { - if (!arguments.length) return links; - links = x; - return force; - }; - force.size = function(x) { - if (!arguments.length) return size; - size = x; - return force; - }; - force.linkDistance = function(x) { - if (!arguments.length) return linkDistance; - linkDistance = d3_functor(x); - return force; - }; - force.distance = force.linkDistance; - force.linkStrength = function(x) { - if (!arguments.length) return linkStrength; - linkStrength = d3_functor(x); - return force; - }; - force.friction = function(x) { - if (!arguments.length) return friction; - friction = x; - return force; - }; - force.charge = function(x) { - if (!arguments.length) return charge; - charge = typeof x === "function" ? x : +x; - return force; - }; - force.gravity = function(x) { - if (!arguments.length) return gravity; - gravity = x; - return force; - }; - force.theta = function(x) { - if (!arguments.length) return theta; - theta = x; - return force; - }; - force.alpha = function(x) { - if (!arguments.length) return alpha; - if (alpha) { - if (x > 0) alpha = x; else alpha = 0; - } else if (x > 0) { - event.start({ - type: "start", - alpha: alpha = x - }); - d3.timer(force.tick); - } - return force; - }; - force.start = function() { - var i, j, n = nodes.length, m = links.length, w = size[0], h = size[1], neighbors, o; - for (i = 0; i < n; ++i) { - (o = nodes[i]).index = i; - o.weight = 0; - } - distances = []; - strengths = []; - for (i = 0; i < m; ++i) { - o = links[i]; - if (typeof o.source == "number") o.source = nodes[o.source]; - if (typeof o.target == "number") o.target = nodes[o.target]; - distances[i] = linkDistance.call(this, o, i); - strengths[i] = linkStrength.call(this, o, i); - ++o.source.weight; - ++o.target.weight; - } - for (i = 0; i < n; ++i) { - o = nodes[i]; - if (isNaN(o.x)) o.x = position("x", w); - if (isNaN(o.y)) o.y = position("y", h); - if (isNaN(o.px)) o.px = o.x; - if (isNaN(o.py)) o.py = o.y; - } - charges = []; - if (typeof charge === "function") { - for (i = 0; i < n; ++i) { - charges[i] = +charge.call(this, nodes[i], i); - } - } else { - for (i = 0; i < n; ++i) { - charges[i] = charge; - } - } - function position(dimension, size) { - var neighbors = neighbor(i), j = -1, m = neighbors.length, x; - while (++j < m) if (!isNaN(x = neighbors[j][dimension])) return x; - return Math.random() * size; - } - function neighbor() { - if (!neighbors) { - neighbors = []; - for (j = 0; j < n; ++j) { - neighbors[j] = []; - } - for (j = 0; j < m; ++j) { - var o = links[j]; - neighbors[o.source.index].push(o.target); - neighbors[o.target.index].push(o.source); - } - } - return neighbors[i]; - } - return force.resume(); - }; - force.resume = function() { - return force.alpha(.1); - }; - force.stop = function() { - return force.alpha(0); - }; - force.drag = function() { - if (!drag) drag = d3.behavior.drag().origin(d3_identity).on("dragstart", d3_layout_forceDragstart).on("drag", dragmove).on("dragend", d3_layout_forceDragend); - this.on("mouseover.force", d3_layout_forceMouseover).on("mouseout.force", d3_layout_forceMouseout).call(drag); - }; - function dragmove(d) { - d.px = d3.event.x, d.py = d3.event.y; - force.resume(); - } - return d3.rebind(force, event, "on"); - }; - function d3_layout_forceDragstart(d) { - d.fixed |= 2; - } - function d3_layout_forceDragend(d) { - d.fixed &= 1; - } - function d3_layout_forceMouseover(d) { - d.fixed |= 4; - d.px = d.x, d.py = d.y; - } - function d3_layout_forceMouseout(d) { - d.fixed &= 3; - } - function d3_layout_forceAccumulate(quad, alpha, charges) { - var cx = 0, cy = 0; - quad.charge = 0; - if (!quad.leaf) { - var nodes = quad.nodes, n = nodes.length, i = -1, c; - while (++i < n) { - c = nodes[i]; - if (c == null) continue; - d3_layout_forceAccumulate(c, alpha, charges); - quad.charge += c.charge; - cx += c.charge * c.cx; - cy += c.charge * c.cy; - } - } - if (quad.point) { - if (!quad.leaf) { - quad.point.x += Math.random() - .5; - quad.point.y += Math.random() - .5; - } - var k = alpha * charges[quad.point.index]; - quad.charge += quad.pointCharge = k; - cx += k * quad.point.x; - cy += k * quad.point.y; - } - quad.cx = cx / quad.charge; - quad.cy = cy / quad.charge; - } - function d3_layout_forceLinkDistance() { - return 20; - } - function d3_layout_forceLinkStrength() { - return 1; - } - d3.layout.partition = function() { - var hierarchy = d3.layout.hierarchy(), size = [ 1, 1 ]; - function position(node, x, dx, dy) { - var children = node.children; - node.x = x; - node.y = node.depth * dy; - node.dx = dx; - node.dy = dy; - if (children && (n = children.length)) { - var i = -1, n, c, d; - dx = node.value ? dx / node.value : 0; - while (++i < n) { - position(c = children[i], x, d = c.value * dx, dy); - x += d; - } - } - } - function depth(node) { - var children = node.children, d = 0; - if (children && (n = children.length)) { - var i = -1, n; - while (++i < n) d = Math.max(d, depth(children[i])); - } - return 1 + d; - } - function partition(d, i) { - var nodes = hierarchy.call(this, d, i); - position(nodes[0], 0, size[0], size[1] / depth(nodes[0])); - return nodes; - } - partition.size = function(x) { - if (!arguments.length) return size; - size = x; - return partition; - }; - return d3_layout_hierarchyRebind(partition, hierarchy); - }; - d3.layout.pie = function() { - var value = Number, sort = d3_layout_pieSortByValue, startAngle = 0, endAngle = 2 * π; - function pie(data) { - var values = data.map(function(d, i) { - return +value.call(pie, d, i); - }); - var a = +(typeof startAngle === "function" ? startAngle.apply(this, arguments) : startAngle); - var k = ((typeof endAngle === "function" ? endAngle.apply(this, arguments) : endAngle) - startAngle) / d3.sum(values); - var index = d3.range(data.length); - if (sort != null) index.sort(sort === d3_layout_pieSortByValue ? function(i, j) { - return values[j] - values[i]; - } : function(i, j) { - return sort(data[i], data[j]); - }); - var arcs = []; - index.forEach(function(i) { - var d; - arcs[i] = { - data: data[i], - value: d = values[i], - startAngle: a, - endAngle: a += d * k - }; - }); - return arcs; - } - pie.value = function(x) { - if (!arguments.length) return value; - value = x; - return pie; - }; - pie.sort = function(x) { - if (!arguments.length) return sort; - sort = x; - return pie; - }; - pie.startAngle = function(x) { - if (!arguments.length) return startAngle; - startAngle = x; - return pie; - }; - pie.endAngle = function(x) { - if (!arguments.length) return endAngle; - endAngle = x; - return pie; - }; - return pie; - }; - var d3_layout_pieSortByValue = {}; - d3.layout.stack = function() { - var values = d3_identity, order = d3_layout_stackOrderDefault, offset = d3_layout_stackOffsetZero, out = d3_layout_stackOut, x = d3_layout_stackX, y = d3_layout_stackY; - function stack(data, index) { - var series = data.map(function(d, i) { - return values.call(stack, d, i); - }); - var points = series.map(function(d) { - return d.map(function(v, i) { - return [ x.call(stack, v, i), y.call(stack, v, i) ]; - }); - }); - var orders = order.call(stack, points, index); - series = d3.permute(series, orders); - points = d3.permute(points, orders); - var offsets = offset.call(stack, points, index); - var n = series.length, m = series[0].length, i, j, o; - for (j = 0; j < m; ++j) { - out.call(stack, series[0][j], o = offsets[j], points[0][j][1]); - for (i = 1; i < n; ++i) { - out.call(stack, series[i][j], o += points[i - 1][j][1], points[i][j][1]); - } - } - return data; - } - stack.values = function(x) { - if (!arguments.length) return values; - values = x; - return stack; - }; - stack.order = function(x) { - if (!arguments.length) return order; - order = typeof x === "function" ? x : d3_layout_stackOrders.get(x) || d3_layout_stackOrderDefault; - return stack; - }; - stack.offset = function(x) { - if (!arguments.length) return offset; - offset = typeof x === "function" ? x : d3_layout_stackOffsets.get(x) || d3_layout_stackOffsetZero; - return stack; - }; - stack.x = function(z) { - if (!arguments.length) return x; - x = z; - return stack; - }; - stack.y = function(z) { - if (!arguments.length) return y; - y = z; - return stack; - }; - stack.out = function(z) { - if (!arguments.length) return out; - out = z; - return stack; - }; - return stack; - }; - function d3_layout_stackX(d) { - return d.x; - } - function d3_layout_stackY(d) { - return d.y; - } - function d3_layout_stackOut(d, y0, y) { - d.y0 = y0; - d.y = y; - } - var d3_layout_stackOrders = d3.map({ - "inside-out": function(data) { - var n = data.length, i, j, max = data.map(d3_layout_stackMaxIndex), sums = data.map(d3_layout_stackReduceSum), index = d3.range(n).sort(function(a, b) { - return max[a] - max[b]; - }), top = 0, bottom = 0, tops = [], bottoms = []; - for (i = 0; i < n; ++i) { - j = index[i]; - if (top < bottom) { - top += sums[j]; - tops.push(j); - } else { - bottom += sums[j]; - bottoms.push(j); - } - } - return bottoms.reverse().concat(tops); - }, - reverse: function(data) { - return d3.range(data.length).reverse(); - }, - "default": d3_layout_stackOrderDefault - }); - var d3_layout_stackOffsets = d3.map({ - silhouette: function(data) { - var n = data.length, m = data[0].length, sums = [], max = 0, i, j, o, y0 = []; - for (j = 0; j < m; ++j) { - for (i = 0, o = 0; i < n; i++) o += data[i][j][1]; - if (o > max) max = o; - sums.push(o); - } - for (j = 0; j < m; ++j) { - y0[j] = (max - sums[j]) / 2; - } - return y0; - }, - wiggle: function(data) { - var n = data.length, x = data[0], m = x.length, i, j, k, s1, s2, s3, dx, o, o0, y0 = []; - y0[0] = o = o0 = 0; - for (j = 1; j < m; ++j) { - for (i = 0, s1 = 0; i < n; ++i) s1 += data[i][j][1]; - for (i = 0, s2 = 0, dx = x[j][0] - x[j - 1][0]; i < n; ++i) { - for (k = 0, s3 = (data[i][j][1] - data[i][j - 1][1]) / (2 * dx); k < i; ++k) { - s3 += (data[k][j][1] - data[k][j - 1][1]) / dx; - } - s2 += s3 * data[i][j][1]; - } - y0[j] = o -= s1 ? s2 / s1 * dx : 0; - if (o < o0) o0 = o; - } - for (j = 0; j < m; ++j) y0[j] -= o0; - return y0; - }, - expand: function(data) { - var n = data.length, m = data[0].length, k = 1 / n, i, j, o, y0 = []; - for (j = 0; j < m; ++j) { - for (i = 0, o = 0; i < n; i++) o += data[i][j][1]; - if (o) for (i = 0; i < n; i++) data[i][j][1] /= o; else for (i = 0; i < n; i++) data[i][j][1] = k; - } - for (j = 0; j < m; ++j) y0[j] = 0; - return y0; - }, - zero: d3_layout_stackOffsetZero - }); - function d3_layout_stackOrderDefault(data) { - return d3.range(data.length); - } - function d3_layout_stackOffsetZero(data) { - var j = -1, m = data[0].length, y0 = []; - while (++j < m) y0[j] = 0; - return y0; - } - function d3_layout_stackMaxIndex(array) { - var i = 1, j = 0, v = array[0][1], k, n = array.length; - for (;i < n; ++i) { - if ((k = array[i][1]) > v) { - j = i; - v = k; - } - } - return j; - } - function d3_layout_stackReduceSum(d) { - return d.reduce(d3_layout_stackSum, 0); - } - function d3_layout_stackSum(p, d) { - return p + d[1]; - } - d3.layout.histogram = function() { - var frequency = true, valuer = Number, ranger = d3_layout_histogramRange, binner = d3_layout_histogramBinSturges; - function histogram(data, i) { - var bins = [], values = data.map(valuer, this), range = ranger.call(this, values, i), thresholds = binner.call(this, range, values, i), bin, i = -1, n = values.length, m = thresholds.length - 1, k = frequency ? 1 : 1 / n, x; - while (++i < m) { - bin = bins[i] = []; - bin.dx = thresholds[i + 1] - (bin.x = thresholds[i]); - bin.y = 0; - } - if (m > 0) { - i = -1; - while (++i < n) { - x = values[i]; - if (x >= range[0] && x <= range[1]) { - bin = bins[d3.bisect(thresholds, x, 1, m) - 1]; - bin.y += k; - bin.push(data[i]); - } - } - } - return bins; - } - histogram.value = function(x) { - if (!arguments.length) return valuer; - valuer = x; - return histogram; - }; - histogram.range = function(x) { - if (!arguments.length) return ranger; - ranger = d3_functor(x); - return histogram; - }; - histogram.bins = function(x) { - if (!arguments.length) return binner; - binner = typeof x === "number" ? function(range) { - return d3_layout_histogramBinFixed(range, x); - } : d3_functor(x); - return histogram; - }; - histogram.frequency = function(x) { - if (!arguments.length) return frequency; - frequency = !!x; - return histogram; - }; - return histogram; - }; - function d3_layout_histogramBinSturges(range, values) { - return d3_layout_histogramBinFixed(range, Math.ceil(Math.log(values.length) / Math.LN2 + 1)); - } - function d3_layout_histogramBinFixed(range, n) { - var x = -1, b = +range[0], m = (range[1] - b) / n, f = []; - while (++x <= n) f[x] = m * x + b; - return f; - } - function d3_layout_histogramRange(values) { - return [ d3.min(values), d3.max(values) ]; - } - d3.layout.hierarchy = function() { - var sort = d3_layout_hierarchySort, children = d3_layout_hierarchyChildren, value = d3_layout_hierarchyValue; - function recurse(node, depth, nodes) { - var childs = children.call(hierarchy, node, depth); - node.depth = depth; - nodes.push(node); - if (childs && (n = childs.length)) { - var i = -1, n, c = node.children = [], v = 0, j = depth + 1, d; - while (++i < n) { - d = recurse(childs[i], j, nodes); - d.parent = node; - c.push(d); - v += d.value; - } - if (sort) c.sort(sort); - if (value) node.value = v; - } else if (value) { - node.value = +value.call(hierarchy, node, depth) || 0; - } - return node; - } - function revalue(node, depth) { - var children = node.children, v = 0; - if (children && (n = children.length)) { - var i = -1, n, j = depth + 1; - while (++i < n) v += revalue(children[i], j); - } else if (value) { - v = +value.call(hierarchy, node, depth) || 0; - } - if (value) node.value = v; - return v; - } - function hierarchy(d) { - var nodes = []; - recurse(d, 0, nodes); - return nodes; - } - hierarchy.sort = function(x) { - if (!arguments.length) return sort; - sort = x; - return hierarchy; - }; - hierarchy.children = function(x) { - if (!arguments.length) return children; - children = x; - return hierarchy; - }; - hierarchy.value = function(x) { - if (!arguments.length) return value; - value = x; - return hierarchy; - }; - hierarchy.revalue = function(root) { - revalue(root, 0); - return root; - }; - return hierarchy; - }; - function d3_layout_hierarchyRebind(object, hierarchy) { - d3.rebind(object, hierarchy, "sort", "children", "value"); - object.nodes = object; - object.links = d3_layout_hierarchyLinks; - return object; - } - function d3_layout_hierarchyChildren(d) { - return d.children; - } - function d3_layout_hierarchyValue(d) { - return d.value; - } - function d3_layout_hierarchySort(a, b) { - return b.value - a.value; - } - function d3_layout_hierarchyLinks(nodes) { - return d3.merge(nodes.map(function(parent) { - return (parent.children || []).map(function(child) { - return { - source: parent, - target: child - }; - }); - })); - } - d3.layout.pack = function() { - var hierarchy = d3.layout.hierarchy().sort(d3_layout_packSort), padding = 0, size = [ 1, 1 ]; - function pack(d, i) { - var nodes = hierarchy.call(this, d, i), root = nodes[0]; - root.x = 0; - root.y = 0; - d3_layout_treeVisitAfter(root, function(d) { - d.r = Math.sqrt(d.value); - }); - d3_layout_treeVisitAfter(root, d3_layout_packSiblings); - var w = size[0], h = size[1], k = Math.max(2 * root.r / w, 2 * root.r / h); - if (padding > 0) { - var dr = padding * k / 2; - d3_layout_treeVisitAfter(root, function(d) { - d.r += dr; - }); - d3_layout_treeVisitAfter(root, d3_layout_packSiblings); - d3_layout_treeVisitAfter(root, function(d) { - d.r -= dr; - }); - k = Math.max(2 * root.r / w, 2 * root.r / h); - } - d3_layout_packTransform(root, w / 2, h / 2, 1 / k); - return nodes; - } - pack.size = function(x) { - if (!arguments.length) return size; - size = x; - return pack; - }; - pack.padding = function(_) { - if (!arguments.length) return padding; - padding = +_; - return pack; - }; - return d3_layout_hierarchyRebind(pack, hierarchy); - }; - function d3_layout_packSort(a, b) { - return a.value - b.value; - } - function d3_layout_packInsert(a, b) { - var c = a._pack_next; - a._pack_next = b; - b._pack_prev = a; - b._pack_next = c; - c._pack_prev = b; - } - function d3_layout_packSplice(a, b) { - a._pack_next = b; - b._pack_prev = a; - } - function d3_layout_packIntersects(a, b) { - var dx = b.x - a.x, dy = b.y - a.y, dr = a.r + b.r; - return dr * dr - dx * dx - dy * dy > .001; - } - function d3_layout_packSiblings(node) { - if (!(nodes = node.children) || !(n = nodes.length)) return; - var nodes, xMin = Infinity, xMax = -Infinity, yMin = Infinity, yMax = -Infinity, a, b, c, i, j, k, n; - function bound(node) { - xMin = Math.min(node.x - node.r, xMin); - xMax = Math.max(node.x + node.r, xMax); - yMin = Math.min(node.y - node.r, yMin); - yMax = Math.max(node.y + node.r, yMax); - } - nodes.forEach(d3_layout_packLink); - a = nodes[0]; - a.x = -a.r; - a.y = 0; - bound(a); - if (n > 1) { - b = nodes[1]; - b.x = b.r; - b.y = 0; - bound(b); - if (n > 2) { - c = nodes[2]; - d3_layout_packPlace(a, b, c); - bound(c); - d3_layout_packInsert(a, c); - a._pack_prev = c; - d3_layout_packInsert(c, b); - b = a._pack_next; - for (i = 3; i < n; i++) { - d3_layout_packPlace(a, b, c = nodes[i]); - var isect = 0, s1 = 1, s2 = 1; - for (j = b._pack_next; j !== b; j = j._pack_next, s1++) { - if (d3_layout_packIntersects(j, c)) { - isect = 1; - break; - } - } - if (isect == 1) { - for (k = a._pack_prev; k !== j._pack_prev; k = k._pack_prev, s2++) { - if (d3_layout_packIntersects(k, c)) { - break; - } - } - } - if (isect) { - if (s1 < s2 || s1 == s2 && b.r < a.r) d3_layout_packSplice(a, b = j); else d3_layout_packSplice(a = k, b); - i--; - } else { - d3_layout_packInsert(a, c); - b = c; - bound(c); - } - } - } - } - var cx = (xMin + xMax) / 2, cy = (yMin + yMax) / 2, cr = 0; - for (i = 0; i < n; i++) { - c = nodes[i]; - c.x -= cx; - c.y -= cy; - cr = Math.max(cr, c.r + Math.sqrt(c.x * c.x + c.y * c.y)); - } - node.r = cr; - nodes.forEach(d3_layout_packUnlink); - } - function d3_layout_packLink(node) { - node._pack_next = node._pack_prev = node; - } - function d3_layout_packUnlink(node) { - delete node._pack_next; - delete node._pack_prev; - } - function d3_layout_packTransform(node, x, y, k) { - var children = node.children; - node.x = x += k * node.x; - node.y = y += k * node.y; - node.r *= k; - if (children) { - var i = -1, n = children.length; - while (++i < n) d3_layout_packTransform(children[i], x, y, k); - } - } - function d3_layout_packPlace(a, b, c) { - var db = a.r + c.r, dx = b.x - a.x, dy = b.y - a.y; - if (db && (dx || dy)) { - var da = b.r + c.r, dc = dx * dx + dy * dy; - da *= da; - db *= db; - var x = .5 + (db - da) / (2 * dc), y = Math.sqrt(Math.max(0, 2 * da * (db + dc) - (db -= dc) * db - da * da)) / (2 * dc); - c.x = a.x + x * dx + y * dy; - c.y = a.y + x * dy - y * dx; - } else { - c.x = a.x + db; - c.y = a.y; - } - } - d3.layout.cluster = function() { - var hierarchy = d3.layout.hierarchy().sort(null).value(null), separation = d3_layout_treeSeparation, size = [ 1, 1 ]; - function cluster(d, i) { - var nodes = hierarchy.call(this, d, i), root = nodes[0], previousNode, x = 0; - d3_layout_treeVisitAfter(root, function(node) { - var children = node.children; - if (children && children.length) { - node.x = d3_layout_clusterX(children); - node.y = d3_layout_clusterY(children); - } else { - node.x = previousNode ? x += separation(node, previousNode) : 0; - node.y = 0; - previousNode = node; - } - }); - var left = d3_layout_clusterLeft(root), right = d3_layout_clusterRight(root), x0 = left.x - separation(left, right) / 2, x1 = right.x + separation(right, left) / 2; - d3_layout_treeVisitAfter(root, function(node) { - node.x = (node.x - x0) / (x1 - x0) * size[0]; - node.y = (1 - (root.y ? node.y / root.y : 1)) * size[1]; - }); - return nodes; - } - cluster.separation = function(x) { - if (!arguments.length) return separation; - separation = x; - return cluster; - }; - cluster.size = function(x) { - if (!arguments.length) return size; - size = x; - return cluster; - }; - return d3_layout_hierarchyRebind(cluster, hierarchy); - }; - function d3_layout_clusterY(children) { - return 1 + d3.max(children, function(child) { - return child.y; - }); - } - function d3_layout_clusterX(children) { - return children.reduce(function(x, child) { - return x + child.x; - }, 0) / children.length; - } - function d3_layout_clusterLeft(node) { - var children = node.children; - return children && children.length ? d3_layout_clusterLeft(children[0]) : node; - } - function d3_layout_clusterRight(node) { - var children = node.children, n; - return children && (n = children.length) ? d3_layout_clusterRight(children[n - 1]) : node; - } - d3.layout.tree = function() { - var hierarchy = d3.layout.hierarchy().sort(null).value(null), separation = d3_layout_treeSeparation, size = [ 1, 1 ]; - function tree(d, i) { - var nodes = hierarchy.call(this, d, i), root = nodes[0]; - function firstWalk(node, previousSibling) { - var children = node.children, layout = node._tree; - if (children && (n = children.length)) { - var n, firstChild = children[0], previousChild, ancestor = firstChild, child, i = -1; - while (++i < n) { - child = children[i]; - firstWalk(child, previousChild); - ancestor = apportion(child, previousChild, ancestor); - previousChild = child; - } - d3_layout_treeShift(node); - var midpoint = .5 * (firstChild._tree.prelim + child._tree.prelim); - if (previousSibling) { - layout.prelim = previousSibling._tree.prelim + separation(node, previousSibling); - layout.mod = layout.prelim - midpoint; - } else { - layout.prelim = midpoint; - } - } else { - if (previousSibling) { - layout.prelim = previousSibling._tree.prelim + separation(node, previousSibling); - } - } - } - function secondWalk(node, x) { - node.x = node._tree.prelim + x; - var children = node.children; - if (children && (n = children.length)) { - var i = -1, n; - x += node._tree.mod; - while (++i < n) { - secondWalk(children[i], x); - } - } - } - function apportion(node, previousSibling, ancestor) { - if (previousSibling) { - var vip = node, vop = node, vim = previousSibling, vom = node.parent.children[0], sip = vip._tree.mod, sop = vop._tree.mod, sim = vim._tree.mod, som = vom._tree.mod, shift; - while (vim = d3_layout_treeRight(vim), vip = d3_layout_treeLeft(vip), vim && vip) { - vom = d3_layout_treeLeft(vom); - vop = d3_layout_treeRight(vop); - vop._tree.ancestor = node; - shift = vim._tree.prelim + sim - vip._tree.prelim - sip + separation(vim, vip); - if (shift > 0) { - d3_layout_treeMove(d3_layout_treeAncestor(vim, node, ancestor), node, shift); - sip += shift; - sop += shift; - } - sim += vim._tree.mod; - sip += vip._tree.mod; - som += vom._tree.mod; - sop += vop._tree.mod; - } - if (vim && !d3_layout_treeRight(vop)) { - vop._tree.thread = vim; - vop._tree.mod += sim - sop; - } - if (vip && !d3_layout_treeLeft(vom)) { - vom._tree.thread = vip; - vom._tree.mod += sip - som; - ancestor = node; - } - } - return ancestor; - } - d3_layout_treeVisitAfter(root, function(node, previousSibling) { - node._tree = { - ancestor: node, - prelim: 0, - mod: 0, - change: 0, - shift: 0, - number: previousSibling ? previousSibling._tree.number + 1 : 0 - }; - }); - firstWalk(root); - secondWalk(root, -root._tree.prelim); - var left = d3_layout_treeSearch(root, d3_layout_treeLeftmost), right = d3_layout_treeSearch(root, d3_layout_treeRightmost), deep = d3_layout_treeSearch(root, d3_layout_treeDeepest), x0 = left.x - separation(left, right) / 2, x1 = right.x + separation(right, left) / 2, y1 = deep.depth || 1; - d3_layout_treeVisitAfter(root, function(node) { - node.x = (node.x - x0) / (x1 - x0) * size[0]; - node.y = node.depth / y1 * size[1]; - delete node._tree; - }); - return nodes; - } - tree.separation = function(x) { - if (!arguments.length) return separation; - separation = x; - return tree; - }; - tree.size = function(x) { - if (!arguments.length) return size; - size = x; - return tree; - }; - return d3_layout_hierarchyRebind(tree, hierarchy); - }; - function d3_layout_treeSeparation(a, b) { - return a.parent == b.parent ? 1 : 2; - } - function d3_layout_treeLeft(node) { - var children = node.children; - return children && children.length ? children[0] : node._tree.thread; - } - function d3_layout_treeRight(node) { - var children = node.children, n; - return children && (n = children.length) ? children[n - 1] : node._tree.thread; - } - function d3_layout_treeSearch(node, compare) { - var children = node.children; - if (children && (n = children.length)) { - var child, n, i = -1; - while (++i < n) { - if (compare(child = d3_layout_treeSearch(children[i], compare), node) > 0) { - node = child; - } - } - } - return node; - } - function d3_layout_treeRightmost(a, b) { - return a.x - b.x; - } - function d3_layout_treeLeftmost(a, b) { - return b.x - a.x; - } - function d3_layout_treeDeepest(a, b) { - return a.depth - b.depth; - } - function d3_layout_treeVisitAfter(node, callback) { - function visit(node, previousSibling) { - var children = node.children; - if (children && (n = children.length)) { - var child, previousChild = null, i = -1, n; - while (++i < n) { - child = children[i]; - visit(child, previousChild); - previousChild = child; - } - } - callback(node, previousSibling); - } - visit(node, null); - } - function d3_layout_treeShift(node) { - var shift = 0, change = 0, children = node.children, i = children.length, child; - while (--i >= 0) { - child = children[i]._tree; - child.prelim += shift; - child.mod += shift; - shift += child.shift + (change += child.change); - } - } - function d3_layout_treeMove(ancestor, node, shift) { - ancestor = ancestor._tree; - node = node._tree; - var change = shift / (node.number - ancestor.number); - ancestor.change += change; - node.change -= change; - node.shift += shift; - node.prelim += shift; - node.mod += shift; - } - function d3_layout_treeAncestor(vim, node, ancestor) { - return vim._tree.ancestor.parent == node.parent ? vim._tree.ancestor : ancestor; - } - d3.layout.treemap = function() { - var hierarchy = d3.layout.hierarchy(), round = Math.round, size = [ 1, 1 ], padding = null, pad = d3_layout_treemapPadNull, sticky = false, stickies, mode = "squarify", ratio = .5 * (1 + Math.sqrt(5)); - function scale(children, k) { - var i = -1, n = children.length, child, area; - while (++i < n) { - area = (child = children[i]).value * (k < 0 ? 0 : k); - child.area = isNaN(area) || area <= 0 ? 0 : area; - } - } - function squarify(node) { - var children = node.children; - if (children && children.length) { - var rect = pad(node), row = [], remaining = children.slice(), child, best = Infinity, score, u = mode === "slice" ? rect.dx : mode === "dice" ? rect.dy : mode === "slice-dice" ? node.depth & 1 ? rect.dy : rect.dx : Math.min(rect.dx, rect.dy), n; - scale(remaining, rect.dx * rect.dy / node.value); - row.area = 0; - while ((n = remaining.length) > 0) { - row.push(child = remaining[n - 1]); - row.area += child.area; - if (mode !== "squarify" || (score = worst(row, u)) <= best) { - remaining.pop(); - best = score; - } else { - row.area -= row.pop().area; - position(row, u, rect, false); - u = Math.min(rect.dx, rect.dy); - row.length = row.area = 0; - best = Infinity; - } - } - if (row.length) { - position(row, u, rect, true); - row.length = row.area = 0; - } - children.forEach(squarify); - } - } - function stickify(node) { - var children = node.children; - if (children && children.length) { - var rect = pad(node), remaining = children.slice(), child, row = []; - scale(remaining, rect.dx * rect.dy / node.value); - row.area = 0; - while (child = remaining.pop()) { - row.push(child); - row.area += child.area; - if (child.z != null) { - position(row, child.z ? rect.dx : rect.dy, rect, !remaining.length); - row.length = row.area = 0; - } - } - children.forEach(stickify); - } - } - function worst(row, u) { - var s = row.area, r, rmax = 0, rmin = Infinity, i = -1, n = row.length; - while (++i < n) { - if (!(r = row[i].area)) continue; - if (r < rmin) rmin = r; - if (r > rmax) rmax = r; - } - s *= s; - u *= u; - return s ? Math.max(u * rmax * ratio / s, s / (u * rmin * ratio)) : Infinity; - } - function position(row, u, rect, flush) { - var i = -1, n = row.length, x = rect.x, y = rect.y, v = u ? round(row.area / u) : 0, o; - if (u == rect.dx) { - if (flush || v > rect.dy) v = rect.dy; - while (++i < n) { - o = row[i]; - o.x = x; - o.y = y; - o.dy = v; - x += o.dx = Math.min(rect.x + rect.dx - x, v ? round(o.area / v) : 0); - } - o.z = true; - o.dx += rect.x + rect.dx - x; - rect.y += v; - rect.dy -= v; - } else { - if (flush || v > rect.dx) v = rect.dx; - while (++i < n) { - o = row[i]; - o.x = x; - o.y = y; - o.dx = v; - y += o.dy = Math.min(rect.y + rect.dy - y, v ? round(o.area / v) : 0); - } - o.z = false; - o.dy += rect.y + rect.dy - y; - rect.x += v; - rect.dx -= v; - } - } - function treemap(d) { - var nodes = stickies || hierarchy(d), root = nodes[0]; - root.x = 0; - root.y = 0; - root.dx = size[0]; - root.dy = size[1]; - if (stickies) hierarchy.revalue(root); - scale([ root ], root.dx * root.dy / root.value); - (stickies ? stickify : squarify)(root); - if (sticky) stickies = nodes; - return nodes; - } - treemap.size = function(x) { - if (!arguments.length) return size; - size = x; - return treemap; - }; - treemap.padding = function(x) { - if (!arguments.length) return padding; - function padFunction(node) { - var p = x.call(treemap, node, node.depth); - return p == null ? d3_layout_treemapPadNull(node) : d3_layout_treemapPad(node, typeof p === "number" ? [ p, p, p, p ] : p); - } - function padConstant(node) { - return d3_layout_treemapPad(node, x); - } - var type; - pad = (padding = x) == null ? d3_layout_treemapPadNull : (type = typeof x) === "function" ? padFunction : type === "number" ? (x = [ x, x, x, x ], - padConstant) : padConstant; - return treemap; - }; - treemap.round = function(x) { - if (!arguments.length) return round != Number; - round = x ? Math.round : Number; - return treemap; - }; - treemap.sticky = function(x) { - if (!arguments.length) return sticky; - sticky = x; - stickies = null; - return treemap; - }; - treemap.ratio = function(x) { - if (!arguments.length) return ratio; - ratio = x; - return treemap; - }; - treemap.mode = function(x) { - if (!arguments.length) return mode; - mode = x + ""; - return treemap; - }; - return d3_layout_hierarchyRebind(treemap, hierarchy); - }; - function d3_layout_treemapPadNull(node) { - return { - x: node.x, - y: node.y, - dx: node.dx, - dy: node.dy - }; - } - function d3_layout_treemapPad(node, padding) { - var x = node.x + padding[3], y = node.y + padding[0], dx = node.dx - padding[1] - padding[3], dy = node.dy - padding[0] - padding[2]; - if (dx < 0) { - x += dx / 2; - dx = 0; - } - if (dy < 0) { - y += dy / 2; - dy = 0; - } - return { - x: x, - y: y, - dx: dx, - dy: dy - }; - } - function d3_dsv(delimiter, mimeType) { - var reFormat = new RegExp('["' + delimiter + "\n]"), delimiterCode = delimiter.charCodeAt(0); - function dsv(url, callback) { - return d3.xhr(url, mimeType, callback).response(response); - } - function response(request) { - return dsv.parse(request.responseText); - } - dsv.parse = function(text) { - var o; - return dsv.parseRows(text, function(row) { - if (o) return o(row); - o = new Function("d", "return {" + row.map(function(name, i) { - return JSON.stringify(name) + ": d[" + i + "]"; - }).join(",") + "}"); - }); - }; - dsv.parseRows = function(text, f) { - var EOL = {}, EOF = {}, rows = [], N = text.length, I = 0, n = 0, t, eol; - function token() { - if (I >= N) return EOF; - if (eol) return eol = false, EOL; - var j = I; - if (text.charCodeAt(j) === 34) { - var i = j; - while (i++ < N) { - if (text.charCodeAt(i) === 34) { - if (text.charCodeAt(i + 1) !== 34) break; - ++i; - } - } - I = i + 2; - var c = text.charCodeAt(i + 1); - if (c === 13) { - eol = true; - if (text.charCodeAt(i + 2) === 10) ++I; - } else if (c === 10) { - eol = true; - } - return text.substring(j + 1, i).replace(/""/g, '"'); - } - while (I < N) { - var c = text.charCodeAt(I++), k = 1; - if (c === 10) eol = true; else if (c === 13) { - eol = true; - if (text.charCodeAt(I) === 10) ++I, ++k; - } else if (c !== delimiterCode) continue; - return text.substring(j, I - k); - } - return text.substring(j); - } - while ((t = token()) !== EOF) { - var a = []; - while (t !== EOL && t !== EOF) { - a.push(t); - t = token(); - } - if (f && !(a = f(a, n++))) continue; - rows.push(a); - } - return rows; - }; - dsv.format = function(rows) { - return rows.map(formatRow).join("\n"); - }; - function formatRow(row) { - return row.map(formatValue).join(delimiter); - } - function formatValue(text) { - return reFormat.test(text) ? '"' + text.replace(/\"/g, '""') + '"' : text; - } - return dsv; - } - d3.csv = d3_dsv(",", "text/csv"); - d3.tsv = d3_dsv(" ", "text/tab-separated-values"); d3.geo = {}; d3.geo.stream = function(object, listener) { if (d3_geo_streamObjectType.hasOwnProperty(object.type)) { @@ -6446,7 +5038,7 @@ d3.geo.stream(object, d3_geo_area); return d3_geo_areaSum; }; - var d3_geo_areaSum, d3_geo_areaRing; + var d3_geo_areaSum, d3_geo_areaRingU, d3_geo_areaRingV; var d3_geo_area = { sphere: function() { d3_geo_areaSum += 4 * π; @@ -6455,30 +5047,29 @@ lineStart: d3_noop, lineEnd: d3_noop, polygonStart: function() { - d3_geo_areaRing = 0; + d3_geo_areaRingU = 1, d3_geo_areaRingV = 0; d3_geo_area.lineStart = d3_geo_areaRingStart; }, polygonEnd: function() { - d3_geo_areaSum += d3_geo_areaRing < 0 ? 4 * π + d3_geo_areaRing : d3_geo_areaRing; + var area = 2 * Math.atan2(d3_geo_areaRingV, d3_geo_areaRingU); + d3_geo_areaSum += area < 0 ? 4 * π + area : area; d3_geo_area.lineStart = d3_geo_area.lineEnd = d3_geo_area.point = d3_noop; } }; function d3_geo_areaRingStart() { - var λ00, φ00, λ1, λ0, φ0, cosφ0, sinφ0; + var λ00, φ00, λ0, cosφ0, sinφ0; d3_geo_area.point = function(λ, φ) { d3_geo_area.point = nextPoint; - λ1 = λ0 = (λ00 = λ) * d3_radians, φ0 = (φ00 = φ) * d3_radians, cosφ0 = Math.cos(φ0), - sinφ0 = Math.sin(φ0); + λ0 = (λ00 = λ) * d3_radians, cosφ0 = Math.cos(φ = (φ00 = φ) * d3_radians / 2 + π / 4), + sinφ0 = Math.sin(φ); }; function nextPoint(λ, φ) { - λ *= d3_radians, φ *= d3_radians; - if (Math.abs(Math.abs(φ0) - π / 2) < ε && Math.abs(Math.abs(φ) - π / 2) < ε) return; - var cosφ = Math.cos(φ), sinφ = Math.sin(φ); - if (Math.abs(φ0 - π / 2) < ε) d3_geo_areaRing += (λ - λ1) * 2; else { - var dλ = λ - λ0, cosdλ = Math.cos(dλ), d = Math.atan2(Math.sqrt((d = cosφ * Math.sin(dλ)) * d + (d = cosφ0 * sinφ - sinφ0 * cosφ * cosdλ) * d), sinφ0 * sinφ + cosφ0 * cosφ * cosdλ), s = (d + π + φ0 + φ) / 4; - d3_geo_areaRing += (dλ < 0 && dλ > -π || dλ > π ? -4 : 4) * Math.atan(Math.sqrt(Math.abs(Math.tan(s) * Math.tan(s - d / 2) * Math.tan(s - π / 4 - φ0 / 2) * Math.tan(s - π / 4 - φ / 2)))); - } - λ1 = λ0, λ0 = λ, φ0 = φ, cosφ0 = cosφ, sinφ0 = sinφ; + λ *= d3_radians; + φ = φ * d3_radians / 2 + π / 4; + 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λ); + d3_geo_areaRingU = u0 * u - v0 * v; + d3_geo_areaRingV = v0 * u + u0 * v; + λ0 = λ, cosφ0 = cosφ, sinφ0 = sinφ; } d3_geo_area.lineEnd = function() { nextPoint(λ00, φ00); @@ -6782,10 +5373,10 @@ polygons = polygons.map(function(polygon, i) { var cx = vertices[i][0], cy = vertices[i][1], angle = polygon.map(function(v) { return Math.atan2(v[0] - cx, v[1] - cy); - }); - return d3.range(polygon.length).sort(function(a, b) { + }), order = d3.range(polygon.length).sort(function(a, b) { return angle[a] - angle[b]; - }).filter(function(d, i, order) { + }); + return order.filter(function(d, i) { return !i || angle[d] - angle[order[i - 1]] > ε; }).map(function(d) { return polygon[d]; @@ -7796,4 +6387,5 @@ d3.time.scale.utc = function() { return d3_time_scale(d3.scale.linear(), d3_time_scaleUTCMethods, d3_time_scaleUTCFormat); }; -})(); \ No newline at end of file + return d3; +}(); \ No newline at end of file diff --git a/land.html b/land.html index 48fa3c09a..e03f34ee3 100644 --- a/land.html +++ b/land.html @@ -1,23 +1,9 @@ - - - - iD - - + -

diff --git a/locale/da.js b/locale/da.js index 6d68e36d7..dfcb28daf 100644 --- a/locale/da.js +++ b/locale/da.js @@ -179,7 +179,11 @@ locale.da = { new_tag: "Nyt tag", edit_tags: "Ret tags", okay: "Ok", - view_on_osm: "Vis på OSM" + view_on_osm: "Vis på OSM", + name: "Name", + editing: "Editing {type}", + additional: "Additional tags", + choose: "What are you adding?" }, restore: { @@ -221,6 +225,7 @@ locale.da = { untagged_point: "Mangler et tag på punkt som ikke er del af en linje eller område", untagged_line: "Mangler tag på linje", untagged_area: "Mangler tag på område", + many_deletions: "You're deleting {n} objects. Are you sure you want to do this? This will delete them from the map that everyone else sees on openstreetmap.org.", tag_suggests_area: "Dette tag {tag} mener denne linje skule være et område, men dette er ikke et område", deprecated_tags: "Uønskede tags: {tags}" } diff --git a/locale/de.js b/locale/de.js index 1c56f5ce7..7667de056 100644 --- a/locale/de.js +++ b/locale/de.js @@ -171,7 +171,11 @@ locale.de = { new_tag: "Neues Attribut", edit_tags: "Attribute bearbeiten", okay: "OK", - view_on_osm: "auf OpenStreetMap ansehen" + view_on_osm: "auf OpenStreetMap ansehen", + name: "Name", + editing: "Editing {type}", + additional: "Additional tags", + choose: "What are you adding?" }, layerswitcher: { @@ -216,6 +220,7 @@ locale.de = { untagged_point: "Punkt ohne Attribute, der kein Teil einer Linie oder Fläche ist", untagged_line: "Linie ohne Attribute", untagged_area: "Fläche ohne Attribute", + many_deletions: "You're deleting {n} objects. Are you sure you want to do this? This will delete them from the map that everyone else sees on openstreetmap.org.", tag_suggests_area: "Das Attribut {tag} suggeriert eine Fläche, ist aber keine Fläche", deprecated_tags: "Veraltete Attribute: {tags}" }, diff --git a/locale/en.js b/locale/en.js index 943ea9af4..b027bd08a 100644 --- a/locale/en.js +++ b/locale/en.js @@ -112,7 +112,7 @@ locale.en = { vertex: "Moved a node in a way.", line: "Moved a line.", area: "Moved an area.", - multiple: "Moved multiple objects" + multiple: "Moved multiple objects." } }, rotate: { @@ -181,7 +181,11 @@ locale.en = { new_tag: "New Tag", edit_tags: "Edit tags", okay: "Okay", - view_on_osm: "View on OSM" + view_on_osm: "View on OSM", + name: "Name", + editing: "Editing {type}", + additional: "Additional tags", + choose: "What are you adding?" }, layerswitcher: { @@ -226,6 +230,7 @@ locale.en = { untagged_point: "Untagged point which is not part of a line or area", untagged_line: "Untagged line", untagged_area: "Untagged area", + many_deletions: "You're deleting {n} objects. Are you sure you want to do this? This will delete them from the map that everyone else sees on openstreetmap.org.", tag_suggests_area: "The tag {tag} suggests line should be area, but it is not an area", deprecated_tags: "Deprecated tags: {tags}" }, diff --git a/locale/es.js b/locale/es.js index d7078d512..b3b3f2c65 100644 --- a/locale/es.js +++ b/locale/es.js @@ -171,7 +171,11 @@ locale.es = { new_tag: "Nueva etiqueta", //"New Tag" edit_tags: "Editar etiquetas", //"Edit tags", okay: "OK", - view_on_osm: "Ver en OSM" //"View on OSM" + view_on_osm: "Ver en OSM", //"View on OSM", + name: "Name", + editing: "Editing {type}", + additional: "Additional tags", + choose: "What are you adding?" }, layerswitcher: { @@ -216,6 +220,7 @@ locale.es = { untagged_point: "Punto sin etiquetar que no es parte de una línea ni de un área.", //"Untagged point which is not part of a line or area", untagged_line: "Línea sin etiquetar", //"Untagged line", untagged_area: "Área sin etiquetar", //"Untagged area", + many_deletions: "You're deleting {n} objects. Are you sure you want to do this? This will delete them from the map that everyone else sees on openstreetmap.org.", tag_suggests_area: "La etiqueta {tag} sugiere que esta línea debería ser una área, pero no lo es.", //"The tag {tag} suggests line should be area, but it is not an area", deprecated_tags: "Etiquetas obsoletas: {tags}" //"Deprecated tags: {tags}" }, diff --git a/locale/fr.js b/locale/fr.js index 66dba3304..f4fcdc446 100644 --- a/locale/fr.js +++ b/locale/fr.js @@ -171,7 +171,11 @@ locale.fr = { new_tag: "Nouveau tag", edit_tags: "Editer les tags", okay: "Okay", - view_on_osm: "Visualiser sur OSM" + view_on_osm: "Visualiser sur OSM", + name: "Name", + editing: "Editing {type}", + additional: "Additional tags", + choose: "What are you adding?" }, layerswitcher: { @@ -216,6 +220,7 @@ locale.fr = { untagged_point: "Point sans aucun tag ne faisant partie ni d'une ligne, ni d'un polygone", untagged_line: "Ligne sans aucun tag", untagged_area: "Polygone sans aucun tag", + many_deletions: "You're deleting {n} objects. Are you sure you want to do this? This will delete them from the map that everyone else sees on openstreetmap.org.", tag_suggests_area: "Ce tag {tag} suppose que cette ligne devrait être un polygone, or ce n'est pas le cas", deprecated_tags: "Tags obsolètes : {tags}" }, diff --git a/locale/it.js b/locale/it.js index eb6fdff43..dd7960a9a 100644 --- a/locale/it.js +++ b/locale/it.js @@ -171,7 +171,11 @@ locale.it = { new_tag: "Nuovo Tag", edit_tags: "Modifica i tag", okay: "Ok", - view_on_osm: "Mostra su OSM" + view_on_osm: "Mostra su OSM", + name: "Name", + editing: "Editing {type}", + additional: "Additional tags", + choose: "What are you adding?" }, layerswitcher: { @@ -216,6 +220,7 @@ locale.it = { untagged_point: "Punto senza tag che non è parte di una linea o di un'area", untagged_line: "Linea senza tag", untagged_area: "Area senza tag", + many_deletions: "You're deleting {n} objects. Are you sure you want to do this? This will delete them from the map that everyone else sees on openstreetmap.org.", tag_suggests_area: "Il tag {tag} fa pensare che la linea sia un'area, ma non rappresenta un'area", deprecated_tags: "Tag deprecati: {tags}" }, diff --git a/locale/ja.js b/locale/ja.js index 025b7f992..13261ecb3 100644 --- a/locale/ja.js +++ b/locale/ja.js @@ -171,7 +171,11 @@ locale.ja = { new_tag: "新規タグ", edit_tags: "タグ編集", okay: "OK", - view_on_osm: "詳細情報確認" + view_on_osm: "詳細情報確認", + name: "Name", + editing: "Editing {type}", + additional: "Additional tags", + choose: "What are you adding?" }, layerswitcher: { @@ -216,6 +220,7 @@ locale.ja = { untagged_point: "ポイントにタグが付与されておらず、ラインやエリアの一部でもありません", untagged_line: "ラインにタグが付与されていません", untagged_area: "エリアにタグが付与されていません", + many_deletions: "You're deleting {n} objects. Are you sure you want to do this? This will delete them from the map that everyone else sees on openstreetmap.org.", tag_suggests_area: "ラインに {tag} タグが付与されています。エリアで描かれるべきです", deprecated_tags: "タグの重複: {tags}" }, diff --git a/locale/lv.js b/locale/lv.js index 70c966f43..e4e635079 100644 --- a/locale/lv.js +++ b/locale/lv.js @@ -171,7 +171,11 @@ locale.lv = { new_tag: "Jauns apzīmējums", edit_tags: "Labot apzīmējumus", okay: "Labi", - view_on_osm: "Apskatīt OSM" + view_on_osm: "Apskatīt OSM", + name: "Name", + editing: "Editing {type}", + additional: "Additional tags", + choose: "What are you adding?" }, layerswitcher: { @@ -216,6 +220,7 @@ locale.lv = { untagged_point: "Neapzīmēts punkts", untagged_line: "Neapzīmēta līnija", untagged_area: "Neapzīmēts apgabals", + many_deletions: "You're deleting {n} objects. Are you sure you want to do this? This will delete them from the map that everyone else sees on openstreetmap.org.", tag_suggests_area: "Apzīmējums {tag} parasti tiek lietots apgabaliem, bet objekts nav apgabals", deprecated_tags: "Novecojuši apzīmējumi: {tags}" }, diff --git a/locale/nl.js b/locale/nl.js index fb183ced8..c7c888895 100644 --- a/locale/nl.js +++ b/locale/nl.js @@ -171,7 +171,11 @@ locale.nl = { new_tag: "Nieuwe tag", edit_tags: "Tags aanpassen", okay: "OK", - view_on_osm: "Bekijk op OSM" + view_on_osm: "Bekijk op OSM", + name: "Name", + editing: "Editing {type}", + additional: "Additional tags", + choose: "What are you adding?" }, layerswitcher: { @@ -216,6 +220,7 @@ locale.nl = { untagged_point: "Punt zonder tags, dat geen onderdeel is van een lijn of vlak", untagged_line: "Lijn zonder tags", untagged_area: "Vlak zonder tags", + many_deletions: "You're deleting {n} objects. Are you sure you want to do this? This will delete them from the map that everyone else sees on openstreetmap.org.", tag_suggests_area: "De tag {tag} suggereert dat de lijn een vlak is, maar het is geen vlak", deprecated_tags: "Afgeschafte tags: {tags}" }, diff --git a/locale/pl.js b/locale/pl.js index 9021fac2f..9b1c55268 100644 --- a/locale/pl.js +++ b/locale/pl.js @@ -171,7 +171,11 @@ locale.pl = { new_tag: "Nowy tag", edit_tags: "Edytuj tagi", okay: "Okej", - view_on_osm: "Zobacz w OSM" + view_on_osm: "Zobacz w OSM", + name: "Name", + editing: "Editing {type}", + additional: "Additional tags", + choose: "What are you adding?" }, layerswitcher: { @@ -216,6 +220,7 @@ locale.pl = { untagged_point: "Nieopisany punkt, który nie jest częścią linii lub obszaru.", untagged_line: "Nieopisana linia.", untagged_area: "Nieopisany obszar.", + many_deletions: "You're deleting {n} objects. Are you sure you want to do this? This will delete them from the map that everyone else sees on openstreetmap.org.", tag_suggests_area: "Tag {tag} sugeruje, że linia powinna być obszarem, ale nim nie jest.", deprecated_tags: "Przestarzałe tagi: {tags}" }, diff --git a/locale/ru.js b/locale/ru.js index 11b10fb61..f4c0b7fd2 100644 --- a/locale/ru.js +++ b/locale/ru.js @@ -171,7 +171,11 @@ locale.ru = { new_tag: "Новый тег", edit_tags: "Править теги", okay: "Готово", - view_on_osm: "Посмотреть в OSM" + view_on_osm: "Посмотреть в OSM", + name: "Name", + editing: "Editing {type}", + additional: "Additional tags", + choose: "What are you adding?" }, layerswitcher: { @@ -216,6 +220,7 @@ locale.ru = { untagged_point: "Точка без тегов и не в составе линии или контура", untagged_line: "Линия без тегов", untagged_area: "Контур без тегов", + many_deletions: "You're deleting {n} objects. Are you sure you want to do this? This will delete them from the map that everyone else sees on openstreetmap.org.", tag_suggests_area: "Тег {tag} обычно ставится на замкнутые контуры, но это не контур", deprecated_tags: "Теги устарели: {tags}" }, diff --git a/locale/tr.js b/locale/tr.js index aaa3e3ea7..7e2ff3bcb 100644 --- a/locale/tr.js +++ b/locale/tr.js @@ -171,7 +171,11 @@ locale.tr = { new_tag: "Yeni Etiket", edit_tags: "Etiketleri güncelle", okay: "Tamam", - view_on_osm: "View on OSM" + view_on_osm: "View on OSM", + name: "Name", + editing: "Editing {type}", + additional: "Additional tags", + choose: "What are you adding?" }, layerswitcher: { @@ -216,6 +220,7 @@ locale.tr = { untagged_point: "Herhangi bir çizgi ya da alana bağlantısı olmayan ve etiketlenmemiş bir nokta.", untagged_line: "Etiketlenmemiş çizgi", untagged_area: "Etiketlenmemiş alan", + many_deletions: "You're deleting {n} objects. Are you sure you want to do this? This will delete them from the map that everyone else sees on openstreetmap.org.", tag_suggests_area: "{tag} etiketi buranın alan olmasını tavsiye ediyor ama alan değil.", deprecated_tags: "Kullanımdan kaldırılmış etiket : {tags}" }, diff --git a/locale/uk.js b/locale/uk.js index ffa5eaeda..dabf4d808 100644 --- a/locale/uk.js +++ b/locale/uk.js @@ -171,7 +171,11 @@ locale.uk = { new_tag: "Новий теґ", edit_tags: "Редагувати теґи", okay: "Готово", - view_on_osm: "Подивтись в ОСМ" + view_on_osm: "Подивтись в ОСМ", + name: "Name", + editing: "Editing {type}", + additional: "Additional tags", + choose: "What are you adding?" }, layerswitcher: { @@ -216,6 +220,7 @@ locale.uk = { untagged_point: "Точка без теґів, що не є частиною лінію чи полігону", untagged_line: "Лінія без теґів", untagged_area: "Полігон без теґів", + many_deletions: "You're deleting {n} objects. Are you sure you want to do this? This will delete them from the map that everyone else sees on openstreetmap.org.", tag_suggests_area: "Теґ {tag} зазвичай ставться на полігони, але об’єкт ним не є", deprecated_tags: "Застарілі теґи: {tags}" }, diff --git a/locale/vi.js b/locale/vi.js index 947cc687f..fa22e4230 100644 --- a/locale/vi.js +++ b/locale/vi.js @@ -112,7 +112,7 @@ locale.vi = { vertex: "Di chuyển nốt trong lối.", line: "Di chuyển đường kẻ.", area: "Di chuyển vùng.", - multiple: "Di chuyển hơn một đối tượng" + multiple: "Di chuyển hơn một đối tượng." } }, reverse: { @@ -172,7 +172,11 @@ locale.vi = { new_tag: "Thẻ Mới", edit_tags: "Sửa đổi các thẻ", okay: "OK", - view_on_osm: "Xem tại OSM" + view_on_osm: "Xem tại OSM", + name: "Name", + editing: "Editing {type}", + additional: "Additional tags", + choose: "What are you adding?" }, layerswitcher: { @@ -217,6 +221,7 @@ locale.vi = { untagged_point: "Địa điểm không có thẻ mà không trực thuộc đường kẻ hoặc vùng", untagged_line: "Đường kẻ không có thẻ", untagged_area: "Vùng không có thẻ", + many_deletions: "You're deleting {n} objects. Are you sure you want to do this? This will delete them from the map that everyone else sees on openstreetmap.org.", tag_suggests_area: "Thẻ {tag} có lẽ dành cho vùng nhưng được gắn vào đường kẻ", deprecated_tags: "Thẻ bị phản đối: {tags}" }, diff --git a/presets/presets.json b/presets/presets.json index 4b1063f31..b3e301f41 100644 --- a/presets/presets.json +++ b/presets/presets.json @@ -13,7 +13,8 @@ "form": [ { "key": "cuisine", - "type": "combo" + "type": "combo", + "indexed": true }, { "key": "internet_access", @@ -645,26 +646,6 @@ "title": "Address" } ] - }, - { - "title": "Other", - "name": "other", - "match": { - "type": ["line", "area"], - "tags": {} - }, - "icon": "square-stroked", - "form": [] - }, - { - "title": "Other", - "name": "other", - "match": { - "type": ["node"], - "tags": {} - }, - "icon": "marker-stroked", - "form": [] } ], diff --git a/screenshot.jpg b/screenshot.jpg deleted file mode 100644 index 03d04ea17..000000000 Binary files a/screenshot.jpg and /dev/null differ diff --git a/test/index.html b/test/index.html index 1a5620266..b8ed08aba 100644 --- a/test/index.html +++ b/test/index.html @@ -26,7 +26,6 @@ - @@ -78,6 +77,7 @@ + @@ -96,7 +96,7 @@ - + @@ -180,7 +180,7 @@ - + diff --git a/test/index_packaged.html b/test/index_packaged.html index c7d8ad7de..c8b6984fe 100644 --- a/test/index_packaged.html +++ b/test/index_packaged.html @@ -38,7 +38,7 @@ - + diff --git a/test/spec/actions/delete_relation.js b/test/spec/actions/delete_relation.js index c96909f3f..28219601a 100644 --- a/test/spec/actions/delete_relation.js +++ b/test/spec/actions/delete_relation.js @@ -14,4 +14,63 @@ describe("iD.actions.DeleteRelation", function () { graph = action(iD.Graph([a, b, parent])); expect(graph.entity(parent.id).members).to.eql([{ id: b.id }]); }); + + it("deletes member nodes not referenced by another parent", function() { + var node = iD.Node(), + relation = iD.Relation({members: [{id: node.id}]}), + action = iD.actions.DeleteRelation(relation.id), + graph = action(iD.Graph([node, relation])); + expect(graph.entity(node.id)).to.be.undefined; + }); + + it("does not delete member nodes referenced by another parent", function() { + var node = iD.Node(), + way = iD.Way({nodes: [node.id]}), + relation = iD.Relation({members: [{id: node.id}]}), + action = iD.actions.DeleteRelation(relation.id), + graph = action(iD.Graph([node, way, relation])); + expect(graph.entity(node.id)).not.to.be.undefined; + }); + + it("does not delete member nodes with interesting tags", function() { + var node = iD.Node({tags: {highway: 'traffic_signals'}}), + relation = iD.Relation({members: [{id: node.id}]}), + action = iD.actions.DeleteRelation(relation.id), + graph = action(iD.Graph([node, relation])); + expect(graph.entity(node.id)).not.to.be.undefined; + }); + + it("deletes member ways not referenced by another parent", function() { + var way = iD.Way(), + relation = iD.Relation({members: [{id: way.id}]}), + action = iD.actions.DeleteRelation(relation.id), + graph = action(iD.Graph([way, relation])); + expect(graph.entity(way.id)).to.be.undefined; + }); + + it("does not delete member ways referenced by another parent", function() { + var way = iD.Way(), + relation1 = iD.Relation({members: [{id: way.id}]}), + relation2 = iD.Relation({members: [{id: way.id}]}), + action = iD.actions.DeleteRelation(relation1.id), + graph = action(iD.Graph([way, relation1, relation2])); + expect(graph.entity(way.id)).not.to.be.undefined; + }); + + it("does not delete member ways with interesting tags", function() { + var way = iD.Node({tags: {highway: 'residential'}}), + relation = iD.Relation({members: [{id: way.id}]}), + action = iD.actions.DeleteRelation(relation.id), + graph = action(iD.Graph([way, relation])); + expect(graph.entity(way.id)).not.to.be.undefined; + }); + + it("deletes nodes of deleted member ways", function() { + var node = iD.Node(), + way = iD.Way({nodes: [node.id]}), + relation = iD.Relation({members: [{id: way.id}]}), + action = iD.actions.DeleteRelation(relation.id), + graph = action(iD.Graph([node, way, relation])); + expect(graph.entity(node.id)).to.be.undefined; + }); }); diff --git a/test/spec/actions/delete_way.js b/test/spec/actions/delete_way.js index 130f5f759..a42ca3e38 100644 --- a/test/spec/actions/delete_way.js +++ b/test/spec/actions/delete_way.js @@ -47,7 +47,7 @@ describe("iD.actions.DeleteWay", function() { c = iD.Node(), way = iD.Way({nodes: [a.id, b.id, c.id, a.id]}), action = iD.actions.DeleteWay(way.id), - graph = iD.Graph([a, b, way]).update(action); + graph = iD.Graph([a, b, c, way]).update(action); expect(graph.entity(a.id)).to.be.undefined; expect(graph.entity(b.id)).to.be.undefined; expect(graph.entity(c.id)).to.be.undefined; diff --git a/test/spec/actions/move.js b/test/spec/actions/move.js new file mode 100644 index 000000000..2017409c7 --- /dev/null +++ b/test/spec/actions/move.js @@ -0,0 +1,51 @@ +describe("iD.actions.Move", function() { + it("moves all nodes in a way by the given amount", function() { + var node1 = iD.Node({loc: [0, 0]}), + node2 = iD.Node({loc: [5, 10]}), + way = iD.Way({nodes: [node1.id, node2.id]}), + delta = [2, 3], + projection = d3.geo.mercator(), + graph = iD.actions.Move([way.id], delta, projection)(iD.Graph([node1, node2, way])), + loc1 = graph.entity(node1.id).loc, + loc2 = graph.entity(node2.id).loc; + expect(loc1[0]).to.be.closeTo( 1.440, 0.001); + expect(loc1[1]).to.be.closeTo(-2.159, 0.001); + expect(loc2[0]).to.be.closeTo( 6.440, 0.001); + expect(loc2[1]).to.be.closeTo( 7.866, 0.001); + }); + + it("moves repeated nodes only once", function() { + var node = iD.Node({loc: [0, 0]}), + way = iD.Way({nodes: [node.id, node.id]}), + delta = [2, 3], + projection = d3.geo.mercator(), + graph = iD.actions.Move([way.id], delta, projection)(iD.Graph([node, way])), + loc = graph.entity(node.id).loc; + expect(loc[0]).to.be.closeTo( 1.440, 0.001); + expect(loc[1]).to.be.closeTo(-2.159, 0.001); + }); + + it("moves multiple ways", function() { + var node = iD.Node({loc: [0, 0]}), + way1 = iD.Way({nodes: [node.id]}), + way2 = iD.Way({nodes: [node.id]}), + delta = [2, 3], + projection = d3.geo.mercator(), + graph = iD.actions.Move([way1.id, way2.id], delta, projection)(iD.Graph([node, way1, way2])), + loc = graph.entity(node.id).loc; + expect(loc[0]).to.be.closeTo( 1.440, 0.001); + expect(loc[1]).to.be.closeTo(-2.159, 0.001); + }); + + it("moves leaf nodes of a relation", function() { + var node = iD.Node({loc: [0, 0]}), + way = iD.Way({nodes: [node.id]}), + relation = iD.Relation({members: [{id: way.id}]}), + delta = [2, 3], + projection = d3.geo.mercator(), + graph = iD.actions.Move([relation.id], delta, projection)(iD.Graph([node, way, relation])), + loc = graph.entity(node.id).loc; + expect(loc[0]).to.be.closeTo( 1.440, 0.001); + expect(loc[1]).to.be.closeTo(-2.159, 0.001); + }); +}); diff --git a/test/spec/actions/move_way.js b/test/spec/actions/move_way.js deleted file mode 100644 index 24bc81593..000000000 --- a/test/spec/actions/move_way.js +++ /dev/null @@ -1,27 +0,0 @@ -describe("iD.actions.MoveWay", function() { - it("moves all nodes in a way by the given amount", function() { - var node1 = iD.Node({loc: [0, 0]}), - node2 = iD.Node({loc: [5, 10]}), - way = iD.Way({nodes: [node1.id, node2.id]}), - delta = [2, 3], - projection = d3.geo.mercator(), - graph = iD.actions.MoveWay(way.id, delta, projection)(iD.Graph([node1, node2, way])), - loc1 = graph.entity(node1.id).loc, - loc2 = graph.entity(node2.id).loc; - expect(loc1[0]).to.be.closeTo( 1.440, 0.001); - expect(loc1[1]).to.be.closeTo(-2.159, 0.001); - expect(loc2[0]).to.be.closeTo( 6.440, 0.001); - expect(loc2[1]).to.be.closeTo( 7.866, 0.001); - }); - - it("moves repeated nodes only once", function() { - var node = iD.Node({loc: [0, 0]}), - way = iD.Way({nodes: [node.id, node.id]}), - delta = [2, 3], - projection = d3.geo.mercator(), - graph = iD.actions.MoveWay(way.id, delta, projection)(iD.Graph([node, way])), - loc = graph.entity(node.id).loc; - expect(loc[0]).to.be.closeTo( 1.440, 0.001); - expect(loc[1]).to.be.closeTo(-2.159, 0.001); - }); -}); diff --git a/test/spec/taginfo.js b/test/spec/taginfo.js index 61f7bcea0..0582401af 100644 --- a/test/spec/taginfo.js +++ b/test/spec/taginfo.js @@ -25,7 +25,7 @@ describe("iD.taginfo", function() { server.respond(); expect(query(server.requests[0].url)).to.eql( - {query: "amen", page: "1", rp: "6", sortname: "count_all", sortorder: "desc"}); + {query: "ame", page: "1", rp: "10", sortname: "count_all", sortorder: "desc"}); expect(callback).to.have.been.calledWith(null, [{"value":"amenity"}]); }); @@ -49,11 +49,11 @@ describe("iD.taginfo", function() { server.respondWith("GET", new RegExp("http://taginfo.openstreetmap.org/api/4/keys/all"), [200, { "Content-Type": "application/json" }, - '{"data":[{"count_all":5190337,"key":"amenity","count_all_fraction":1.0, "count_nodes_fraction":1.0},\ - {"count_all":1,"key":"amenityother","count_all_fraction":0.0, "count_nodes_fraction":1.0}]}']); + '{"data":[{"count_all":5190337,"count_nodes":500000,"key":"amenity","count_all_fraction":1.0, "count_nodes_fraction":1.0},\ + {"count_all":1,"key":"amenityother","count_all_fraction":0.0, "count_nodes":100}]}']); server.respond(); - expect(callback).to.have.been.calledWith(null, [{"value":"amenity"},{"value":"amenityother"}]); + expect(callback).to.have.been.calledWith(null, [{"value":"amenity"}]); }); }); diff --git a/test/spec/ui/inspector.js b/test/spec/ui/inspector.js index f572d77e0..2b075cd07 100644 --- a/test/spec/ui/inspector.js +++ b/test/spec/ui/inspector.js @@ -4,7 +4,7 @@ describe("iD.ui.Inspector", function () { entity, graph, context; function render() { - inspector = iD.ui.Inspector().context(context); + inspector = iD.ui.Inspector(context); element = d3.select('body') .append('div') .attr('id', 'inspector-wrap')