diff --git a/css/20_map.css b/css/20_map.css index 9f399ce4e..38106a223 100644 --- a/css/20_map.css +++ b/css/20_map.css @@ -14,17 +14,24 @@ pointer-events: none; } -/* Line/Area shadows and point/vertex targets are interactive */ +/* `.target` objects are interactive */ /* They can be picked up, clicked, hovered, or things can connect to them */ -.layer-areas path.shadow, -.layer-lines path.shadow { - pointer-events: stroke; -} -.layer-points-targets * { - pointer-events: all; +.node.target { + pointer-events: fill; + fill-opacity: 0.8; + fill: currentColor; + stroke: none; } -/* .active objects (currently being drawn or dragged) are not interactive */ +.way.target { + pointer-events: stroke; + fill: none; + stroke-width: 10; + stroke-opacity: 0.8; + stroke: currentColor; +} + +/* `.active` objects (currently being drawn or dragged) are not interactive */ /* This is important to allow the events to drop through to whatever is */ /* below them on the map, so you can still hover and connect to other things. */ .layer-osm .active { @@ -95,12 +102,6 @@ g.midpoint .shadow { fill-opacity: 0; } -.target { - color: rgba(0,0,0,0); - fill-opacity: 0.8; - fill: currentColor; -} - g.vertex.related:not(.selected) .shadow, g.vertex.hover:not(.selected) .shadow, g.midpoint.related:not(.selected) .shadow, diff --git a/css/70_fills.css b/css/70_fills.css index 34947b232..3587bbee8 100644 --- a/css/70_fills.css +++ b/css/70_fills.css @@ -33,4 +33,5 @@ .fill-partial path.area.fill { fill-opacity: 0; stroke-width: 60px; + pointer-events: visibleStroke; } diff --git a/css/80_app.css b/css/80_app.css index 83205fb0b..bc2f268c0 100644 --- a/css/80_app.css +++ b/css/80_app.css @@ -2864,6 +2864,7 @@ img.tile-removing { stroke-width: 1; } +.nocolor { color: rgba(0, 0, 0, 0); } .red { color: rgba(255, 0, 0, 0.75); } .green { color: rgba(0, 255, 0, 0.75); } .blue { color: rgba(0, 0, 255, 0.75); } diff --git a/modules/behavior/draw.js b/modules/behavior/draw.js index f4629d7f5..6a6ed2b2b 100644 --- a/modules/behavior/draw.js +++ b/modules/behavior/draw.js @@ -116,6 +116,9 @@ export function behaviorDraw(context) { function click() { var d = datum(); + + // Try to snap.. + // See also: `modes/drag_node.js doMove()` if (d.type === 'way') { var dims = context.map().dimensions(); var mouse = context.mouse(); diff --git a/modules/modes/drag_node.js b/modules/modes/drag_node.js index 2a5824170..90298a485 100644 --- a/modules/modes/drag_node.js +++ b/modules/modes/drag_node.js @@ -131,8 +131,8 @@ export function modeDragNode(context) { _dragEntity = entity; - // activeIDs generate no pointer events. This prevents the node or vertex - // being dragged from trying to connect to itself or its parent element. + // `.active` elements have `pointer-events: none`. + // This prevents the node or vertex being dragged from trying to connect to itself. _activeIDs = context.graph().parentWays(entity) .map(function(parent) { return parent.id; }); _activeIDs.push(entity.id); @@ -158,17 +158,25 @@ export function modeDragNode(context) { var currPoint = (d3_event && d3_event.point) || context.projection(_lastLoc); var currMouse = vecSub(currPoint, nudge); var loc = context.projection.invert(currMouse); - var d = datum(); if (!_nudgeInterval) { - // try to snap + // If we're not nudging at the edge of the viewport, try to snap.. + // See also `behavior/draw.js click()` + var d = datum(); + + // Snap to a node (not self) if (d.type === 'node' && d.id !== entity.id) { loc = d.loc; + + // Snap to a way (not an area fill) } else if (d.type === 'way' && !d3_select(d3_event.sourceEvent.target).classed('fill')) { - var childNodes = context.childNodes(d); - var childIDs = childNodes.map(function(node) { return node.id; }); - if (childIDs.indexOf(entity.id) === -1) { - loc = geoChooseEdge(childNodes, context.mouse(), context.projection).loc; + + // var childNodes = context.childNodes(d); + // var childIDs = childNodes.map(function(node) { return node.id; }); + var choice = geoChooseEdge(context.childNodes(d), context.mouse(), context.projection); + // (not along a segment adjacent to self) + if (entity.id !== d.nodes[choice.index - 1] && entity.id !== d.nodes[choice.index]) { + loc = choice.loc; } } } diff --git a/modules/svg/areas.js b/modules/svg/areas.js index 0f219738e..d540a08a0 100644 --- a/modules/svg/areas.js +++ b/modules/svg/areas.js @@ -41,7 +41,29 @@ export function svgAreas(projection, context) { } - return function drawAreas(selection, graph, entities, filter) { + function drawTargets(selection, graph, entities, filter) { + var fillClass = context.getDebug('target') ? 'pink ' : 'nocolor '; + var getPath = svgPath(projection, graph); + + var targets = selection.selectAll('.area.target') + .filter(filter) + .data(entities, function key(d) { return d.id; }); + + // exit + targets.exit() + .remove(); + + // enter/update + targets.enter() + .append('path') + .merge(targets) + .attr('d', getPath) + .attr('class', function(d) { return 'way area target ' + fillClass + d.id; }); + } + + + + function drawAreas(selection, graph, entities, filter) { var path = svgPath(projection, graph, true), areas = {}, multipolygon; @@ -99,7 +121,7 @@ export function svgAreas(projection, context) { .attr('d', path); - var layer = selection.selectAll('.layer-areas'); + var layer = selection.selectAll('.layer-areas .layer-areas-areas'); var areagroup = layer .selectAll('g.areagroup') @@ -145,5 +167,12 @@ export function svgAreas(projection, context) { }) .call(svgTagClasses()) .attr('d', path); - }; + + + // touch targets + selection.selectAll('.layer-areas .layer-areas-targets') + .call(drawTargets, graph, data.stroke, filter); + } + + return drawAreas; } diff --git a/modules/svg/lines.js b/modules/svg/lines.js index 1646f083b..caf11c294 100644 --- a/modules/svg/lines.js +++ b/modules/svg/lines.js @@ -36,13 +36,33 @@ export function svgLines(projection, context) { }; + function drawTargets(selection, graph, entities, filter) { + var fillClass = context.getDebug('target') ? 'pink ' : 'nocolor '; + var getPath = svgPath(projection, graph); + + var targets = selection.selectAll('.line.target') + .filter(filter) + .data(entities, function key(d) { return d.id; }); + + // exit + targets.exit() + .remove(); + + // enter/update + targets.enter() + .append('path') + .merge(targets) + .attr('d', getPath) + .attr('class', function(d) { return 'way line target ' + fillClass + d.id; }); + } + + function drawLines(selection, graph, entities, filter) { - function waystack(a, b) { - var selected = context.selectedIDs(), - scoreA = selected.indexOf(a.id) !== -1 ? 20 : 0, - scoreB = selected.indexOf(b.id) !== -1 ? 20 : 0; + var selected = context.selectedIDs(); + var scoreA = selected.indexOf(a.id) !== -1 ? 20 : 0; + var scoreB = selected.indexOf(b.id) !== -1 ? 20 : 0; if (a.tags.highway) { scoreA -= highway_stack[a.tags.highway]; } if (b.tags.highway) { scoreB -= highway_stack[b.tags.highway]; } @@ -91,15 +111,15 @@ export function svgLines(projection, context) { } - var getPath = svgPath(projection, graph), - ways = [], - pathdata = {}, - onewaydata = {}, - oldMultiPolygonOuters = {}; + var getPath = svgPath(projection, graph); + var ways = []; + var pathdata = {}; + var onewaydata = {}; + var oldMultiPolygonOuters = {}; for (var i = 0; i < entities.length; i++) { - var entity = entities[i], - outer = osmSimpleMultipolygonOuterMember(entity, graph); + var entity = entities[i]; + var outer = osmSimpleMultipolygonOuterMember(entity, graph); if (outer) { ways.push(entity.mergeTags(outer.tags)); oldMultiPolygonOuters[outer.id] = true; @@ -117,7 +137,7 @@ export function svgLines(projection, context) { }); - var layer = selection.selectAll('.layer-lines'); + var layer = selection.selectAll('.layer-lines .layer-lines-lines'); var layergroup = layer .selectAll('g.layergroup') @@ -164,8 +184,8 @@ export function svgLines(projection, context) { .selectAll('path') .filter(filter) .data( - function() { return onewaydata[this.parentNode.__data__] || []; }, - function(d) { return [d.id, d.index]; } + function data() { return onewaydata[this.parentNode.__data__] || []; }, + function key(d) { return [d.id, d.index]; } ); oneways.exit() @@ -181,6 +201,11 @@ export function svgLines(projection, context) { if (detected.ie) { oneways.each(function() { this.parentNode.insertBefore(this, this); }); } + + + // touch targets + selection.selectAll('.layer-lines .layer-lines-targets') + .call(drawTargets, graph, ways, filter); } diff --git a/modules/svg/midpoints.js b/modules/svg/midpoints.js index d6aaa0ab9..60ff03de8 100644 --- a/modules/svg/midpoints.js +++ b/modules/svg/midpoints.js @@ -17,7 +17,7 @@ export function svgMidpoints(projection, context) { function drawTargets(selection, graph, entities, filter) { - var debugClass = 'pink'; + var fillClass = context.getDebug('target') ? 'pink ' : 'nocolor '; var targets = selection.selectAll('.midpoint.target') .filter(filter) .data(entities, function key(d) { return d.id; }); @@ -30,10 +30,9 @@ export function svgMidpoints(projection, context) { targets.enter() .append('circle') .attr('r', 12) - .attr('class', function(d) { return 'midpoint target ' + d.id; }) .merge(targets) - .attr('transform', svgPointTransform(projection)) - .classed(debugClass, context.getDebug('target')); + .attr('class', function(d) { return 'node midpoint target ' + fillClass + d.id; }) + .attr('transform', svgPointTransform(projection)); } diff --git a/modules/svg/osm.js b/modules/svg/osm.js index 8a7defe94..db9a26a12 100644 --- a/modules/svg/osm.js +++ b/modules/svg/osm.js @@ -9,6 +9,18 @@ export function svgOsm(projection, context, dispatch) { .append('g') .attr('class', function(d) { return 'layer-osm layer-' + d; }); + selection.selectAll('.layer-areas').selectAll('.layer-areas-group') + .data(['areas', 'targets']) + .enter() + .append('g') + .attr('class', function(d) { return 'layer-areas-group layer-areas-' + d; }); + + selection.selectAll('.layer-lines').selectAll('.layer-lines-group') + .data(['lines', 'targets']) + .enter() + .append('g') + .attr('class', function(d) { return 'layer-lines-group layer-lines-' + d; }); + selection.selectAll('.layer-points').selectAll('.layer-points-group') .data(['points', 'midpoints', 'vertices', 'turns', 'targets']) .enter() diff --git a/modules/svg/points.js b/modules/svg/points.js index 8f38733e8..dfa32e575 100644 --- a/modules/svg/points.js +++ b/modules/svg/points.js @@ -22,7 +22,7 @@ export function svgPoints(projection, context) { function drawTargets(selection, graph, entities, filter) { - var debugClass = 'pink'; + var fillClass = context.getDebug('target') ? 'pink ' : 'nocolor '; var targets = selection.selectAll('.point.target') .filter(filter) .data(entities, function key(d) { return d.id; }); @@ -34,14 +34,13 @@ export function svgPoints(projection, context) { // enter/update targets.enter() .append('rect') - .attr('x', -15) - .attr('y', -30) - .attr('width', 30) - .attr('height', 36) - .attr('class', function(d) { return 'node point target ' + d.id; }) + .attr('x', -10) + .attr('y', -26) + .attr('width', 20) + .attr('height', 30) .merge(targets) - .attr('transform', svgPointTransform(projection)) - .classed(debugClass, context.getDebug('target')); + .attr('class', function(d) { return 'node point target ' + fillClass + d.id; }) + .attr('transform', svgPointTransform(projection)); } diff --git a/modules/svg/vertices.js b/modules/svg/vertices.js index e2231b8c4..35a1ac208 100644 --- a/modules/svg/vertices.js +++ b/modules/svg/vertices.js @@ -171,7 +171,7 @@ export function svgVertices(projection, context) { function drawTargets(selection, graph, entities, filter) { - var debugClass = 'pink'; + var fillClass = context.getDebug('target') ? 'pink ' : 'nocolor '; var targets = selection.selectAll('.vertex.target') .filter(filter) .data(entities, function key(d) { return d.id; }); @@ -184,10 +184,9 @@ export function svgVertices(projection, context) { targets.enter() .append('circle') .attr('r', radiuses.shadow[3]) // just use the biggest one for now - .attr('class', function(d) { return 'node vertex target ' + d.id; }) .merge(targets) - .attr('transform', svgPointTransform(projection)) - .classed(debugClass, context.getDebug('target')); + .attr('class', function(d) { return 'node vertex target ' + fillClass + d.id; }) + .attr('transform', svgPointTransform(projection)); }