@@ -104,6 +104,9 @@ StatContour <- ggproto("StatContour", Stat,
104104
105105 compute_group = function (data , scales , z.range , bins = NULL , binwidth = NULL ,
106106 breaks = NULL , na.rm = FALSE ) {
107+ # Undo data rotation
108+ rotation <- estimate_contour_angle(data $ x , data $ y )
109+ data [c(" x" , " y" )] <- rotate_xy(data $ x , data $ y , - rotation )
107110
108111 breaks <- contour_breaks(z.range , bins , binwidth , breaks )
109112
@@ -113,6 +116,8 @@ StatContour <- ggproto("StatContour", Stat,
113116 path_df $ level <- as.numeric(path_df $ level )
114117 path_df $ nlevel <- rescale_max(path_df $ level )
115118
119+ # Re-apply data rotation
120+ path_df [c(" x" , " y" )] <- rotate_xy(path_df $ x , path_df $ y , rotation )
116121 path_df
117122 }
118123)
@@ -138,6 +143,11 @@ StatContourFilled <- ggproto("StatContourFilled", Stat,
138143 },
139144
140145 compute_group = function (data , scales , z.range , bins = NULL , binwidth = NULL , breaks = NULL , na.rm = FALSE ) {
146+
147+ # Undo data rotation
148+ rotation <- estimate_contour_angle(data $ x , data $ y )
149+ data [c(" x" , " y" )] <- rotate_xy(data $ x , data $ y , - rotation )
150+
141151 breaks <- contour_breaks(z.range , bins , binwidth , breaks )
142152
143153 isobands <- withr :: with_options(list (OutDec = " ." ), xyz_to_isobands(data , breaks ))
@@ -149,6 +159,8 @@ StatContourFilled <- ggproto("StatContourFilled", Stat,
149159 path_df $ level_high <- breaks [as.numeric(path_df $ level ) + 1 ]
150160 path_df $ level_mid <- 0.5 * (path_df $ level_low + path_df $ level_high )
151161 path_df $ nlevel <- rescale_max(path_df $ level_high )
162+ # Re-apply data rotation
163+ path_df [c(" x" , " y" )] <- rotate_xy(path_df $ x , path_df $ y , rotation )
152164
153165 path_df
154166 }
@@ -356,3 +368,49 @@ contour_deduplicate <- function(data, check = c("x", "y", "group", "PANEL")) {
356368 }
357369 data
358370}
371+
372+ estimate_contour_angle <- function (x , y ) {
373+
374+ # Compute most frequent angle among first 20 points
375+ all_angles <- atan2(diff(head(y , 20L )), diff(head(x , 20L )))
376+ freq <- tabulate(match(all_angles , unique(all_angles )))
377+ i <- which.max(freq )
378+
379+ # If this angle represents less than half of the angles, we probably
380+ # have unordered data, in which case the approach above is invalid
381+ if ((freq [i ] / sum(freq )) < 0.5 ) {
382+ # In such case, try approach with convex hull
383+ hull <- grDevices :: chull(x , y )
384+ hull <- c(hull , hull [1 ])
385+ # Find largest edge along hull
386+ dx <- diff(x [hull ])
387+ dy <- diff(y [hull ])
388+ i <- which.max(sqrt(dx ^ 2 + dy ^ 2 ))
389+ # Take angle of largest edge
390+ angle <- atan2(dy [i ], dx [i ])
391+ } else {
392+ angle <- all_angles [i ]
393+ }
394+
395+ # No need to rotate contour data when angle is straight
396+ straight <- abs(angle - c(- 1 , - 0.5 , 0 , 0.5 , 1 ) * pi ) < sqrt(.Machine $ double.eps )
397+ if (any(straight )) {
398+ return (0 )
399+ }
400+ angle
401+ }
402+
403+ rotate_xy <- function (x , y , angle ) {
404+ # Skip rotation if angle was straight
405+ if (angle == 0 ) {
406+ return (list (x = x , y = y ))
407+ }
408+ cos <- cos(angle )
409+ sin <- sin(angle )
410+ # Using zapsmall to make `unique0` later recognise values that may have
411+ # rounding errors.
412+ list (
413+ x = zapsmall(cos * x - sin * y , digits = 13 ),
414+ y = zapsmall(sin * x + cos * y , digits = 13 )
415+ )
416+ }
0 commit comments