@@ -83,9 +83,16 @@ gg2list <- function(p, width = NULL, height = NULL) {
83
83
panel <- ggfun(" train_ranges" )(panel , p $ coordinates )
84
84
data <- by_layer(function (l , d ) l $ compute_geom_2(d ))
85
85
# ------------------------------------------------------------------------
86
- # end of ggplot_build(), start of layer -> trace conversion
86
+ # end of ggplot_build()
87
87
# ------------------------------------------------------------------------
88
88
89
+ if (inherits(p $ coordinates , " CoordFlip" )) {
90
+ # flip labels
91
+ p $ labels [c(" x" , " y" )] <- p $ labels [c(" y" , " x" )]
92
+ # TODO: is there anything else we need to flip? p$scales?
93
+ }
94
+
95
+ # important panel summary stats
89
96
nPanels <- nrow(panel $ layout )
90
97
nRows <- max(panel $ layout $ ROW )
91
98
nCols <- max(panel $ layout $ COL )
@@ -143,37 +150,6 @@ gg2list <- function(p, width = NULL, height = NULL) {
143
150
# we may tack on more traces with visible="legendonly"
144
151
traces <- lapply(traces , function (x ) { x $ showlegend <- FALSE ; x })
145
152
146
- # Bars require all sorts of hackery:
147
- # (1) position_*() is layer-specific, but `layout.barmode` is plot-specific.
148
- # (2) coord_flip() is plot-specific, but `bar.orientiation` is trace-specific
149
- # (3) position_stack() non-sense
150
- traceTypes <- unlist(lapply(traces , " [[" , " type" ))
151
- idx <- which(traceTypes %in% " bar" )
152
- if (length(idx )) {
153
- # determine `layout.barmode`
154
- positions <- sapply(layers , type , " position" )
155
- geoms <- sapply(layers , type , " geom" )
156
- # bar geometry requires us to flip the orientation for flipped coordinates
157
- if (" CoordFlip" %in% class(p $ coordinates )) {
158
- for (i in idx ) {
159
- traces [[i ]]$ orientation <- " h"
160
- y <- traces [[i ]]$ y
161
- traces [[i ]]$ y <- traces [[i ]]$ x
162
- traces [[i ]]$ x <- y
163
- }
164
- }
165
- }
166
-
167
- # bargeoms <- geoms[grepl("^bar$", geoms)]
168
- # if (length(bargeoms)) {
169
- # list(
170
- # stack = "stack",
171
- # dodge = "group",
172
- #
173
- # )
174
- # }
175
- #
176
-
177
153
# ------------------------------------------------------------------------
178
154
# axis/facet/margin conversion
179
155
# ------------------------------------------------------------------------
@@ -198,8 +174,8 @@ gg2list <- function(p, width = NULL, height = NULL) {
198
174
gglayout $ titlefont <- text2font(theme $ plot.title )
199
175
gglayout $ margin $ t <- gglayout $ margin $ t + gglayout $ titlefont $ size
200
176
}
201
-
202
- # panel margins
177
+ # panel margins must be computed before panel/axis loops
178
+ # (in order to use get_domains())
203
179
panelMarginX <- unitConvert(
204
180
theme [[" panel.margin.x" ]] %|| % theme [[" panel.margin" ]],
205
181
" npc" , " width"
@@ -222,6 +198,7 @@ gg2list <- function(p, width = NULL, height = NULL) {
222
198
theme [[" axis.ticks.x" ]] %|| % theme [[" axis.ticks" ]],
223
199
" npc" , " height"
224
200
)
201
+ # allocate enough space for the _longest_ text label
225
202
axisTextX <- theme [[" axis.text.x" ]] %|| % theme [[" axis.text" ]]
226
203
labz <- unlist(lapply(panel $ ranges , " [[" , " x.labels" ))
227
204
lab <- labz [which.max(nchar(labz ))]
@@ -233,6 +210,7 @@ gg2list <- function(p, width = NULL, height = NULL) {
233
210
theme [[" axis.ticks.y" ]] %|| % theme [[" axis.ticks" ]],
234
211
" npc" , " width"
235
212
)
213
+ # allocate enough space for the _longest_ text label
236
214
axisTextY <- theme [[" axis.text.y" ]] %|| % theme [[" axis.text" ]]
237
215
labz <- unlist(lapply(panel $ ranges , " [[" , " y.labels" ))
238
216
lab <- labz [which.max(nchar(labz ))]
@@ -263,17 +241,16 @@ gg2list <- function(p, width = NULL, height = NULL) {
263
241
axisName <- lay [, paste0(xy , " axis" )]
264
242
anchor <- lay [, paste0(xy , " anchor" )]
265
243
rng <- panel $ ranges [[i ]]
266
- sc <- scales $ get_scales(xy )
244
+ sc <- if (inherits(p $ coordinates , " CoordFlip" )) {
245
+ scales $ get_scales(setdiff(c(" x" , " y" ), xy ))
246
+ } else {
247
+ scales $ get_scales(xy )
248
+ }
267
249
# type of unit conversion
268
250
type <- if (xy == " x" ) " height" else " width"
269
- # set some axis defaults (and override some of them later)
270
251
# https://plot.ly/r/reference/#layout-xaxis
271
- #
272
- # TODO: implement minor grid lines with another axis object
273
- # and _always_ hide ticks/text?
274
252
axisObj <- list (
275
- title = if (! is_blank(axisTitle )) sc $ name %|| % p $ labels [[xy ]],
276
- titlefont = text2font(axisTitle , type ),
253
+ # this might be changed later in re_scale()
277
254
type = " linear" ,
278
255
autorange = FALSE ,
279
256
tickmode = " array" ,
@@ -297,53 +274,21 @@ gg2list <- function(p, width = NULL, height = NULL) {
297
274
zeroline = FALSE ,
298
275
anchor = anchor
299
276
)
300
- # bold/italic axis title
301
- axisObj $ title <- faced(axisObj $ title , theme $ axis.text $ face )
302
- axisObj <- re_scale(axisObj , sc )
303
-
304
- # tack axis object onto the layout
305
- gglayout [[axisName ]] <- axisObj
306
-
277
+ # TODO: implement minor grid lines with another axis object
278
+ # and _always_ hide ticks/text?
279
+ gglayout [[axisName ]] <- re_scale(axisObj , sc )
307
280
281
+ # do some stuff that should be done once for the entire plot
308
282
if (i == 1 ) {
309
283
# convert days to milliseconds, if necessary
310
- if (" date" %in% p $ scales $ get_scales( xy ) $ scale_name ) {
284
+ if (" date" %in% sc $ scale_name ) {
311
285
traces <- lapply(traces , function (z ) {
312
286
z [[xy ]] <- z [[xy ]] * 24 * 60 * 60 * 1000
313
287
z
314
288
})
315
289
}
316
- # account for (exterior) axis/strip text in plot margins
317
- side <- if (xy == " x" ) " b" else " l"
318
- way <- if (xy == " x" ) " v" else " h"
319
- tickText <- axisObj $ ticktext [which.max(nchar(axisObj $ ticktext ))]
320
- # apparently ggplot2 doesn't support axis.title/axis.text margins
321
- gglayout $ margin [[side ]] <- gglayout $ margin [[side ]] + axisObj $ ticklen +
322
- # account for rotated title (just like we've done for ticks?)
323
- axisObj $ titlefont $ size +
324
- bbox(tickText , axisObj $ tickangle , axisObj $ tickfont $ size )[[way ]]
325
-
290
+ # add space for exterior facet strips in `layout.margin`
326
291
if (has_facet(p )) {
327
- # draw axis titles as annotations
328
- if (! is_blank(axisTitle ) && nchar(axisObj $ title %|| % " " ) > 0 ) {
329
- # npc is on a 0-1 scale of the _entire_ device,
330
- # but we really need offsets relative to the plotting region
331
- # (to do this correctly, we need the terminal height/width of the plot)
332
- offset <- 2 * (0 -
333
- unitConvert(axisText , " npc" , type ) -
334
- unitConvert(axisTitle , " npc" , type ) / 2 -
335
- unitConvert(theme $ axis.ticks.length , " npc" , type ))
336
- x <- if (xy == " x" ) 0.5 else offset
337
- y <- if (xy == " x" ) offset else 0.5
338
- gglayout $ annotations <- c(
339
- gglayout $ annotations ,
340
- make_label(
341
- axisObj $ title , x , y , el = axisTitle ,
342
- xanchor = " center" , yanchor = " middle"
343
- )
344
- )
345
- }
346
- # add space for exterior facet strips in `layout.margin`
347
292
stripSize <- unitConvert(stripText , " pixels" , type )
348
293
if (xy == " x" ) {
349
294
gglayout $ margin $ t <- gglayout $ margin $ t + stripSize
@@ -352,7 +297,39 @@ gg2list <- function(p, width = NULL, height = NULL) {
352
297
gglayout $ margin $ r <- gglayout $ margin $ r + stripSize
353
298
}
354
299
}
355
-
300
+ axisTitleText <- sc $ name %|| % p $ labels [[xy ]] %|| % " "
301
+ axisTickText <- axisObj $ ticktext [which.max(nchar(axisObj $ ticktext ))]
302
+ side <- if (xy == " x" ) " b" else " l"
303
+ way <- if (xy == " x" ) " v" else " h"
304
+ # account for axis ticks, ticks text, and titles in plot margins
305
+ # (apparently ggplot2 doesn't support axis.title/axis.text margins)
306
+ gglayout $ margin [[side ]] <- gglayout $ margin [[side ]] + axisObj $ ticklen +
307
+ bbox(axisTickText , axisObj $ tickangle , axisObj $ tickfont $ size )[[way ]] +
308
+ bbox(axisTitleText , axisTitle $ angle , unitConvert(axisTitle , " pixels" , type ))[[way ]]
309
+ # draw axis titles as annotations
310
+ # (plotly.js axis titles aren't smart enough to dodge ticks & text)
311
+ if (! is_blank(axisTitle ) && nchar(axisTitleText ) > 0 ) {
312
+ axisTextSize <- unitConvert(axisText , " npc" , type )
313
+ axisTitleSize <- unitConvert(axisTitle , " npc" , type )
314
+ offset <-
315
+ (0 -
316
+ bbox(axisTickText , axisText $ angle , axisTextSize )[[way ]] -
317
+ bbox(axisTitleText , axisTitle $ angle , axisTitleSize )[[way ]] / 2 -
318
+ unitConvert(theme $ axis.ticks.length , " npc" , type ))
319
+ # npc is on a 0-1 scale of the _entire_ device,
320
+ # but these units _should_ be wrt to the plotting region
321
+ # multiplying the offset by 2 seems to work, but this is a terrible hack
322
+ offset <- 2 * offset
323
+ x <- if (xy == " x" ) 0.5 else offset
324
+ y <- if (xy == " x" ) offset else 0.5
325
+ gglayout $ annotations <- c(
326
+ gglayout $ annotations ,
327
+ make_label(
328
+ faced(axisTitleText , axisTitle $ face ), x , y , el = axisTitle ,
329
+ xanchor = " center" , yanchor = " middle"
330
+ )
331
+ )
332
+ }
356
333
}
357
334
358
335
} # end of axis loop
@@ -362,49 +339,40 @@ gg2list <- function(p, width = NULL, height = NULL) {
362
339
ydom <- gglayout [[lay [, " yaxis" ]]]$ domain
363
340
border <- make_panel_border(xdom , ydom , theme )
364
341
gglayout $ shapes <- c(gglayout $ shapes , border )
342
+
365
343
# facet strips -> plotly annotations
366
344
# TODO: use p$facet$labeller for the actual strip text!
367
- if (inherits(p $ facet , " grid" ) && lay $ COL == nCols ) {
345
+ if (has_facet(p ) && lay $ ROW == 1 && ! is_blank(theme [[" strip.text.x" ]])){
346
+ vars <- ifelse(inherits(p $ facet , " wrap" ), " facets" , " cols" )
368
347
txt <- paste(
369
- lay [, as.character(p $ facet $ rows )],
370
- collapse = " , "
348
+ lay [, as.character(p $ facet [[vars ]])], collapse = " , "
371
349
)
372
- if (! is_blank(theme [[" strip.text.y" ]])) {
373
- lab <- make_label(
374
- txt , x = max(xdom ), y = mean(ydom ),
375
- el = theme [[" strip.text.y" ]] %|| % theme [[" strip.text" ]],
376
- xanchor = " left" , yanchor = " bottom"
377
- )
378
- gglayout $ annotations <- c(gglayout $ annotations , lab )
379
- strip <- make_strip_rect(xdom , ydom , theme , " right" )
380
- gglayout $ shapes <- c(gglayout $ shapes , strip )
381
- }
350
+ lab <- make_label(
351
+ txt , x = mean(xdom ), y = max(ydom ),
352
+ el = theme [[" strip.text.x" ]] %|| % theme [[" strip.text" ]],
353
+ xanchor = " center" , yanchor = " bottom"
354
+ )
355
+ gglayout $ annotations <- c(gglayout $ annotations , lab )
356
+ strip <- make_strip_rect(xdom , ydom , theme , " top" )
357
+ gglayout $ shapes <- c(gglayout $ shapes , strip )
382
358
}
383
- if (inherits(p $ facet , " wrap " ) || inherits( p $ facet , " grid" ) && lay $ ROW == 1 ){
384
- vars <- ifelse(inherits( p $ facet , " wrap " ), " facets " , " cols " )
359
+ if (inherits(p $ facet , " grid" ) && lay $ COL == nCols &&
360
+ ! is_blank( theme [[ " strip.text.y " ]])) {
385
361
txt <- paste(
386
- lay [, as.character(p $ facet [[vars ]])],
387
- collapse = " , "
362
+ lay [, as.character(p $ facet $ rows )], collapse = " , "
388
363
)
389
- if (! is_blank(theme [[" strip.text.x" ]])) {
390
- lab <- make_label(
391
- txt , x = mean(xdom ), y = max(ydom ),
392
- el = theme [[" strip.text.x" ]] %|| % theme [[" strip.text" ]],
393
- xanchor = " center" , yanchor = " bottom"
394
- )
395
- gglayout $ annotations <- c(gglayout $ annotations , lab )
396
- strip <- make_strip_rect(xdom , ydom , theme , " top" )
397
- gglayout $ shapes <- c(gglayout $ shapes , strip )
398
- }
364
+ lab <- make_label(
365
+ txt , x = max(xdom ), y = mean(ydom ),
366
+ el = theme [[" strip.text.y" ]] %|| % theme [[" strip.text" ]],
367
+ xanchor = " left" , yanchor = " bottom"
368
+ )
369
+ gglayout $ annotations <- c(gglayout $ annotations , lab )
370
+ strip <- make_strip_rect(xdom , ydom , theme , " right" )
371
+ gglayout $ shapes <- c(gglayout $ shapes , strip )
399
372
}
400
373
401
374
} # end of panel loop
402
375
403
- # if facets are present, wipe out 'official' [x/y]axis title(s)
404
- if (has_facet(p )) {
405
- gglayout <- strip_axis(gglayout , c(" title" , " titlefont" ))
406
- }
407
-
408
376
# ------------------------------------------------------------------------
409
377
# guide/legend conversion
410
378
# ------------------------------------------------------------------------
@@ -480,14 +448,52 @@ gg2list <- function(p, width = NULL, height = NULL) {
480
448
return (NULL )
481
449
}
482
450
483
- traces <- c(traces , lapply(gdefs , gdef2trace ))
451
+ traces <- compact( c(traces , lapply(gdefs , gdef2trace ) ))
484
452
485
453
# TODO:
486
454
# (1) shrink guide size(s). Set fractions in colorbar.lenmode
487
455
# (2) position guide(s)?
488
456
# (3)
489
457
}
490
458
459
+ # Bar hackery:
460
+ # (1) coord_flip() is plot-specific, but `bar.orientiation` is trace-specific
461
+ # (2) position_*() is layer-specific, but `layout.barmode` is plot-specific.
462
+ geoms <- sapply(layers , ggtype , " geom" )
463
+ if (any(idx <- geoms %in% " bar" )) {
464
+ gglayout $ bargap <- 0
465
+ # since `layout.barmode` is plot-specific, we can't support multiple bar
466
+ # geoms with different positions
467
+ positions <- sapply(layers , ggtype , " position" )
468
+ position <- unique(positions [geoms %in% " bar" ])
469
+ if (length(position ) > 1 ) {
470
+ warning(" plotly doesn't support multiple positions\n " ,
471
+ " across geom_bar() layers" , call. = FALSE )
472
+ position <- position [1 ]
473
+ }
474
+ # note: ggplot2 doesn't flip x/y scales when the coord is flipped
475
+ # (i.e., at this point, y should be the count/density)
476
+ is_hist <- inherits(p $ scales $ get_scales(" x" ), " ScaleContinuous" )
477
+ gglayout $ barmode <- if (position %in% " identity" && is_hist ) {
478
+ " overlay"
479
+ } else if (position %in% c(" identity" , " stack" , " fill" )) {
480
+ " stack"
481
+ } else {
482
+ " group"
483
+ }
484
+ }
485
+
486
+ # flipped coordinates
487
+ if (inherits(p $ coordinates , " CoordFlip" )) {
488
+ for (i in seq_along(traces )) {
489
+ tr <- traces [[i ]]
490
+ # flip x/y in traces
491
+ traces [[i ]][c(" x" , " y" )] <- tr [c(" y" , " x" )]
492
+ if (identical(tr $ type , " bar" )) traces [[i ]]$ orientation <- " h"
493
+ # TODO: do I have to flip axis objects?
494
+ }
495
+ }
496
+
491
497
l <- list (data = compact(traces ), layout = compact(gglayout ))
492
498
# ensure properties are boxed correctly
493
499
l <- add_boxed(rm_asis(l ))
@@ -605,15 +611,6 @@ has_facet <- function(x) {
605
611
inherits(x $ facet , c(" grid" , " wrap" ))
606
612
}
607
613
608
- # remove a property from an axis element
609
- strip_axis <- function (x , y = c(" title" , " titlefont" )) {
610
- idx <- grepl(" [x-y]axis" , names(x ))
611
- axes <- x [idx ]
612
- axes <- lapply(axes , function (x ) { x [y ] <- NULL ; x })
613
- x [idx ] <- axes
614
- x
615
- }
616
-
617
614
# ' Estimate bounding box of a rotated string
618
615
# '
619
616
# ' @param txt a character string of length 1
@@ -738,6 +735,7 @@ rect2shape <- function(rekt = ggplot2::element_rect()) {
738
735
# this helps us import functions in a way that R CMD check won't cry about
739
736
ggfun <- function (x ) getFromNamespace(x , " ggplot2" )
740
737
741
- type <- function (x , y = " geom" ) {
738
+ ggtype <- function (x , y = " geom" ) {
742
739
sub(y , " " , tolower(class(x [[y ]])[1 ]))
743
740
}
741
+
0 commit comments