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:)
This commit is contained in:
John Firebaugh
2013-05-11 14:16:19 -07:00
parent 6060e886cb
commit 70e5411114
3 changed files with 39 additions and 86 deletions
+1 -5
View File
@@ -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;
+38 -70
View File
@@ -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) {
-11
View File
@@ -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]}),