Commit fdd6c620 authored by Mike Bostock's avatar Mike Bostock
Browse files

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.
parent 5596e4fb
......@@ -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;
}
......
This source diff could not be displayed because it is too large. You can view the blob instead.
......@@ -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];
};
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];
};
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;
while (++i < n) {
a = b;
b = this[i];
area += a[1] * b[0] - a[0] * b[1];
}
return area * .5;
};
// 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)) {
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]);
}
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;
}
......
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;
}
......
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": {
......
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment