Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

grid.layout doesn't like respect and compound units

Using unit.pmax as the default comparison of widths/heights in gtable is proving harder than I'd hoped; after several hours of head scratching I've narrowed it down to this situation:

library(grid)

w <- list(unit(1,"null"), unit(1,"null"))
class(w) <-  c("unit.list", "unit")
h <- unit(1, "in")
gl1 <- grid.layout(1, 2, widths = w, heights = h,
                   respect = TRUE)
grid.newpage()
grid.show.layout(gl1) # fine

w2 <- w
w2[[1]] <- unit.pmax(unit(1,"null"), unit(1,"null"))
gl2 <- grid.layout(1, 2, widths = w2, heights = h,
                   respect = FALSE)
grid.newpage()
grid.show.layout(gl2)# fine

gl3 <- grid.layout(1, 2, widths = w2, heights = h,
                   respect = TRUE)
grid.newpage()
grid.show.layout(gl3)

## Error in grid.Call.graphics(L_setviewport, vp, TRUE) : 
##  non-finite location and/or size for viewport

so the combination of unit.pmax(unit(1,"null"), unit(1,"null")) and respect = TRUE makes grid complain. In case you're wondering, that situation would come up in ggplot2 with facet_grid and theme(aspect.ratio = ...).

I can vaguely picture that unit.pmax() should simplify null units before attempting to use the respect parameter, but I don't really know what this all means. In practice though, it prevents me from improving gtable's cbind/rbind.

Any workaround?

Edit: I'm not sure how to provide a minimal example with ggplot2, other than installing my fork and running

ggplot(data.frame(x=1:8, y=1:8, f=gl(2,4)), aes(x, y)) + 
  geom_point() +  
  facet_grid(f~.) + 
  theme(aspect.ratio=3)
# Error in grid.Call.graphics(L_setviewport, vp, TRUE) : 
#  non-finite location and/or size for viewport

so unit.pmax() fails in this case, while the current comparison method compare.unit(,,pmax) fails in other situations, such as,

p1 = qplot(1, 1); p2 = qplot(1,1)
cbind(ggplotGrob(p1), ggplotGrob(p2), size="max")
# Error in mmm < each : comparison of these types is not implemented
like image 791
baptiste Avatar asked Jun 01 '14 20:06

baptiste


2 Answers

It's not optimal, but if all else fails, you could just rewrite unit.pmax to make it do what you wish it did.

The following function acts just like unit.pmax() except that, whenever it's asked to find the maximimum of two or more unit objects, all in "null" units, it returns their value of the "largest" one, rather than an expression of the form max(x,y,...). (See the second code block below for an example.)

unit.pmax2 <- 
function (...) 
{
    select.i <- function(unit, i) {
        unit[i, top = FALSE]
    }
    x <- list(...)
    numargs <- length(x)
    if (numargs == 0L) 
        stop("no arguments where at least one expected")
    maxlength <- 0L
    for (i in seq_len(numargs)) if (length(x[[i]]) > maxlength) 
        maxlength <- length(x[[i]])        
    ## result <- max(unit.list.from.list(lapply(x, select.i, 1L)))
    UL <- grid:::unit.list.from.list(lapply(x, select.i, 1L))                 ##
    result <- if(all(sapply(UL, attr, "unit")=="null")) {                     ##
                  UL[which.max(UL)]} else {max(UL)}                           ##
    if (maxlength > 1L) 
        for (i in 2L:maxlength) {
            ## result <- unit.c(result, max(unit.list.from.list(lapply(x, 
            ##             select.i, i))))
            UL <- grid:::unit.list.from.list(lapply(x, select.i, i))          ##
            temp <- if(all(sapply(UL, attr, "unit")=="null")) {               ##
                        UL[which.max(UL)]} else {max(UL)}                     ##
            result <- unit.c(result, temp)                                    ##
        }
    result
}

To see the difference between unit.pmax() and unit.pmax2(), compare:

A <- list(unit(1,"null"), unit(1,"null"), unit(1,"null"))
B <- list(unit(1,"null"), unit(4,"null"), unit(1,"null"))
C <- list(unit(1,"null"), unit(2,"null"), unit(1,"inch"))

class(A) <- class(B) <- class(C) <- c("unit.list", "unit")

unit.pmax(A, B, C)
# [1] max(1null, 1null, 1null) max(1null, 4null, 2null) max(1null, 1null, 1inch)    
unit.pmax2(A, B, C)
# [1] 1null                    4null                    max(1null, 1null, 1inch)

Testing it out shows that it works. (Note that you also need to replace w2[[1]] <- ... with w2[1] <- ... to avoid a complaint when respect = TRUE.)

library(grid)

w2 <- list(unit(1,"null"), unit(1,"null"))
class(w2) <-  c("unit.list", "unit")
h <- unit(1, "in")

w2[1] <- unit.pmax2(unit(1,"null"), unit(1,"null"))
## w2[[1]] <- unit.pmax(unit(1,"null"), unit(1,"null"))  ## For comparison
gl3 <- grid.layout(1, 2, widths = w2, heights = h,
                   respect = TRUE)
grid.newpage()
grid.show.layout(gl3)

enter image description here

like image 125
Josh O'Brien Avatar answered Oct 20 '22 01:10

Josh O'Brien


A fix by Paul Murrell in R-devel @r65845 appears to solve the problem. Unfortunately, that means the update to gtable will have to wait at least until the next R release (and possibly much longer, as ggplot2 dev usually takes a conservative approach about supporting older releases).

like image 42
baptiste Avatar answered Oct 20 '22 02:10

baptiste