@@ -83,9 +83,16 @@ gg2list <- function(p, width = NULL, height = NULL) {
8383 panel <- ggfun(" train_ranges" )(panel , p $ coordinates )
8484 data <- by_layer(function (l , d ) l $ compute_geom_2(d ))
8585 # ------------------------------------------------------------------------
86- # end of ggplot_build(), start of layer -> trace conversion
86+ # end of ggplot_build()
8787 # ------------------------------------------------------------------------
8888
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
8996 nPanels <- nrow(panel $ layout )
9097 nRows <- max(panel $ layout $ ROW )
9198 nCols <- max(panel $ layout $ COL )
@@ -143,37 +150,6 @@ gg2list <- function(p, width = NULL, height = NULL) {
143150 # we may tack on more traces with visible="legendonly"
144151 traces <- lapply(traces , function (x ) { x $ showlegend <- FALSE ; x })
145152
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-
177153 # ------------------------------------------------------------------------
178154 # axis/facet/margin conversion
179155 # ------------------------------------------------------------------------
@@ -198,8 +174,8 @@ gg2list <- function(p, width = NULL, height = NULL) {
198174 gglayout $ titlefont <- text2font(theme $ plot.title )
199175 gglayout $ margin $ t <- gglayout $ margin $ t + gglayout $ titlefont $ size
200176 }
201-
202- # panel margins
177+ # panel margins must be computed before panel/axis loops
178+ # (in order to use get_domains())
203179 panelMarginX <- unitConvert(
204180 theme [[" panel.margin.x" ]] %|| % theme [[" panel.margin" ]],
205181 " npc" , " width"
@@ -222,6 +198,7 @@ gg2list <- function(p, width = NULL, height = NULL) {
222198 theme [[" axis.ticks.x" ]] %|| % theme [[" axis.ticks" ]],
223199 " npc" , " height"
224200 )
201+ # allocate enough space for the _longest_ text label
225202 axisTextX <- theme [[" axis.text.x" ]] %|| % theme [[" axis.text" ]]
226203 labz <- unlist(lapply(panel $ ranges , " [[" , " x.labels" ))
227204 lab <- labz [which.max(nchar(labz ))]
@@ -233,6 +210,7 @@ gg2list <- function(p, width = NULL, height = NULL) {
233210 theme [[" axis.ticks.y" ]] %|| % theme [[" axis.ticks" ]],
234211 " npc" , " width"
235212 )
213+ # allocate enough space for the _longest_ text label
236214 axisTextY <- theme [[" axis.text.y" ]] %|| % theme [[" axis.text" ]]
237215 labz <- unlist(lapply(panel $ ranges , " [[" , " y.labels" ))
238216 lab <- labz [which.max(nchar(labz ))]
@@ -263,17 +241,16 @@ gg2list <- function(p, width = NULL, height = NULL) {
263241 axisName <- lay [, paste0(xy , " axis" )]
264242 anchor <- lay [, paste0(xy , " anchor" )]
265243 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+ }
267249 # type of unit conversion
268250 type <- if (xy == " x" ) " height" else " width"
269- # set some axis defaults (and override some of them later)
270251 # https://plot.ly/r/reference/#layout-xaxis
271- #
272- # TODO: implement minor grid lines with another axis object
273- # and _always_ hide ticks/text?
274252 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()
277254 type = " linear" ,
278255 autorange = FALSE ,
279256 tickmode = " array" ,
@@ -297,53 +274,21 @@ gg2list <- function(p, width = NULL, height = NULL) {
297274 zeroline = FALSE ,
298275 anchor = anchor
299276 )
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 )
307280
281+ # do some stuff that should be done once for the entire plot
308282 if (i == 1 ) {
309283 # convert days to milliseconds, if necessary
310- if (" date" %in% p $ scales $ get_scales( xy ) $ scale_name ) {
284+ if (" date" %in% sc $ scale_name ) {
311285 traces <- lapply(traces , function (z ) {
312286 z [[xy ]] <- z [[xy ]] * 24 * 60 * 60 * 1000
313287 z
314288 })
315289 }
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`
326291 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`
347292 stripSize <- unitConvert(stripText , " pixels" , type )
348293 if (xy == " x" ) {
349294 gglayout $ margin $ t <- gglayout $ margin $ t + stripSize
@@ -352,7 +297,39 @@ gg2list <- function(p, width = NULL, height = NULL) {
352297 gglayout $ margin $ r <- gglayout $ margin $ r + stripSize
353298 }
354299 }
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+ }
356333 }
357334
358335 } # end of axis loop
@@ -362,49 +339,40 @@ gg2list <- function(p, width = NULL, height = NULL) {
362339 ydom <- gglayout [[lay [, " yaxis" ]]]$ domain
363340 border <- make_panel_border(xdom , ydom , theme )
364341 gglayout $ shapes <- c(gglayout $ shapes , border )
342+
365343 # facet strips -> plotly annotations
366344 # 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" )
368347 txt <- paste(
369- lay [, as.character(p $ facet $ rows )],
370- collapse = " , "
348+ lay [, as.character(p $ facet [[vars ]])], collapse = " , "
371349 )
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 )
382358 }
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 " ]])) {
385361 txt <- paste(
386- lay [, as.character(p $ facet [[vars ]])],
387- collapse = " , "
362+ lay [, as.character(p $ facet $ rows )], collapse = " , "
388363 )
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 )
399372 }
400373
401374 } # end of panel loop
402375
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-
408376 # ------------------------------------------------------------------------
409377 # guide/legend conversion
410378 # ------------------------------------------------------------------------
@@ -480,14 +448,52 @@ gg2list <- function(p, width = NULL, height = NULL) {
480448 return (NULL )
481449 }
482450
483- traces <- c(traces , lapply(gdefs , gdef2trace ))
451+ traces <- compact( c(traces , lapply(gdefs , gdef2trace ) ))
484452
485453 # TODO:
486454 # (1) shrink guide size(s). Set fractions in colorbar.lenmode
487455 # (2) position guide(s)?
488456 # (3)
489457 }
490458
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+
491497 l <- list (data = compact(traces ), layout = compact(gglayout ))
492498 # ensure properties are boxed correctly
493499 l <- add_boxed(rm_asis(l ))
@@ -605,15 +611,6 @@ has_facet <- function(x) {
605611 inherits(x $ facet , c(" grid" , " wrap" ))
606612}
607613
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-
617614# ' Estimate bounding box of a rotated string
618615# '
619616# ' @param txt a character string of length 1
@@ -738,6 +735,7 @@ rect2shape <- function(rekt = ggplot2::element_rect()) {
738735# this helps us import functions in a way that R CMD check won't cry about
739736ggfun <- function (x ) getFromNamespace(x , " ggplot2" )
740737
741- type <- function (x , y = " geom" ) {
738+ ggtype <- function (x , y = " geom" ) {
742739 sub(y , " " , tolower(class(x [[y ]])[1 ]))
743740}
741+
0 commit comments