diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 23828b49..82d9e733 100755 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -28,16 +28,24 @@ The readme contains some tips on running tests. For dev you will need to install the requirements-dev.txt file. -clone the repo an cd into it. +Clone the repo an cd into it. -setup a virtual environment +Setup a virtual environment: +**BASH** ```bash python3 -m venv venv . venv/bin/activate pip install -r requirements-dev.txt ``` +**PowerShell** +```powershell +python3 -m venv winvenv +. .\winvenv\Scripts\activate +pip install -r requirements-dev.txt +``` + and you should be good to go. The Makefile can be used to run all tests. diff --git a/domonic/d3/polygon.py b/domonic/d3/polygon.py index 01613ec6..2db075f8 100644 --- a/domonic/d3/polygon.py +++ b/domonic/d3/polygon.py @@ -44,7 +44,6 @@ def polygonCentroid(polygon): k *= 3 return [x / k, y / k] - def cross(a, b, c): """[Returns the 2D cross product of AB and AC vectors, i.e., the z-component of the 3D cross product in a quadrant I Cartesian coordinate system (+x is @@ -61,73 +60,34 @@ def cross(a, b, c): """ return (b[0] - a[0]) * (c[1] - a[1]) - (b[1] - a[1]) * (c[0] - a[0]) - -def lexicographicOrder(a, b, *args): - return a[0] - b[0] or a[1] - b[1] - - -def computeUpperHullIndexes(points): - """[Computes the upper convex hull per the monotone chain algorithm. - Assumes points.length >= 3, is sorted by x, unique in y. - Returns an array of indices into points in left-to-right order.] - - Args: - points ([type]): [description] - """ - n = len(points) - indexes = [0, 1] - size = 2 - for i in range(2, n): - while (size > 1 and cross(points[indexes[size - 2]], points[indexes[size - 1]], points[i]) <= 0): - size -= 1 - size += 1 - indexes[size] = i - - return Array(indexes).slice(0, size) # remove popped points - - def polygonHull(points): n = len(points) if n < 3: return None - sortedPoints = [] - flippedPoints = [] - - print(points) - - for i in range(0, n): - print( points[i][0], points[i][1], i) - sortedPoints.append([points[i][0], points[i][1], i]) - - print(sortedPoints) - - sortedPoints = Array(sortedPoints).sort(lexicographicOrder) - - for i in range(0, n): - flippedPoints[i] = [sortedPoints[i][0], -sortedPoints[i][1]] - - upperIndexes = computeUpperHullIndexes(sortedPoints) - lowerIndexes = computeUpperHullIndexes(flippedPoints) - - # Construct the hull polygon, removing possible duplicate endpoints. - skipLeft = lowerIndexes[0] == upperIndexes[0] - skipRight = lowerIndexes[len(lowerIndexes) - 1] == upperIndexes[len(upperIndexes) - 1] - hull = [] - - # Add upper hull in right-to-l order. - # Then add lower hull in left-to-right order. - i = len(upperIndexes) - while i >= 0: - hull.append(points[sortedPoints[upperIndexes[i]][2]]) - i -= 1 - - i = skipLeft - while i < len(lowerIndexes) - skipRight: - hull.append(points[sortedPoints[lowerIndexes[i]][2]]) - i += 1 - + # Convert list to tuples and remove duplicates + points = [t for t in (set(tuple(i) for i in points))] + + # Sort lexicographically + points = sorted(points) + + # Build hulls according to Andrew's monotone chain algorithm + lowerHull = [] + for p in points: + while len(lowerHull) >= 2 and cross(lowerHull[-2], lowerHull[-1], p) <=0 : + lowerHull.pop() + lowerHull.append(p) + + upperHull = [] + for p in reversed(points): + while len(upperHull) >= 2 and cross(upperHull[-2], upperHull[-1], p) <= 0: + upperHull.pop() + upperHull.append(p) + + # Build polygon hull from upper/lower hulls and return to list form + hull = sorted([list(i) for i in lowerHull[:-1] + upperHull[:-1]]) + return hull @@ -171,4 +131,4 @@ def polygonLength(polygon): ya -= yb perimeter += Math.hypot(xa, ya) i += 1 - return perimeter + return perimeter \ No newline at end of file diff --git a/tests/test_d3.py b/tests/test_d3.py index ba972ff8..f9793b62 100644 --- a/tests/test_d3.py +++ b/tests/test_d3.py @@ -1541,13 +1541,29 @@ def test_polygonArea(self): self.assertEqual(polygonArea(square), 16) self.assertEqual(polygonArea(triangle), 12) - # def test_polygonCentroid(): - # def test_cross(): - # def test_lexicographicOrder(): - # def test_polygonHull(): + def test_polygonCentroid(self): + irreg = [[-4,0],[8,12],[4,8],[-4,-4],[0,0]] # centroid: [0, 4] + square = [[0,4],[4,4],[4,0],[0,0]] # centroid: [2, 2] + triangle = [[-4,0],[0,4],[4,2]] # centroid: [0, 2] + + self.assertEqual(polygonCentroid(irreg), [0, 4]) + self.assertEqual(polygonCentroid(square), [2, 2]) + self.assertEqual(polygonCentroid(triangle), [0, 2]) + + def test_polygonHull(self): + points_0 = [[0,6],[12,8],[23,-5],[-5,-3],[5,11],[12,4],[7,7],[6,1]] + points_1 = [[-4,0],[8,12],[4,8],[-4,-4],[0,0],[-6,12],[23,2],[19,-8],[-7,-6]] + + hull_0 = [[-5,-3],[0,6],[5,11],[12,8],[23,-5]] + hull_1 = [[-7,-6],[-6,12],[8,12],[19,-8],[23,2]] + + self.assertEqual(polygonHull(points_0), hull_0) + self.assertEqual(polygonHull(points_1), hull_1) + + # def test_polygonContains(): # def test_polygonLength(): if __name__ == '__main__': - unittest.main() + unittest.main() \ No newline at end of file