From 70e54111146ed6b8b99b75d51ead36d859e2d165 Mon Sep 17 00:00:00 2001 From: John Firebaugh Date: Sat, 11 May 2013 14:16:19 -0700 Subject: [PATCH] Optimize vertex rendering * Cache icon * Append rather than insert * Do fewer things on update * Don't create a fill unless needed * Don't apply tag and member classes (never used) * Drop down to raw setAttribute (d3 is slow :trollface:) --- css/map.css | 6 +-- js/id/svg/vertices.js | 108 ++++++++++++++------------------------ test/spec/svg/vertices.js | 11 ---- 3 files changed, 39 insertions(+), 86 deletions(-) diff --git a/css/map.css b/css/map.css index 56a1e7240..b4428c77a 100644 --- a/css/map.css +++ b/css/map.css @@ -73,7 +73,7 @@ g.point.active, g.point.active * { /* vertices and midpoints */ g.vertex .fill { - fill: none; + fill: #000; } g.vertex .stroke { @@ -86,10 +86,6 @@ g.vertex.shared .stroke { fill: #aaa; } -g.vertex.tagged .fill { - fill: #000; -} - g.midpoint .fill { fill: #ddd; stroke: black; diff --git a/js/id/svg/vertices.js b/js/id/svg/vertices.js index d6e20d66d..cb69164f4 100644 --- a/js/id/svg/vertices.js +++ b/js/id/svg/vertices.js @@ -54,10 +54,10 @@ iD.svg.Vertices = function(projection, context) { }).length > 1; } - function draw(groups, graph, zoom) { - var group = groups.enter() - .insert('g', ':first-child') - .attr('class', function(d) { return 'node vertex ' + d.id; }); + function draw(groups, vertices, klass, graph, zoom) { + groups = groups.data(vertices, function(entity) { + return iD.Entity.key(entity) + ',' + zoom; + }); if (zoom < 17) { zoom = 0; @@ -67,79 +67,51 @@ iD.svg.Vertices = function(projection, context) { zoom = 2; } - group.append('circle') - .attr('class', function(d) { return 'node vertex shadow ' + d.id; }); - - group.append('circle') - .attr('class', function(d) { return 'node vertex stroke ' + d.id; }); - - groups.attr('transform', iD.svg.PointTransform(projection)) - .call(iD.svg.TagClasses()) - .call(iD.svg.MemberClasses(graph)) - .classed('tagged', function(entity) { return entity.hasInterestingTags(); }) - .classed('shared', function(entity) { return graph.isShared(entity); }); - + var icons = {}; function icon(entity) { - return zoom !== 0 && + if (entity.id in icons) return icons[entity.id]; + return icons[entity.id] = (zoom !== 0 && entity.hasInterestingTags() && - context.presets().match(entity, graph).icon; + context.presets().match(entity, graph).icon); } - function center(entity) { - if (icon(entity)) { - d3.select(this) - .attr('cx', 0.5) - .attr('cy', -0.5); - } else { - d3.select(this) - .attr('cy', 0) - .attr('cx', 0); + function circle(klass) { + var rads = radiuses[klass]; + return function(entity) { + var i = icon(entity), + c = i ? 0.5 : 0, + r = rads[i ? 3 : zoom]; + this.setAttribute('class', 'node vertex ' + klass + ' ' + entity.id); + this.setAttribute('cx', c); + this.setAttribute('cy', -c); + this.setAttribute('r', r); } } - groups.select('circle.shadow') - .each(center) - .attr('r', function(entity) { - return radiuses.shadow[icon(entity) ? 3 : zoom]; - }); + var enter = groups.enter().append('g') + .attr('class', function(d) { return 'node vertex ' + klass + ' ' + d.id; }); - groups.select('circle.stroke') - .each(center) - .attr('r', function(entity) { - return radiuses.stroke[icon(entity) ? 3 : zoom]; - }); + enter.append('circle') + .each(circle('shadow')); - // Each vertex gets either a circle or a use, depending - // on if it has a icon or not. + enter.append('circle') + .each(circle('stroke')); - var fill = groups.selectAll('circle.fill') - .data(function(entity) { - return icon(entity) ? [] : [entity]; - }, iD.Entity.key); - - fill.enter().append('circle') - .attr('class', function(d) { return 'node vertex fill ' + d.id; }) - .each(center) - .attr('r', radiuses.fill[zoom]); - - fill.exit() - .remove(); - - var use = groups.selectAll('use') - .data(function(entity) { - var i = icon(entity); - return i ? [i] : []; - }, function(d) { - return d; - }); - - use.enter().append('use') + // Vertices with icons get a `use`. + enter.filter(function(d) { return icon(d); }) + .append('use') .attr('transform', 'translate(-6, -6)') .attr('clip-path', 'url(#clip-square-12)') - .attr('xlink:href', function(icon) { return '#maki-' + icon + '-12'; }); + .attr('xlink:href', function(d) { return '#maki-' + icon(d) + '-12'; }); - use.exit() - .remove(); + // Vertices with tags get a `circle`. + enter.filter(function(d) { return !icon(d) && d.hasInterestingTags(); }) + .append('circle') + .each(circle('fill')); + + groups + .attr('transform', iD.svg.PointTransform(projection)) + .classed('shared', function(entity) { return graph.isShared(entity); }); groups.exit() .remove(); @@ -164,9 +136,7 @@ iD.svg.Vertices = function(projection, context) { surface.select('.layer-hit').selectAll('g.vertex.vertex-persistent') .filter(filter) - .data(vertices, iD.Entity.key) - .call(draw, graph, zoom) - .classed('vertex-persistent', true); + .call(draw, vertices, 'vertex-persistent', graph, zoom); drawHover(surface, graph, zoom); } @@ -175,9 +145,7 @@ iD.svg.Vertices = function(projection, context) { var hovered = hover ? siblingAndChildVertices([hover.id], graph) : {}; surface.select('.layer-hit').selectAll('g.vertex.vertex-hover') - .data(d3.values(hovered), iD.Entity.key) - .call(draw, graph, zoom) - .classed('vertex-hover', true); + .call(draw, d3.values(hovered), 'vertex-hover', graph, zoom); } drawVertices.drawHover = function(surface, graph, _, zoom) { diff --git a/test/spec/svg/vertices.js b/test/spec/svg/vertices.js index 4b29949c4..ae6c71d98 100644 --- a/test/spec/svg/vertices.js +++ b/test/spec/svg/vertices.js @@ -9,17 +9,6 @@ describe("iD.svg.Vertices", function () { .call(iD.svg.Surface(context)); }); - it("adds tag classes", function () { - var node = iD.Node({tags: {highway: "traffic_signals"}, loc: [0, 0]}), - way = iD.Way({nodes: [node.id]}), - graph = iD.Graph([node, way]); - - surface.call(iD.svg.Vertices(projection, context), graph, [node], 17); - - expect(surface.select('.vertex')).to.be.classed('tag-highway'); - expect(surface.select('.vertex')).to.be.classed('tag-highway-traffic_signals'); - }); - it("adds the .shared class to vertices that are members of two or more ways", function () { var node = iD.Node({loc: [0, 0]}), way1 = iD.Way({nodes: [node.id]}),