diff --git a/css/map.css b/css/map.css
index fc54d7499..bf600b18d 100644
--- a/css/map.css
+++ b/css/map.css
@@ -154,6 +154,10 @@ path.multipolygon {
fill-rule: evenodd;
}
+path.area.member-type-multipolygon {
+ fill: none;
+}
+
path.area.selected {
stroke-width:4 !important;
}
diff --git a/index.html b/index.html
index 64d839d93..16dce5779 100644
--- a/index.html
+++ b/index.html
@@ -43,6 +43,7 @@
+
diff --git a/js/id/svg/areas.js b/js/id/svg/areas.js
index e77fe8a4d..5a3c31288 100644
--- a/js/id/svg/areas.js
+++ b/js/id/svg/areas.js
@@ -52,7 +52,8 @@ iD.svg.Areas = function(projection) {
paths
.order()
.attr('d', lineString)
- .call(iD.svg.TagClasses());
+ .call(iD.svg.TagClasses())
+ .call(iD.svg.MemberClasses(graph));
paths.exit()
.remove();
diff --git a/js/id/svg/lines.js b/js/id/svg/lines.js
index 167a9e75e..375a88f05 100644
--- a/js/id/svg/lines.js
+++ b/js/id/svg/lines.js
@@ -33,27 +33,27 @@ iD.svg.Lines = function(projection) {
return as - bs;
}
- function drawPaths(group, lines, filter, classes, lineString) {
- var paths = group.selectAll('path')
- .filter(filter)
- .data(lines, iD.Entity.key);
-
- paths.enter()
- .append('path')
- .attr('class', classes);
-
- paths
- .order()
- .attr('d', lineString)
- .call(iD.svg.TagClasses());
-
- paths.exit()
- .remove();
-
- return paths;
- }
-
return function drawLines(surface, graph, entities, filter) {
+ function drawPaths(group, lines, filter, classes, lineString) {
+ var paths = group.selectAll('path')
+ .filter(filter)
+ .data(lines, iD.Entity.key);
+
+ paths.enter()
+ .append('path')
+ .attr('class', classes);
+
+ paths
+ .order()
+ .attr('d', lineString)
+ .call(iD.svg.TagClasses())
+ .call(iD.svg.MemberClasses(graph));
+
+ paths.exit()
+ .remove();
+
+ return paths;
+ }
if (!alength) {
var arrow = surface.append('text').text(arrowtext);
diff --git a/js/id/svg/member_classes.js b/js/id/svg/member_classes.js
new file mode 100644
index 000000000..b675288c3
--- /dev/null
+++ b/js/id/svg/member_classes.js
@@ -0,0 +1,32 @@
+iD.svg.MemberClasses = function(graph) {
+ var tagClassRe = /^member-?/;
+
+ return function memberClassesSelection(selection) {
+ selection.each(function memberClassesEach(d, i) {
+ var classes, value = this.className;
+
+ if (value.baseVal !== undefined) value = value.baseVal;
+
+ classes = value.trim().split(/\s+/).filter(function(name) {
+ return name.length && !tagClassRe.test(name);
+ }).join(' ');
+
+ var relations = graph.parentRelations(d);
+
+ if (relations.length) {
+ classes += ' member';
+ }
+
+ relations.forEach(function (relation) {
+ classes += ' member-type-' + relation.tags.type;
+ classes += ' member-role-' + _.find(relation.members, function (member) { return member.id == d.id; }).role;
+ });
+
+ classes = classes.trim();
+
+ if (classes !== value) {
+ d3.select(this).attr('class', classes);
+ }
+ });
+ };
+};
diff --git a/js/id/svg/multipolygons.js b/js/id/svg/multipolygons.js
index bad07ff41..6330cf685 100644
--- a/js/id/svg/multipolygons.js
+++ b/js/id/svg/multipolygons.js
@@ -40,7 +40,8 @@ iD.svg.Multipolygons = function(projection) {
paths
.order()
.attr('d', lineString)
- .call(iD.svg.TagClasses());
+ .call(iD.svg.TagClasses())
+ .call(iD.svg.MemberClasses(graph));
paths.exit()
.remove();
diff --git a/js/id/svg/points.js b/js/id/svg/points.js
index 37c4bbf91..5862a5cb9 100644
--- a/js/id/svg/points.js
+++ b/js/id/svg/points.js
@@ -45,7 +45,8 @@ iD.svg.Points = function(projection) {
.attr('transform', 'translate(-8, -8)');
groups.attr('transform', iD.svg.PointTransform(projection))
- .call(iD.svg.TagClasses());
+ .call(iD.svg.TagClasses())
+ .call(iD.svg.MemberClasses(graph));
// Selecting the following implicitly
// sets the data (point entity) on the element
diff --git a/js/id/svg/vertices.js b/js/id/svg/vertices.js
index ff775fa3e..ce81dface 100644
--- a/js/id/svg/vertices.js
+++ b/js/id/svg/vertices.js
@@ -31,6 +31,7 @@ iD.svg.Vertices = function(projection) {
groups.attr('transform', iD.svg.PointTransform(projection))
.call(iD.svg.TagClasses())
+ .call(iD.svg.MemberClasses(graph))
.classed('shared', function(entity) { return graph.parentWays(entity).length > 1; });
// Selecting the following implicitly
diff --git a/test/index.html b/test/index.html
index 7f63be2a3..0777751d5 100644
--- a/test/index.html
+++ b/test/index.html
@@ -45,6 +45,7 @@
+
@@ -159,6 +160,8 @@
+
+
diff --git a/test/index_packaged.html b/test/index_packaged.html
index 042a0227e..070a2666b 100644
--- a/test/index_packaged.html
+++ b/test/index_packaged.html
@@ -64,6 +64,8 @@
+
+
diff --git a/test/spec/svg/areas.js b/test/spec/svg/areas.js
index 112e25d03..4f984540a 100644
--- a/test/spec/svg/areas.js
+++ b/test/spec/svg/areas.js
@@ -28,6 +28,18 @@ describe("iD.svg.Areas", function () {
expect(surface.select('.area')).to.be.classed('tag-building-yes');
});
+ it("adds member classes", function () {
+ var area = iD.Way({tags: {area: 'yes'}}),
+ relation = iD.Relation({members: [{id: area.id, role: 'outer'}], tags: {type: 'multipolygon'}}),
+ graph = iD.Graph([area, relation]);
+
+ surface.call(iD.svg.Areas(projection), graph, [area], filter);
+
+ expect(surface.select('.area')).to.be.classed('member');
+ expect(surface.select('.area')).to.be.classed('member-role-outer');
+ expect(surface.select('.area')).to.be.classed('member-type-multipolygon');
+ });
+
it("preserves non-area paths", function () {
var area = iD.Way({tags: {area: 'yes'}}),
graph = iD.Graph([area]);
diff --git a/test/spec/svg/member_classes.js b/test/spec/svg/member_classes.js
new file mode 100644
index 000000000..651ba2342
--- /dev/null
+++ b/test/spec/svg/member_classes.js
@@ -0,0 +1,54 @@
+describe("iD.svg.MemberClasses", function () {
+ var selection;
+
+ beforeEach(function () {
+ selection = d3.select(document.createElementNS('http://www.w3.org/2000/svg', 'g'));
+ });
+
+ it("adds no classes to elements that aren't a member of any relations", function() {
+ var node = iD.Node(),
+ graph = iD.Graph([node]);
+
+ selection
+ .datum(node)
+ .call(iD.svg.MemberClasses(graph));
+
+ expect(selection.attr('class')).to.equal(null);
+ });
+
+ it("adds tags for member, role, and type", function() {
+ var node = iD.Node(),
+ relation = iD.Relation({members: [{id: node.id, role: 'r'}], tags: {type: 't'}}),
+ graph = iD.Graph([node, relation]);
+
+ selection
+ .datum(node)
+ .call(iD.svg.MemberClasses(graph));
+
+ expect(selection.attr('class')).to.equal('member member-type-t member-role-r');
+ });
+
+ it('removes classes for tags that are no longer present', function() {
+ var node = iD.Entity(),
+ graph = iD.Graph([node]);
+
+ selection
+ .attr('class', 'member member-type-t member-role-r')
+ .datum(node)
+ .call(iD.svg.MemberClasses(graph));
+
+ expect(selection.attr('class')).to.equal('');
+ });
+
+ it("preserves existing non-'member-'-prefixed classes", function() {
+ var node = iD.Entity(),
+ graph = iD.Graph([node]);
+
+ selection
+ .attr('class', 'selected')
+ .datum(node)
+ .call(iD.svg.MemberClasses(graph));
+
+ expect(selection.attr('class')).to.equal('selected');
+ });
+});