@@ -3,10 +3,12 @@ import "geom";
33import "point" ;
44
55/**
6- * Computes the 2D convex hull of a set of points using Graham's scanning
7- * algorithm. The algorithm has been implemented as described in Cormen,
8- * Leiserson, and Rivest's Introduction to Algorithms. The running time of
9- * this algorithm is O(n log n), where n is the number of input points.
6+ * Computes the 2D convex hull of a set of points using the monotone chain
7+ * algorithm:
8+ * http://en.wikibooks.org/wiki/Algorithm_Implementation/Geometry/Convex_hull/Monotone_chain)
9+ *
10+ * The runtime of this algorithm is O(n log n), where n is the number of input
11+ * points. However in practice it outperforms other O(n log n) hulls.
1012 *
1113 * @param vertices [[x1, y1], [x2, y2], …]
1214 * @returns polygon [[x1, y1], [x2, y2], …]
@@ -18,85 +20,37 @@ d3.geom.hull = function(vertices) {
1820 if ( arguments . length ) return hull ( vertices ) ;
1921
2022 function hull ( data ) {
23+ // Hull of < 3 points is not well-defined
2124 if ( data . length < 3 ) return [ ] ;
2225
2326 var fx = d3_functor ( x ) ,
2427 fy = d3_functor ( y ) ,
25- n = data . length ,
26- vertices , // TODO use parallel arrays
27- plen = n - 1 ,
28- points = [ ] ,
29- stack = [ ] ,
30- d ,
31- i , j , h = 0 , x1 , y1 , x2 , y2 , u , v , a , sp ;
28+ n = data . length ;
3229
33- if ( fx === d3_geom_pointX && y === d3_geom_pointY ) vertices = data ;
34- else for ( i = 0 , vertices = [ ] ; i < n ; ++ i ) {
35- vertices . push ( [ + fx . call ( this , d = data [ i ] , i ) , + fy . call ( this , d , i ) ] ) ;
30+ for ( i = 0 , points = [ ] ; i < n ; ++ i ) {
31+ points . push ( [ + fx . call ( this , d = data [ i ] , i ) , + fy . call ( this , d , i ) , i ] ) ;
3632 }
3733
38- // find the starting ref point: leftmost point with the minimum y coord
39- for ( i = 1 ; i < n ; ++ i ) {
40- if ( vertices [ i ] [ 1 ] < vertices [ h ] [ 1 ]
41- || vertices [ i ] [ 1 ] == vertices [ h ] [ 1 ]
42- && vertices [ i ] [ 0 ] < vertices [ h ] [ 0 ] ) h = i ;
43- }
34+ // sort ascending by x-coord first, y-coord second
35+ points . sort ( function ( a , b ) {
36+ return ( a [ 0 ] < b [ 0 ] || ( a [ 0 ] === b [ 0 ] && a [ 1 ] < b [ 1 ] ) ) ? - 1 : 1 ;
37+ } ) ;
4438
45- // calculate polar angles from ref point and sort
46- for ( i = 0 ; i < n ; ++ i ) {
47- if ( i === h ) continue ;
48- y1 = vertices [ i ] [ 1 ] - vertices [ h ] [ 1 ] ;
49- x1 = vertices [ i ] [ 0 ] - vertices [ h ] [ 0 ] ;
50- points . push ( { angle : Math . atan2 ( y1 , x1 ) , index : i } ) ;
51- }
52- points . sort ( function ( a , b ) { return a . angle - b . angle ; } ) ;
39+ // we flip bottommost points across y axis so we can use the upper hull routine on both
40+ var flipped_points = [ ] ;
41+ for ( var i = 0 ; i < n ; i ++ ) flipped_points . push ( [ points [ i ] [ 0 ] , - points [ i ] [ 1 ] ] ) ;
5342
54- // toss out duplicate angles
55- a = points [ 0 ] . angle ;
56- v = points [ 0 ] . index ;
57- u = 0 ;
58- for ( i = 1 ; i < plen ; ++ i ) {
59- j = points [ i ] . index ;
60- if ( a == points [ i ] . angle ) {
61- // keep angle for point most distant from the reference
62- x1 = vertices [ v ] [ 0 ] - vertices [ h ] [ 0 ] ;
63- y1 = vertices [ v ] [ 1 ] - vertices [ h ] [ 1 ] ;
64- x2 = vertices [ j ] [ 0 ] - vertices [ h ] [ 0 ] ;
65- y2 = vertices [ j ] [ 1 ] - vertices [ h ] [ 1 ] ;
66- if ( x1 * x1 + y1 * y1 >= x2 * x2 + y2 * y2 ) {
67- points [ i ] . index = - 1 ;
68- continue ;
69- } else {
70- points [ u ] . index = - 1 ;
71- }
72- }
73- a = points [ i ] . angle ;
74- u = i ;
75- v = j ;
76- }
43+ var uhull = d3_geom_hull_find_upper_hull ( points ) ;
44+ var lhull = d3_geom_hull_find_upper_hull ( flipped_points ) ;
7745
78- // initialize the stack
79- stack . push ( h ) ;
80- for ( i = 0 , j = 0 ; i < 2 ; ++ j ) {
81- if ( points [ j ] . index > - 1 ) {
82- stack . push ( points [ j ] . index ) ;
83- i ++ ;
84- }
85- }
86- sp = stack . length ;
87-
88- // do graham's scan
89- for ( ; j < plen ; ++ j ) {
90- if ( points [ j ] . index < 0 ) continue ; // skip tossed out points
91- while ( ! d3_geom_hullCCW ( stack [ sp - 2 ] , stack [ sp - 1 ] , points [ j ] . index , vertices ) ) {
92- -- sp ;
93- }
94- stack [ sp ++ ] = points [ j ] . index ;
95- }
96-
97- // construct the hull
98- var poly = [ ] ;
99- for ( i = sp - 1 ; i >= 0 ; -- i ) poly . push ( data [ stack [ i ] ] ) ;
46+ // construct the poly, removing possible duplicate endpoints
47+ var skip_l = ( lhull [ 0 ] === uhull [ 0 ] ) ,
48+ skip_r = ( lhull [ lhull . length - 1 ] === uhull [ uhull . length - 1 ] ) ,
49+ poly = [ ] ;
50+ for ( var i = uhull . length - 1 ; i >= 0 ; i -- )
51+ poly . push ( data [ points [ uhull [ i ] ] [ 2 ] ] ) ;
52+ for ( var i = + skip_l ; i < lhull . length - skip_r ; i ++ )
53+ poly . push ( data [ points [ lhull [ i ] ] [ 2 ] ] ) ;
10054 return poly ;
10155 }
10256
@@ -111,11 +65,26 @@ d3.geom.hull = function(vertices) {
11165 return hull ;
11266} ;
11367
114- // are three points in counter-clockwise order?
115- function d3_geom_hullCCW ( i1 , i2 , i3 , v ) {
116- var t , a , b , c , d , e , f ;
117- t = v [ i1 ] ; a = t [ 0 ] ; b = t [ 1 ] ;
118- t = v [ i2 ] ; c = t [ 0 ] ; d = t [ 1 ] ;
119- t = v [ i3 ] ; e = t [ 0 ] ; f = t [ 1 ] ;
120- return ( f - b ) * ( c - a ) - ( d - b ) * ( e - a ) > 0 ;
68+ // finds the 'upper convex hull' (see wiki link above)
69+ // assumes points arg has >=3 elements, is sorted by x, unique in y
70+ // returns array of indices into points in left to right order
71+ function d3_geom_hull_find_upper_hull ( points ) {
72+ var n = points . length ,
73+ hull = [ 0 , 1 ] ,
74+ hs = 2 ; // hull size
75+
76+ for ( var i = 2 ; i < n ; i ++ ) {
77+ while ( hs > 1 && ! d3_geom_hull_CW ( points [ hull [ hs - 2 ] ] , points [ hull [ hs - 1 ] ] , points [ i ] ) ) {
78+ hs -- ;
79+ }
80+ hull [ hs ++ ] = i ;
81+ }
82+ // we slice to make sure that the points we 'popped' from hull don't stay behind
83+ return hull . slice ( 0 , hs ) ;
84+ }
85+
86+ // are three points a, b, c in clockwise order?
87+ // i.e. is the sign of (b-a)x(c-a) positive?
88+ function d3_geom_hull_CW ( a , b , c ) {
89+ return ( b [ 0 ] - a [ 0 ] ) * ( c [ 1 ] - a [ 1 ] ) - ( b [ 1 ] - a [ 1 ] ) * ( c [ 0 ] - a [ 0 ] ) > 0 ;
12190}
0 commit comments