This appears to cause a significant performance regression as shown in the
rotating Ocean example (http://bl.ocks.org/mbostock/6713736): performance went
from 60 FPS to 20 FPS.
This commit is contained in:
Mike Bostock 2013-10-10 17:19:13 -07:00
Родитель af6a4e027e
Коммит e3c3e5487c
7 изменённых файлов: 183 добавлений и 255 удалений

164
d3.js поставляемый
Просмотреть файл

@ -2616,15 +2616,16 @@ d3 = function() {
function d3_true() {
return true;
}
function d3_geo_clipPolygon(segments, compare, clipStartInside, pointInPolygon, interpolate, listener) {
var subject = [], clip = [], rings = [], n = segments.length;
for (var i = 0; i < n; ++i) {
var segment = segments[i];
if ((m = segment.length - 1) <= 0) continue;
var m, p0 = segment[0], p1 = segment[m];
function d3_geo_clipPolygon(segments, compare, clipStartInside, interpolate, listener) {
var subject = [], clip = [];
segments.forEach(function(segment) {
if ((n = segment.length - 1) <= 0) return;
var n, p0 = segment[0], p1 = segment[n];
if (d3_geo_sphericalEqual(p0, p1)) {
rings.push(segment);
continue;
listener.lineStart();
for (var i = 0; i < n; ++i) listener.point((p0 = segment[i])[0], p0[1]);
listener.lineEnd();
return;
}
var a = new d3_geo_clipPolygonIntersection(p0, segment, null, true), b = new d3_geo_clipPolygonIntersection(p0, null, a, false);
a.o = b;
@ -2635,86 +2636,45 @@ d3 = function() {
a.o = b;
subject.push(a);
clip.push(b);
});
clip.sort(compare);
d3_geo_clipPolygonLinkCircular(subject);
d3_geo_clipPolygonLinkCircular(clip);
if (!subject.length) return;
for (var i = 0, entry = clipStartInside, n = clip.length; i < n; ++i) {
clip[i].e = entry = !entry;
}
if (subject.length) {
clip.sort(compare);
d3_geo_clipPolygonLinkCircular(subject);
d3_geo_clipPolygonLinkCircular(clip);
for (var i = 0, entry = clipStartInside, n = clip.length; i < n; ++i) {
clip[i].e = entry = !entry;
}
var start = subject[0], listener_ = listener, point;
if (rings.length) listener = d3_geo_clipBufferListener();
while (1) {
var current = start, isSubject = true;
while (current.v) if ((current = current.n) === start) break;
if (current.v) break;
listener.polygonStart();
listener.lineStart();
do {
current.v = current.o.v = true;
if (current.e) {
if (isSubject) {
for (var i = 0, points = current.z, n = points.length; i < n; ++i) {
listener.point((point = points[i])[0], point[1]);
}
} else {
interpolate(current.x, current.n.x, 1, listener);
}
current = current.n;
var start = subject[0], points, point;
while (1) {
var current = start, isSubject = true;
while (current.v) if ((current = current.n) === start) return;
points = current.z;
listener.lineStart();
do {
current.v = current.o.v = true;
if (current.e) {
if (isSubject) {
for (var i = 0, n = points.length; i < n; ++i) listener.point((point = points[i])[0], point[1]);
} else {
if (isSubject) {
for (var points = current.z, i = points.length; --i >= 0; ) {
listener.point((point = points[i])[0], point[1]);
}
} else {
interpolate(current.x, current.p.x, -1, listener);
}
current = current.p;
interpolate(current.x, current.n.x, 1, listener);
}
current = current.o;
isSubject = !isSubject;
} while (!current.v);
listener.lineEnd();
listener.polygonEnd();
}
if (n = rings.length) {
var exteriors = listener.buffer(), exteriorPolygon = [ null ];
listener = listener_;
for (var j = 0, m = exteriors.length; j < m; ++j) {
var exterior = exteriorPolygon[0] = exteriors[j];
listener.polygonStart();
d3_geo_clipPolygonStreamRing(exterior, listener);
for (var i = 0; i < n; ++i) {
var ring = rings[i];
if (ring && pointInPolygon(ring[0], exteriorPolygon)) {
d3_geo_clipPolygonStreamRing(ring, listener);
rings[i] = null;
}
current = current.n;
} else {
if (isSubject) {
points = current.p.z;
for (var i = points.length - 1; i >= 0; --i) listener.point((point = points[i])[0], point[1]);
} else {
interpolate(current.x, current.p.x, -1, listener);
}
listener.polygonEnd();
current = current.p;
}
}
} else if ((n = rings.length) || clipStartInside) {
listener.polygonStart();
if (clipStartInside) {
listener.lineStart();
interpolate(null, null, 1, listener);
listener.lineEnd();
}
for (var i = 0; i < n; ++i) {
d3_geo_clipPolygonStreamRing(rings[i], listener);
}
listener.polygonEnd();
current = current.o;
points = current.z;
isSubject = !isSubject;
} while (!current.v);
listener.lineEnd();
}
}
function d3_geo_clipPolygonStreamRing(ring, listener) {
listener.lineStart();
for (var i = 0, n = ring.length - 1, p; i < n; ++i) {
listener.point((p = ring[i])[0], p[1]);
}
listener.lineEnd();
}
function d3_geo_clipPolygonLinkCircular(array) {
if (!(n = array.length)) return;
var n, i = 0, a = array[0], b;
@ -2747,12 +2707,22 @@ d3 = function() {
clip.lineEnd = ringEnd;
segments = [];
polygon = [];
listener.polygonStart();
},
polygonEnd: function() {
clip.point = point;
clip.lineStart = lineStart;
clip.lineEnd = lineEnd;
d3_geo_clipPolygon(d3.merge(segments), d3_geo_clipSort, d3_geo_pointInPolygon(rotatedClipStart, polygon), d3_geo_pointInPolygon, interpolate, listener);
segments = d3.merge(segments);
var clipStartInside = d3_geo_pointInPolygon(rotatedClipStart, polygon);
if (segments.length) {
d3_geo_clipPolygon(segments, d3_geo_clipSort, clipStartInside, interpolate, listener);
} else if (clipStartInside) {
listener.lineStart();
interpolate(null, null, 1, listener);
listener.lineEnd();
}
listener.polygonEnd();
segments = polygon = null;
},
sphere: function() {
@ -2793,11 +2763,19 @@ d3 = function() {
function ringEnd() {
pointRing(ring[0][0], ring[0][1]);
ringListener.lineEnd();
var clean = ringListener.clean(), ringSegments = buffer.buffer(), n = ringSegments.length;
var clean = ringListener.clean(), ringSegments = buffer.buffer(), segment, n = ringSegments.length;
ring.pop();
polygon.push(ring);
ring = null;
if (!n) return;
if (clean & 1) {
segment = ringSegments[0];
var n = segment.length - 1, i = -1, point;
listener.lineStart();
while (++i < n) listener.point((point = segment[i])[0], point[1]);
listener.lineEnd();
return;
}
if (n > 1 && clean & 2) ringSegments.push(ringSegments.pop().concat(ringSegments.shift()));
segments.push(ringSegments.filter(d3_geo_clipSegmentLength1));
}
@ -2817,8 +2795,6 @@ d3 = function() {
line.push([ λ, φ ]);
},
lineEnd: d3_noop,
polygonStart: d3_noop,
polygonEnd: d3_noop,
buffer: function() {
var buffer = lines;
lines = [];
@ -3111,11 +3087,25 @@ d3 = function() {
clean = true;
},
polygonEnd: function() {
d3_geo_clipPolygon(d3.merge(segments), compare, pointInPolygon([ x0, y1 ], polygon), pointInPolygon, interpolate, listener = listener_);
listener = listener_;
segments = d3.merge(segments);
var clipStartInside = insidePolygon([ x0, y1 ]), inside = clean && clipStartInside, visible = segments.length;
if (inside || visible) {
listener.polygonStart();
if (inside) {
listener.lineStart();
interpolate(null, null, 1, listener);
listener.lineEnd();
}
if (visible) {
d3_geo_clipPolygon(segments, compare, clipStartInside, interpolate, listener);
}
listener.polygonEnd();
}
segments = polygon = ring = null;
}
};
function pointInPolygon(p, polygon) {
function insidePolygon(p) {
var wn = 0, n = polygon.length, y = p[1];
for (var i = 0; i < n; ++i) {
for (var j = 1, v = polygon[i], m = v.length, a = v[0], b; j < m; ++j) {

10
d3.min.js поставляемый

Различия файлов скрыты, потому что одна или несколько строк слишком длинны

Просмотреть файл

@ -48,12 +48,28 @@ function d3_geo_clipExtent(x0, y0, x1, y1) {
clean = true;
},
polygonEnd: function() {
d3_geo_clipPolygon(d3.merge(segments), compare, pointInPolygon([x0, y1], polygon), pointInPolygon, interpolate, listener = listener_);
listener = listener_;
segments = d3.merge(segments);
var clipStartInside = insidePolygon([x0, y1]),
inside = clean && clipStartInside,
visible = segments.length;
if (inside || visible) {
listener.polygonStart();
if (inside) {
listener.lineStart();
interpolate(null, null, 1, listener);
listener.lineEnd();
}
if (visible) {
d3_geo_clipPolygon(segments, compare, clipStartInside, interpolate, listener);
}
listener.polygonEnd();
}
segments = polygon = ring = null;
}
};
function pointInPolygon(p, polygon) {
function insidePolygon(p) {
var wn = 0, // the winding number counter
n = polygon.length,
y = p[1];

Просмотреть файл

@ -1,27 +1,26 @@
import "../math/trigonometry";
import "spherical";
// General spherical polygon clipping algorithm.
// Given a polygon that has been cut into visible line segments, rejoins the
// segments by interpolating along the clip edge where necessary.
function d3_geo_clipPolygon(segments, compare, clipStartInside, pointInPolygon, interpolate, listener) {
// General spherical polygon clipping algorithm: takes a polygon, cuts it into
// visible line segments and rejoins the segments by interpolating along the
// clip edge.
function d3_geo_clipPolygon(segments, compare, clipStartInside, interpolate, listener) {
var subject = [],
clip = [],
rings = [],
n = segments.length;
clip = [];
for (var i = 0; i < n; ++i) {
var segment = segments[i];
if ((m = segment.length - 1) <= 0) continue;
var m, p0 = segment[0], p1 = segment[m];
segments.forEach(function(segment) {
if ((n = segment.length - 1) <= 0) return;
var n, p0 = segment[0], p1 = segment[n];
// If the first and last points of a segment are coincident, then treat as
// a closed ring.
// TODO if all rings are closed, then the winding order of the exterior
// ring should be checked.
if (d3_geo_sphericalEqual(p0, p1)) {
rings.push(segment);
continue;
listener.lineStart();
for (var i = 0; i < n; ++i) listener.point((p0 = segment[i])[0], p0[1]);
listener.lineEnd();
return;
}
var a = new d3_geo_clipPolygonIntersection(p0, segment, null, true),
@ -34,107 +33,50 @@ function d3_geo_clipPolygon(segments, compare, clipStartInside, pointInPolygon,
a.o = b;
subject.push(a);
clip.push(b);
});
clip.sort(compare);
d3_geo_clipPolygonLinkCircular(subject);
d3_geo_clipPolygonLinkCircular(clip);
if (!subject.length) return;
for (var i = 0, entry = clipStartInside, n = clip.length; i < n; ++i) {
clip[i].e = entry = !entry;
}
// If there are any segments to be joined…
if (subject.length) {
clip.sort(compare);
d3_geo_clipPolygonLinkCircular(subject);
d3_geo_clipPolygonLinkCircular(clip);
// Mark intersection points as alternating between entering and exiting.
for (var i = 0, entry = clipStartInside, n = clip.length; i < n; ++i) {
clip[i].e = entry = !entry;
}
var start = subject[0],
listener_ = listener,
point;
// If there are closed rings, then buffer the rejoined segments so they can
// be output with the correct interior rings later.
if (rings.length) listener = d3_geo_clipBufferListener();
while (1) {
// Find first unvisited intersection.
var current = start,
isSubject = true;
while (current.v) if ((current = current.n) === start) break;
if (current.v) break;
listener.polygonStart();
listener.lineStart();
do {
current.v = current.o.v = true;
if (current.e) {
if (isSubject) {
for (var i = 0, points = current.z, n = points.length; i < n; ++i) {
listener.point((point = points[i])[0], point[1]);
}
} else {
interpolate(current.x, current.n.x, 1, listener);
}
current = current.n;
var start = subject[0],
points,
point;
while (1) {
// Find first unvisited intersection.
var current = start,
isSubject = true;
while (current.v) if ((current = current.n) === start) return;
points = current.z;
listener.lineStart();
do {
current.v = current.o.v = true;
if (current.e) {
if (isSubject) {
for (var i = 0, n = points.length; i < n; ++i) listener.point((point = points[i])[0], point[1]);
} else {
if (isSubject) {
for (var points = current.z, i = points.length; --i >= 0;) {
listener.point((point = points[i])[0], point[1]);
}
} else {
interpolate(current.x, current.p.x, -1, listener);
}
current = current.p;
interpolate(current.x, current.n.x, 1, listener);
}
current = current.o;
isSubject = !isSubject;
} while (!current.v);
listener.lineEnd();
listener.polygonEnd();
}
if (n = rings.length) {
var exteriors = listener.buffer(),
exteriorPolygon = [null];
listener = listener_;
for (var j = 0, m = exteriors.length; j < m; ++j) {
var exterior = exteriorPolygon[0] = exteriors[j];
listener.polygonStart();
d3_geo_clipPolygonStreamRing(exterior, listener);
for (var i = 0; i < n; ++i) {
var ring = rings[i];
if (ring && pointInPolygon(ring[0], exteriorPolygon)) {
d3_geo_clipPolygonStreamRing(ring, listener);
rings[i] = null;
}
current = current.n;
} else {
if (isSubject) {
points = current.p.z;
for (var i = points.length - 1; i >= 0; --i) listener.point((point = points[i])[0], point[1]);
} else {
interpolate(current.x, current.p.x, -1, listener);
}
listener.polygonEnd();
current = current.p;
}
}
current = current.o;
points = current.z;
isSubject = !isSubject;
} while (!current.v);
listener.lineEnd();
}
// Otherwise, there are no intersections.
else if ((n = rings.length) || clipStartInside) {
listener.polygonStart();
// If the clip polygon is inside the subject polygon, then the clip polygon
// becomes the exterior.
if (clipStartInside) {
listener.lineStart();
interpolate(null, null, 1, listener);
listener.lineEnd();
}
for (var i = 0; i < n; ++i) {
d3_geo_clipPolygonStreamRing(rings[i], listener);
}
listener.polygonEnd();
}
}
function d3_geo_clipPolygonStreamRing(ring, listener) {
listener.lineStart();
for (var i = 0, n = ring.length - 1, p; i < n; ++i) {
listener.point((p = ring[i])[0], p[1]);
}
listener.lineEnd();
}
function d3_geo_clipPolygonLinkCircular(array) {

Просмотреть файл

@ -18,14 +18,23 @@ function d3_geo_clip(pointVisible, clipLine, interpolate, clipStart) {
clip.lineEnd = ringEnd;
segments = [];
polygon = [];
listener.polygonStart();
},
polygonEnd: function() {
clip.point = point;
clip.lineStart = lineStart;
clip.lineEnd = lineEnd;
d3_geo_clipPolygon(d3.merge(segments), d3_geo_clipSort, d3_geo_pointInPolygon(rotatedClipStart, polygon), d3_geo_pointInPolygon, interpolate, listener);
segments = d3.merge(segments);
var clipStartInside = d3_geo_pointInPolygon(rotatedClipStart, polygon);
if (segments.length) {
d3_geo_clipPolygon(segments, d3_geo_clipSort, clipStartInside, interpolate, listener);
} else if (clipStartInside) {
listener.lineStart();
interpolate(null, null, 1, listener);
listener.lineEnd();
}
listener.polygonEnd();
segments = polygon = null;
},
sphere: function() {
@ -72,6 +81,7 @@ function d3_geo_clip(pointVisible, clipLine, interpolate, clipStart) {
var clean = ringListener.clean(),
ringSegments = buffer.buffer(),
segment,
n = ringSegments.length;
ring.pop();
@ -80,6 +90,18 @@ function d3_geo_clip(pointVisible, clipLine, interpolate, clipStart) {
if (!n) return;
// No intersections.
if (clean & 1) {
segment = ringSegments[0];
var n = segment.length - 1,
i = -1,
point;
listener.lineStart();
while (++i < n) listener.point((point = segment[i])[0], point[1]);
listener.lineEnd();
return;
}
// Rejoin connected segments.
// TODO reuse bufferListener.rejoin()?
if (n > 1 && clean & 2) ringSegments.push(ringSegments.pop().concat(ringSegments.shift()));
@ -102,8 +124,6 @@ function d3_geo_clipBufferListener() {
lineStart: function() { lines.push(line = []); },
point: function(λ, φ) { line.push([λ, φ]); },
lineEnd: d3_noop,
polygonStart: d3_noop,
polygonEnd: d3_noop,
buffer: function() {
var buffer = lines;
lines = [];

Просмотреть файл

@ -1,5 +1,4 @@
var vows = require("vows"),
_ = require("../../"),
load = require("../load"),
assert = require("../assert");
@ -144,45 +143,6 @@ suite.addBatch({
{type: "lineEnd"},
{type: "polygonEnd"}
]);
},
"a polygon that should be split into two polygons, each with a hole": function(d3) {
var clip = d3.geo.clipExtent().extent([[240, 110], [720, 375]]),
stream = clip.stream(testContext);
_.geo.stream({type: "Polygon", coordinates: [
[[300, 30], [660, 30], [660, 200], [570, 200], [570, 80], [390, 80], [390, 200], [300, 200], [300, 30]],
[[330, 140], [330, 170], [360, 170], [360, 140], [330, 140]],
[[600, 140], [600, 170], [630, 170], [630, 140], [600, 140]]
]}, stream);
assert.deepEqual(testContext.buffer(), [
{type: "polygonStart"},
{type: "lineStart"},
{type: "point", x: 660, y: 110},
{type: "point", x: 660, y: 200},
{type: "point", x: 570, y: 200},
{type: "point", x: 570, y: 110},
{type: "lineEnd"},
{type: "lineStart"},
{type: "point", x: 600, y: 140},
{type: "point", x: 600, y: 170},
{type: "point", x: 630, y: 170},
{type: "point", x: 630, y: 140},
{type: "lineEnd"},
{type: "polygonEnd"},
{type: "polygonStart"},
{type: "lineStart"},
{type: "point", x: 390, y: 110},
{type: "point", x: 390, y: 200},
{type: "point", x: 300, y: 200},
{type: "point", x: 300, y: 110},
{type: "lineEnd"},
{type: "lineStart"},
{type: "point", x: 330, y: 140},
{type: "point", x: 330, y: 170},
{type: "point", x: 360, y: 170},
{type: "point", x: 360, y: 140},
{type: "lineEnd"},
{type: "polygonEnd"}
]);
}
}
}

Просмотреть файл

@ -401,8 +401,8 @@ suite.addBatch({
.precision(0)
.clipAngle(90))({type: "Polygon", coordinates: [pole]});
assert.deepEqual(testContext.buffer().filter(function(d) { return d.type === "moveTo"; }), [
{type: "moveTo", x: 87, y: 700},
{type: "moveTo", x: 510, y: 160}
{type: "moveTo", x: 510, y: 160},
{type: "moveTo", x: 87, y: 700}
]);
}
},
@ -506,8 +506,8 @@ suite.addBatch({
"renders a small circle of 120° in two parts": function(p) {
p(_.geo.circle().angle(120)());
assert.deepEqual(testContext.buffer().filter(function(d) { return d.type === "moveTo"; }), [
{type: "moveTo", x: 87, y: 700},
{type: "moveTo", x: 276, y: 493}
{type: "moveTo", x: 276, y: 493},
{type: "moveTo", x: 87, y: 700}
]);
}
},
@ -580,9 +580,9 @@ suite.addBatch({
"renders stripes": function(p) {
p(stripes(80, -80));
assert.deepEqual(testContext.buffer().filter(function(d) { return d.type === "moveTo"; }), [
{type: "moveTo", x: 1331, y: 259},
{type: "moveTo", x: -420, y: -150},
{type: "moveTo", x: -420, y: 650}
{type: "moveTo", x: -420, y: 650},
{type: "moveTo", x: 1331, y: 259}
]);
}
},
@ -600,8 +600,8 @@ suite.addBatch({
"renders stripes": function(p) {
p(stripes(80, -80));
assert.deepEqual(testContext.buffer().filter(function(d) { return d.type === "moveTo"; }), [
{type: "moveTo", x: 1350, y: 210},
{type: "moveTo", x: 480, y: 200}
{type: "moveTo", x: 480, y: 200},
{type: "moveTo", x: 1350, y: 210}
]);
}
},