Support both open & closed polygons.

Rather than converting closed polygons to open polygons on input (which could
potentially be destructive), tweak the polygon methods slightly so that they
work on either input.

This commit also changes d3.geom.polygon to use a prototype, much like
d3.selection, rather than creating every method as a closure. This makes it much
faster to construct a polygon.
This commit is contained in:
Mike Bostock 2013-06-28 08:29:16 -07:00
Родитель 5596e4fbc3
Коммит fdd6c62050
9 изменённых файлов: 168 добавлений и 145 удалений

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

@ -380,11 +380,6 @@ d3 = function() {
} catch (e) {
d3_array = d3_arrayCopy;
}
var d3_arraySubclass = [].__proto__ ? function(array, prototype) {
array.__proto__ = prototype;
} : function(array, prototype) {
for (var property in prototype) array[property] = prototype[property];
};
function d3_noop() {}
d3.dispatch = function() {
var dispatch = new d3_dispatch(), i = -1, n = arguments.length;
@ -458,8 +453,13 @@ d3 = function() {
return s.replace(d3_requote_re, "\\$&");
};
var d3_requote_re = /[\\\^\$\*\+\?\|\[\]\(\)\.\{\}]/g;
var d3_subclass = {}.__proto__ ? function(object, prototype) {
object.__proto__ = prototype;
} : function(object, prototype) {
for (var property in prototype) object[property] = prototype[property];
};
function d3_selection(groups) {
d3_arraySubclass(groups, d3_selectionPrototype);
d3_subclass(groups, d3_selectionPrototype);
return groups;
}
var d3_select = function(s, n) {
@ -891,7 +891,7 @@ d3 = function() {
return n;
};
function d3_selection_enter(selection) {
d3_arraySubclass(selection, d3_selection_enterPrototype);
d3_subclass(selection, d3_selection_enterPrototype);
return selection;
}
var d3_selection_enterPrototype = [];
@ -4284,52 +4284,55 @@ d3 = function() {
return (f - b) * (c - a) - (d - b) * (e - a) > 0;
}
d3.geom.polygon = function(coordinates) {
coordinates.area = function() {
var i = -1, n = coordinates.length, a, b = coordinates[n - 1], area = 0;
while (++i < n) {
a = b;
b = coordinates[i];
area += a[1] * b[0] - a[0] * b[1];
}
return area * .5;
};
coordinates.centroid = function(k) {
var i = -1, n = coordinates.length, x = 0, y = 0, a, b = coordinates[n - 1], c;
if (!arguments.length) k = -1 / (6 * coordinates.area());
while (++i < n) {
a = b;
b = coordinates[i];
c = a[0] * b[1] - b[0] * a[1];
x += (a[0] + b[0]) * c;
y += (a[1] + b[1]) * c;
}
return [ x * k, y * k ];
};
coordinates.clip = function(subject) {
var input, i = -1, n = coordinates.length, j, m, a = coordinates[n - 1], b, c, d;
while (++i < n) {
input = subject.slice();
subject.length = 0;
b = coordinates[i];
c = input[(m = input.length) - 1];
j = -1;
while (++j < m) {
d = input[j];
if (d3_geom_polygonInside(d, a, b)) {
if (!d3_geom_polygonInside(c, a, b)) {
subject.push(d3_geom_polygonIntersect(c, d, a, b));
}
subject.push(d);
} else if (d3_geom_polygonInside(c, a, b)) {
d3_subclass(coordinates, d3_geom_polygonPrototype);
return coordinates;
};
var d3_geom_polygonPrototype = d3.geom.polygon.prototype = [];
d3_geom_polygonPrototype.area = function() {
var i = -1, n = this.length, a, b = this[n - 1], area = 0;
while (++i < n) {
a = b;
b = this[i];
area += a[1] * b[0] - a[0] * b[1];
}
return area * .5;
};
d3_geom_polygonPrototype.centroid = function(k) {
var i = -1, n = this.length, x = 0, y = 0, a, b = this[n - 1], c;
if (!arguments.length) k = -1 / (6 * this.area());
while (++i < n) {
a = b;
b = this[i];
c = a[0] * b[1] - b[0] * a[1];
x += (a[0] + b[0]) * c;
y += (a[1] + b[1]) * c;
}
return [ x * k, y * k ];
};
d3_geom_polygonPrototype.clip = function(subject) {
var input, closed = d3_geom_polygonClosed(subject), i = -1, n = this.length - d3_geom_polygonClosed(this), j, m, a = this[n - 1], b, c, d;
while (++i < n) {
input = subject.slice();
subject.length = 0;
b = this[i];
c = input[(m = input.length - closed) - 1];
j = -1;
while (++j < m) {
d = input[j];
if (d3_geom_polygonInside(d, a, b)) {
if (!d3_geom_polygonInside(c, a, b)) {
subject.push(d3_geom_polygonIntersect(c, d, a, b));
}
c = d;
subject.push(d);
} else if (d3_geom_polygonInside(c, a, b)) {
subject.push(d3_geom_polygonIntersect(c, d, a, b));
}
a = b;
c = d;
}
return subject;
};
return coordinates;
if (closed) subject.push(subject[0]);
a = b;
}
return subject;
};
function d3_geom_polygonInside(p, a, b) {
return (b[0] - a[0]) * (p[1] - a[1]) < (b[1] - a[1]) * (p[0] - a[0]);
@ -4338,6 +4341,10 @@ d3 = function() {
var x1 = c[0], x3 = a[0], x21 = d[0] - x1, x43 = b[0] - x3, y1 = c[1], y3 = a[1], y21 = d[1] - y1, y43 = b[1] - y3, ua = (x43 * (y1 - y3) - y43 * (x1 - x3)) / (y43 * x21 - x43 * y21);
return [ x1 + ua * x21, y1 + ua * y21 ];
}
function d3_geom_polygonClosed(coordinates) {
var a = coordinates[0], b = coordinates[coordinates.length - 1];
return !(a[0] - b[0] || a[1] - b[1]);
}
d3.geom.delaunay = function(vertices) {
var edges = vertices.map(function() {
return [];
@ -7421,7 +7428,7 @@ d3 = function() {
d3.svg.symbolTypes = d3_svg_symbols.keys();
var d3_svg_symbolSqrt3 = Math.sqrt(3), d3_svg_symbolTan30 = Math.tan(30 * d3_radians);
function d3_transition(groups, id) {
d3_arraySubclass(groups, d3_transitionPrototype);
d3_subclass(groups, d3_transitionPrototype);
groups.id = id;
return groups;
}

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

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

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

@ -17,15 +17,3 @@ try {
} catch(e) {
d3_array = d3_arrayCopy;
}
var d3_arraySubclass = [].__proto__?
// Until ECMAScript supports array subclassing, prototype injection works well.
function(array, prototype) {
array.__proto__ = prototype;
}:
// And if your browser doesn't support __proto__, we'll use direct extension.
function(array, prototype) {
for (var property in prototype) array[property] = prototype[property];
};

11
src/core/subclass.js Normal file
Просмотреть файл

@ -0,0 +1,11 @@
var d3_subclass = {}.__proto__?
// Until ECMAScript supports array subclassing, prototype injection works well.
function(object, prototype) {
object.__proto__ = prototype;
}:
// And if your browser doesn't support __proto__, we'll use direct extension.
function(object, prototype) {
for (var property in prototype) object[property] = prototype[property];
};

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

@ -1,78 +1,88 @@
import "../core/subclass";
import "geom";
// Note: removes the closing coordinate if the polygon is not already open.
d3.geom.polygon = function(coordinates) {
d3_geom_polygonOpen(coordinates);
d3_subclass(coordinates, d3_geom_polygonPrototype);
return coordinates;
};
coordinates.area = function() {
var i = -1,
n = coordinates.length,
a,
b = coordinates[n - 1],
area = 0;
while (++i < n) {
a = b;
b = coordinates[i];
area += a[1] * b[0] - a[0] * b[1];
}
return area * .5;
};
var d3_geom_polygonPrototype = d3.geom.polygon.prototype = [];
coordinates.centroid = function(k) {
var i = -1,
n = coordinates.length,
x = 0,
y = 0,
a,
b = coordinates[n - 1],
c;
if (!arguments.length) k = -1 / (6 * coordinates.area());
while (++i < n) {
a = b;
b = coordinates[i];
c = a[0] * b[1] - b[0] * a[1];
x += (a[0] + b[0]) * c;
y += (a[1] + b[1]) * c;
}
return [x * k, y * k];
};
d3_geom_polygonPrototype.area = function() {
var i = -1,
n = this.length,
a,
b = this[n - 1],
area = 0;
// The Sutherland-Hodgman clipping algorithm.
// Note: requires the clip polygon to be open, counterclockwise and convex.
coordinates.clip = function(subject) {
var input,
i = -1,
n = coordinates.length,
j,
m,
a = coordinates[n - 1],
b,
c,
d;
while (++i < n) {
input = subject.slice();
subject.length = 0;
b = coordinates[i];
c = input[(m = input.length) - 1];
j = -1;
while (++j < m) {
d = input[j];
if (d3_geom_polygonInside(d, a, b)) {
if (!d3_geom_polygonInside(c, a, b)) {
subject.push(d3_geom_polygonIntersect(c, d, a, b));
}
subject.push(d);
} else if (d3_geom_polygonInside(c, a, b)) {
while (++i < n) {
a = b;
b = this[i];
area += a[1] * b[0] - a[0] * b[1];
}
return area * .5;
};
d3_geom_polygonPrototype.centroid = function(k) {
var i = -1,
n = this.length,
x = 0,
y = 0,
a,
b = this[n - 1],
c;
if (!arguments.length) k = -1 / (6 * this.area());
while (++i < n) {
a = b;
b = this[i];
c = a[0] * b[1] - b[0] * a[1];
x += (a[0] + b[0]) * c;
y += (a[1] + b[1]) * c;
}
return [x * k, y * k];
};
// The Sutherland-Hodgman clipping algorithm.
// Note: requires the clip polygon to be counterclockwise and convex.
d3_geom_polygonPrototype.clip = function(subject) {
var input,
closed = d3_geom_polygonClosed(subject),
i = -1,
n = this.length - d3_geom_polygonClosed(this),
j,
m,
a = this[n - 1],
b,
c,
d;
while (++i < n) {
input = subject.slice();
subject.length = 0;
b = this[i];
c = input[(m = input.length - closed) - 1];
j = -1;
while (++j < m) {
d = input[j];
if (d3_geom_polygonInside(d, a, b)) {
if (!d3_geom_polygonInside(c, a, b)) {
subject.push(d3_geom_polygonIntersect(c, d, a, b));
}
c = d;
subject.push(d);
} else if (d3_geom_polygonInside(c, a, b)) {
subject.push(d3_geom_polygonIntersect(c, d, a, b));
}
a = b;
c = d;
}
return subject;
};
if (closed) subject.push(subject[0]);
a = b;
}
return coordinates;
return subject;
};
function d3_geom_polygonInside(p, a, b) {
@ -87,9 +97,9 @@ function d3_geom_polygonIntersect(c, d, a, b) {
return [x1 + ua * x21, y1 + ua * y21];
}
// If coordinates is not open, removes the closing point.
function d3_geom_polygonOpen(coordinates) {
// Returns true if the polygon is closed.
function d3_geom_polygonClosed(coordinates) {
var a = coordinates[0],
b = coordinates[coordinates.length - 1];
if (!(a[0] - b[0] || a[1] - b[1])) coordinates.pop();
return !(a[0] - b[0] || a[1] - b[1]);
}

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

@ -1,8 +1,8 @@
import "../core/array";
import "../core/subclass";
import "selection";
function d3_selection_enter(selection) {
d3_arraySubclass(selection, d3_selection_enterPrototype);
d3_subclass(selection, d3_selection_enterPrototype);
return selection;
}

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

@ -1,9 +1,10 @@
import "../core/array";
import "../core/document";
import "../core/subclass";
import "../core/vendor";
function d3_selection(groups) {
d3_arraySubclass(groups, d3_selectionPrototype);
d3_subclass(groups, d3_selectionPrototype);
return groups;
}

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

@ -1,12 +1,12 @@
import "../arrays/map";
import "../core/array";
import "../core/subclass";
import "../event/dispatch";
import "../event/timer";
import "../interpolate/ease";
import "../selection/selection";
function d3_transition(groups, id) {
d3_arraySubclass(groups, d3_transitionPrototype);
d3_subclass(groups, d3_transitionPrototype);
groups.id = id; // Note: read-only!

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

@ -12,8 +12,8 @@ suite.addBatch({
topic: function(polygon) {
return polygon([[0, 0], [0, 1], [1, 1], [1, 0], [0, 0]]);
},
"is converted to an open polygon": function(p) {
assertPolygonInDelta(p, [[0, 0], [0, 1], [1, 1], [1, 0]]);
"preserves input coordinates": function(p) {
assertPolygonInDelta(p, [[0, 0], [0, 1], [1, 1], [1, 0], [0, 0]]);
},
"has area 1": function(p) {
assert.equal(p.area(), 1);
@ -23,14 +23,17 @@ suite.addBatch({
},
"can clip an open counterclockwise triangle": function(p) {
assertPolygonInDelta(p.clip([[0.9, 0.5], [2, -1], [0.5, 0.1]]), [[0.9, 0.5], [1, 0.363636], [1, 0], [0.636363, 0], [0.5, 0.1]], 1e-4);
},
"can clip a closed counterclockwise triangle": function(p) {
assertPolygonInDelta(p.clip([[0.9, 0.5], [2, -1], [0.5, 0.1], [0.9, 0.5]]), [[0.9, 0.5], [1, 0.363636], [1, 0], [0.636363, 0], [0.5, 0.1], [0.9, 0.5]], 1e-4);
}
},
"closed clockwise unit square": {
topic: function(polygon) {
return polygon([[0, 0], [1, 0], [1, 1], [0, 1], [0, 0]]);
},
"is converted to an open polygon": function(p) {
assertPolygonInDelta(p, [[0, 0], [1, 0], [1, 1], [0, 1]]);
"preserves input coordinates": function(p) {
assertPolygonInDelta(p, [[0, 0], [1, 0], [1, 1], [0, 1], [0, 0]]);
},
"has area 1": function(p) {
assert.equal(p.area(), -1);
@ -46,8 +49,8 @@ suite.addBatch({
topic: function(polygon) {
return polygon([[1, 1], [3, 2], [2, 3], [1, 1]]);
},
"is converted to an open polygon": function(p) {
assertPolygonInDelta(p, [[1, 1], [3, 2], [2, 3]]);
"preserves input coordinates": function(p) {
assertPolygonInDelta(p, [[1, 1], [3, 2], [2, 3], [1, 1]]);
},
"has area 1.5": function(p) {
assert.equal(p.area(), -1.5);
@ -74,6 +77,9 @@ suite.addBatch({
},
"can clip an open counterclockwise triangle": function(p) {
assertPolygonInDelta(p.clip([[0.9, 0.5], [2, -1], [0.5, 0.1]]), [[0.9, 0.5], [1, 0.363636], [1, 0], [0.636363, 0], [0.5, 0.1]], 1e-4);
},
"can clip an closed counterclockwise triangle": function(p) {
assertPolygonInDelta(p.clip([[0.9, 0.5], [2, -1], [0.5, 0.1], [0.9, 0.5]]), [[0.9, 0.5], [1, 0.363636], [1, 0], [0.636363, 0], [0.5, 0.1], [0.9, 0.5]], 1e-4);
}
},
"open clockwise unit square": {