8

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
Richie Cotton
  • 118,240
  • 47
  • 247
  • 360
baptiste
  • 75,767
  • 19
  • 198
  • 294
  • IIRC, unit calculation, i.e., `unit.pmax` etc, is possible only at the drawing time. – kohske Jun 02 '14 at 06:10
  • And could you show the concrete situation wherein you need the workaround? – kohske Jun 02 '14 at 06:11
  • @kohske good to see you here -- I'm working on Paul Murrell's fork of gtable as a base, trying to implement a few additional functions and fix some issues. Specifically, this problem came up with cbind/rbind; trying to use [unit.pmax instead of `compare.units(,,max)` fails](https://github.com/baptiste/gtable/blob/master/R/rbind-cbind.r#L46) for ggplots that use a combination of `facet_grid()` and `theme(aspect.ratio = ...)`. – baptiste Jun 02 '14 at 10:32

2 Answers2

3

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

Josh O'Brien
  • 159,210
  • 26
  • 366
  • 455
  • 1
    nice work, thanks! I anticipate problems though, if we have `w2[[1]] <- unit.pmax2(unit(1,"null"), unit(2,"null"))` for example. I suspect the real solution would be to special-case the comparison of multiple null units (simply returning the max value). – baptiste Jun 02 '14 at 17:16
  • @baptiste Great point. I just fixed that and changed the explanation up top and the example in the 2nd code block to reflect/highlight the change. (Knew I wouldn't get it right on the first iteration, but figured you'd be an ideal person to just bounce the idea off of!) – Josh O'Brien Jun 02 '14 at 17:46
  • Excellent edit, that should work. Unfortunately using non-exported grid functions would compromise its use in a package. Maybe the best option is to file a feature request for grid itself. – baptiste Jun 02 '14 at 18:52
  • @baptiste For sure. I almost suggested that earlier, as I'm guessing Dr. Murrell would be receptive to the request, or else that he would have some other good suggestion. Please let me know what you hear back from him. – Josh O'Brien Jun 02 '14 at 18:54
3

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).

baptiste
  • 75,767
  • 19
  • 198
  • 294