Commit 2db1fe85 authored by Mike Bostock's avatar Mike Bostock
Browse files

Merge branch 'fix-polygon-clip' into 3.2.3

parents ec0f410c fdd6c620
......@@ -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,50 +4284,55 @@ d3 = function() {
return (f - b) * (c - a) - (d - b) * (e - a) > 0;
}
d3.geom.polygon = function(coordinates) {
coordinates.area = function() {
var i = 0, n = coordinates.length, area = coordinates[n - 1][1] * coordinates[0][0] - coordinates[n - 1][0] * coordinates[0][1];
while (++i < n) {
area += coordinates[i - 1][1] * coordinates[i][0] - coordinates[i - 1][0] * coordinates[i][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]);
......@@ -4336,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 [];
......@@ -7416,7 +7425,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";
d3.geom.polygon = function(coordinates) {
d3_subclass(coordinates, d3_geom_polygonPrototype);
return coordinates;
};
coordinates.area = function() {
var i = 0,
n = coordinates.length,
area = coordinates[n - 1][1] * coordinates[0][0] - coordinates[n - 1][0] * coordinates[0][1];
while (++i < n) {
area += coordinates[i - 1][1] * coordinates[i][0] - coordinates[i - 1][0] * coordinates[i][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;
};
d3_geom_polygonPrototype.centroid = function(k) {
var i = -1,
n = this.length,
x = 0,
y = 0,
a,
b = this[n - 1],
c;
// The Sutherland-Hodgman clipping algorithm.
// Note: requires the clip polygon to be 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)) {
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) {
......@@ -80,3 +96,10 @@ function d3_geom_polygonIntersect(c, d, a, b) {
ua = (x43 * (y1 - y3) - y43 * (x1 - x3)) / (y43 * x21 - x43 * y21);
return [x1 + ua * x21, y1 + ua * y21];
}
// Returns true if the polygon is closed.
function d3_geom_polygonClosed(coordinates) {
var a = coordinates[0],
b = coordinates[coordinates.length - 1];
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,70 +12,105 @@ suite.addBatch({
topic: function(polygon) {
return polygon([[0, 0], [0, 1], [1, 1], [1, 0], [0, 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);
},
"has centroid ⟨.5,.5⟩": function(p) {
assert.deepEqual(p.centroid(), [.5, .5]);
assertPointInDelta(p.centroid(), [.5, .5]);
},
"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]]);
},
"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);
},
"has centroid ⟨.5,.5⟩": function(p) {
assert.deepEqual(p.centroid(), [.5, .5]);
assertPointInDelta(p.centroid(), [.5, .5]);
},
"is not currently supported for clipping": function(p) {
// because clipping requires a counterclockwise source polygon
}
},
"closed clockwise triangle": {
topic: function(polygon) {
return polygon([[1, 1], [3, 2], [2, 3], [1, 1]]);
},
"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);
},
"has centroid ⟨2,2⟩": function(p) {
var centroid = p.centroid();
assert.inDelta(centroid[0], 2, 1e-6);
assert.inDelta(centroid[1], 2, 1e-6);
assertPointInDelta(p.centroid(), [2, 2]);
},
"is not currently supported for clipping": function(p) {
// because clipping requires a counterclockwise source polygon
}
},
"open counterclockwise unit square": {
topic: function(polygon) {
return polygon([[0, 0], [0, 1], [1, 1], [1, 0]]);
},
"remains an open polygon": function(p) {
assertPolygonInDelta(p, [[0, 0], [0, 1], [1, 1], [1, 0]]);
},
"has area 1": function(p) {
assert.equal(p.area(), 1);
},
"has centroid ⟨.5,.5⟩": function(p) {
assert.deepEqual(p.centroid(), [.5, .5]);
assertPointInDelta(p.centroid(), [.5, .5]);
},
"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": {
topic: function(polygon) {
return polygon([[0, 0], [1, 0], [1, 1], [0, 1]]);
},
"remains an open polygon": function(p) {
assertPolygonInDelta(p, [[0, 0], [1, 0], [1, 1], [0, 1]]);
},
"has area 1": function(p) {
assert.equal(p.area(), -1);
},
"has centroid ⟨.5,.5⟩": function(p) {
assert.deepEqual(p.centroid(), [.5, .5]);
assertPointInDelta(p.centroid(), [.5, .5]);
},
"is not currently supported for clipping": function(p) {
// because clipping requires a counterclockwise source polygon
}
},
"open clockwise triangle": {
topic: function(polygon) {
return polygon([[1, 1], [3, 2], [2, 3]]);
},
"remains an open polygon": function(p) {
assertPolygonInDelta(p, [[1, 1], [3, 2], [2, 3]]);
},
"has area 1.5": function(p) {
assert.equal(p.area(), -1.5);
},
"has centroid ⟨2,2⟩": function(p) {
var centroid = p.centroid();
assert.inDelta(centroid[0], 2, 1e-6);
assert.inDelta(centroid[1], 2, 1e-6);
assertPointInDelta(p.centroid(), [2, 2]);
}
},
"large square": {
......@@ -95,4 +130,22 @@ suite.addBatch({
}
});
function assertPointInDelta(expected, actual, δ, message) {
if (!δ) δ = 0;
if (!pointInDelta(expected, actual, δ)) {
assert.fail(JSON.stringify(actual), JSON.stringify(expected), message || "expected {expected}, got {actual}", "===", assertPointInDelta);
}
}
function assertPolygonInDelta(expected, actual, δ, message) {
if (!δ) δ = 0;
if (expected.length !== actual.length || expected.some(function(e, i) { return !pointInDelta(e, actual[i], δ); })) {
assert.fail(JSON.stringify(actual), JSON.stringify(expected), message || "expected {expected}, got {actual}", "===", assertPolygonInDelta);
}
}
function pointInDelta(a, b, δ) {
return !(Math.abs(a[0] - b[0]) > δ || Math.abs(a[1] - b[1]) > δ);
}
suite.export(module);
......@@ -8,7 +8,7 @@ require("./XMLHttpRequest");
module.exports = function() {
var files = [].slice.call(arguments).map(function(d) { return "src/" + d; }),
expression = "d3",
sandbox = {Date: Date}; // so we can use deepEqual in tests
sandbox = {console: console, Date: Date}; // so we can use deepEqual in tests
files.unshift("src/start");
files.push("src/end");
......
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