29
29
# ' `dup_axis` is provide as a shorthand for creating a secondary axis that
30
30
# ' is a duplication of the primary axis, effectively mirroring the primary axis.
31
31
# '
32
+ # ' As of v3.1, date and datetime scales have limited secondary axis capabilities.
33
+ # ' Unlike other continuous scales, secondary axis transformations for date and datetime scales
34
+ # ' must respect their primary POSIX data structure.
35
+ # ' This means they may only be transformed via addition or subtraction, e.g.
36
+ # ' `~. + hms::hms(days = 8)`, or
37
+ # ' `~.- 8*60*60`. Nonlinear transformations will return an error.
38
+ # ' To produce a time-since-event secondary axis in this context, users
39
+ # ' may consider adapting secondary axis labels.
40
+ # '
32
41
# ' @examples
33
42
# ' p <- ggplot(mtcars, aes(cyl, mpg)) +
34
43
# ' geom_point()
56
65
# ' price = seq(20, 200000, length.out = 10)
57
66
# ' )
58
67
# '
59
- # ' # useful for labelling different time scales in the same plot
68
+ # ' # This may useful for labelling different time scales in the same plot
60
69
# ' ggplot(df, aes(x = dx, y = price)) + geom_line() +
61
70
# ' scale_x_datetime("Date", date_labels = "%b %d",
62
71
# ' date_breaks = "6 hour",
@@ -136,6 +145,7 @@ AxisSecondary <- ggproto("AxisSecondary", NULL,
136
145
if (! is.formula(self $ trans )) stop(" transformation for secondary axes must be a formula" , call. = FALSE )
137
146
if (is.derived(self $ name ) && ! is.waive(scale $ name )) self $ name <- scale $ name
138
147
if (is.derived(self $ breaks )) self $ breaks <- scale $ breaks
148
+ if (is.waive(self $ breaks )) self $ breaks <- scale $ trans $ breaks
139
149
if (is.derived(self $ labels )) self $ labels <- scale $ labels
140
150
},
141
151
@@ -148,37 +158,66 @@ AxisSecondary <- ggproto("AxisSecondary", NULL,
148
158
)
149
159
},
150
160
151
- break_info = function (self , range , scale ) {
152
- if (self $ empty()) return ()
153
-
154
- # Get original range before transformation
155
- inv_range <- scale $ trans $ inverse(range )
161
+ mono_test = function (self , scale ){
162
+ range <- scale $ range $ range
163
+ along_range <- seq(range [1 ], range [2 ], length.out = self $ detail )
164
+ old_range <- scale $ trans $ inverse(along_range )
156
165
157
166
# Create mapping between primary and secondary range
158
- old_range <- seq(inv_range [1 ], inv_range [2 ], length.out = self $ detail )
159
167
full_range <- self $ transform_range(old_range )
160
168
161
169
# Test for monotonicity
162
170
if (length(unique(sign(diff(full_range )))) != 1 )
163
171
stop(" transformation for secondary axes must be monotonic" )
172
+ },
173
+
174
+ break_info = function (self , range , scale ) {
175
+ if (self $ empty()) return ()
176
+
177
+ # Test for monotonicity on unexpanded range
178
+ self $ mono_test(scale )
179
+
180
+ # Get scale's original range before transformation
181
+ along_range <- seq(range [1 ], range [2 ], length.out = self $ detail )
182
+ old_range <- scale $ trans $ inverse(along_range )
183
+
184
+ # Create mapping between primary and secondary range
185
+ full_range <- self $ transform_range(old_range )
164
186
165
187
# Get break info for the secondary axis
166
- new_range <- range(scale $ transform(full_range ), na.rm = TRUE )
167
- sec_scale <- self $ create_scale(new_range , scale )
168
- range_info <- sec_scale $ break_info()
188
+ new_range <- range(full_range , na.rm = TRUE )
189
+
190
+ # patch for date and datetime scales just to maintain functionality
191
+ # works only for linear secondary transforms that respect the time or date transform
192
+ if (scale $ trans $ name %in% c(" date" , " time" )){
193
+ temp_scale <- self $ create_scale(new_range , trans = scale $ trans )
194
+ range_info <- temp_scale $ break_info()
195
+ names(range_info ) <- paste0(" sec." , names(range_info ))
196
+ return (range_info )
197
+ }
198
+
199
+ temp_scale <- self $ create_scale(new_range )
200
+ range_info <- temp_scale $ break_info()
201
+
202
+ # Map the break values back to their correct position on the primary scale
203
+ old_val <- lapply(range_info $ major_source , function (x ) which.min(abs(full_range - x )))
204
+ old_val <- old_range [unlist(old_val )]
205
+ old_val_trans <- scale $ trans $ transform(old_val )
206
+ range_info $ major [] <- round(rescale(scale $ map(old_val_trans , range(old_val_trans )), from = range ), digits = 3 )
207
+
169
208
names(range_info ) <- paste0(" sec." , names(range_info ))
170
209
range_info
171
210
},
172
211
173
212
# Temporary scale for the purpose of calling break_info()
174
- create_scale = function (self , range , primary ) {
213
+ create_scale = function (self , range , trans = identity_trans() ) {
175
214
scale <- ggproto(NULL , ScaleContinuousPosition ,
176
- name = self $ name ,
177
- breaks = self $ breaks ,
178
- labels = self $ labels ,
179
- limits = range ,
180
- expand = c(0 , 0 ),
181
- trans = primary $ trans
215
+ name = self $ name ,
216
+ breaks = self $ breaks ,
217
+ labels = self $ labels ,
218
+ limits = range ,
219
+ expand = c(0 , 0 ),
220
+ trans = trans
182
221
)
183
222
scale $ train(range )
184
223
scale
0 commit comments