@@ -104,6 +104,9 @@ StatContour <- ggproto("StatContour", Stat,
104
104
105
105
compute_group = function (data , scales , z.range , bins = NULL , binwidth = NULL ,
106
106
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 )
107
110
108
111
breaks <- contour_breaks(z.range , bins , binwidth , breaks )
109
112
@@ -113,6 +116,8 @@ StatContour <- ggproto("StatContour", Stat,
113
116
path_df $ level <- as.numeric(path_df $ level )
114
117
path_df $ nlevel <- rescale_max(path_df $ level )
115
118
119
+ # Re-apply data rotation
120
+ path_df [c(" x" , " y" )] <- rotate_xy(path_df $ x , path_df $ y , rotation )
116
121
path_df
117
122
}
118
123
)
@@ -138,6 +143,11 @@ StatContourFilled <- ggproto("StatContourFilled", Stat,
138
143
},
139
144
140
145
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
+
141
151
breaks <- contour_breaks(z.range , bins , binwidth , breaks )
142
152
143
153
isobands <- withr :: with_options(list (OutDec = " ." ), xyz_to_isobands(data , breaks ))
@@ -149,6 +159,8 @@ StatContourFilled <- ggproto("StatContourFilled", Stat,
149
159
path_df $ level_high <- breaks [as.numeric(path_df $ level ) + 1 ]
150
160
path_df $ level_mid <- 0.5 * (path_df $ level_low + path_df $ level_high )
151
161
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 )
152
164
153
165
path_df
154
166
}
@@ -356,3 +368,49 @@ contour_deduplicate <- function(data, check = c("x", "y", "group", "PANEL")) {
356
368
}
357
369
data
358
370
}
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