Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

get_dims should handle cases where height or width is fully determined #9

Open
infotroph opened this issue Mar 5, 2016 · 2 comments

Comments

@infotroph
Copy link
Owner

It is possible, though maybe rare in practice, for a Grid layout to contain nulls in the heights but not the widths or vice versa. The concept behind get_dims still applies in these cases, but we need to handle it explicitly. Current version throws "subscript out of bounds" when it discovers there are 0 elements in panel_asps.

I started work on this in 7315e1d but gave up and commented out the whole section in d4dd55f. Considerations:

  • What if the user-specified maximum is smaller than the fixed plot dimensions? Probably shrink to limits, with a warning.
  • Make sure to handle the case where BOTH dimensions are fixed. Easy enough, right? Right?
  • Need to think carefully about detecting user intentions. Does width=3cm, height=1null mean "3 cm tall, width to fill" or "3 cm tall, aspect=1"? Is it possible/reasonable to parse that intention from grob dimensions and respect alone?
  • Related to previous: Does the correct behavior depend on how respect is set? Currently leaning no (if the other dimension has no nulls, it doesn't mean anything to respect them), but my understanding of respect is weak.
  • Am I perverse enough to try to handle composite units, e.g. height=3cm+1null? gtable and GridExtra both seem buggy on this (looks like they ignore the null part?), so probably not worth it.

I don't think this scenario ever happens in ggplot output, but probably needs fixing before I can claim full GridExtra/cowplot support.

@fraupflaume
Copy link

While this issue isn't the same as what started this issue thread, it falls in line with the issue title. (This is both an issue and a solution.)

I recently answered a question on SO. The questioner was looking for a function like get_dims(). However, the user was working in RMarkdown, which has preset sizes. When knitting, the values get_dims() returned were the default knitr figure sizes. You can see the question here. I ended up changing the get_dims() function. I essentially removed the check for presets, so that the user could get the best dimensions for dynamic figure sizing in the RMD. Perhaps this is something you could offer for others along with this very useful library?

Here's the code for get_dims() as modified. There were no additions, just elements that I commented out. (You will find this in the SO Q&A link as well.)

getDims = function(ggobj, maxheight, maxwidth = maxheight, units = "in", 
    ...) 
{
    # if (inherits(ggobj, "ggplot") && !isTRUE(ggobj$respect) && 
    #     is.null(ggobj$theme$aspect.ratio) && is.null(ggobj$coordinates$ratio) && 
    #     is.null(theme_get()$aspect.ratio)) {
    #     return(list(height = maxheight, width = maxwidth))
    # }
    tmpf = tempfile(pattern = "dispos-a-plot", fileext = ".png")
    png(filename = tmpf, height = maxheight, width = maxwidth, 
        units = units, res = 120, ...)
    on.exit({
        dev.off()
        unlink(tmpf)
    })
    if (inherits(ggobj, "ggplot")) {
        g = ggplotGrob(ggobj)
    }
    else if (inherits(ggobj, "gtable")) {
        g = ggobj
    }
    else {
        stop("Don't know how to get sizes for object of class ", 
            deparse(class(ggobj)))
    }
    stopifnot(grid::convertUnit(grid::unit(1, "null"), "in", 
        "x", valueOnly = TRUE) == 0)
    known_ht = sum(grid::convertHeight(g$heights, units, valueOnly = TRUE))
    known_wd = sum(grid::convertWidth(g$widths, units, valueOnly = TRUE))
    free_ht = maxheight - known_ht
    free_wd = maxwidth - known_wd
    if (packageVersion("grid") >= "4.0.0") {
        null_rowhts <- as.numeric(g$heights[grid::unitType(g$heights) == 
            "null"])
        null_colwds <- as.numeric(g$widths[grid::unitType(g$widths) == 
            "null"])
        panel_asps <- (matrix(null_rowhts, ncol = 1) %*% matrix(1/null_colwds, 
            nrow = 1))
    }
    else {
        all_null_rowhts <- (grid::convertHeight(.null_as_if_inch(g$heights), 
            "in", valueOnly = TRUE) - grid::convertHeight(g$heights, 
            "in", valueOnly = TRUE))
        all_null_colwds <- (grid::convertWidth(.null_as_if_inch(g$widths), 
            "in", valueOnly = TRUE) - grid::convertWidth(g$widths, 
            "in", valueOnly = TRUE))
        null_rowhts <- all_null_rowhts[all_null_rowhts > 0]
        null_colwds <- all_null_colwds[all_null_colwds > 0]
        panel_asps <- (matrix(null_rowhts, ncol = 1) %*% matrix(1/null_colwds, 
            nrow = 1))
    }
    max_rowhts = free_ht/sum(null_rowhts) * null_rowhts
    max_colwds = free_wd/sum(null_colwds) * null_colwds
    rowhts_if_maxwd = max_colwds[1] * panel_asps[, 1]
    colwds_if_maxht = max_rowhts[1]/panel_asps[1, ]
    height = min(maxheight, known_ht + sum(rowhts_if_maxwd))
    width = min(maxwidth, known_wd + sum(colwds_if_maxht))
    return(list(height = height, width = width))
}

This function could then be used in R Markdown. Note that the second chunk has dynamic chunk options.

```{r whatChuGot, echo=FALSE, message=FALSE, cache=TRUE}

df <- data.frame(lon = c(14.04, 14.06), lat = c(53.04, 53.07), 
                 species = c("species_1", "species_2"))
cbbox <- make_bbox(lon = c(14.0, 14.2), lat = c(53.0, 53.1), f = .1)
map_data <- get_map(location = cbbox,  source = "stamen")
ggp <- ggmap(map_data) +
  geom_point(data = df,
             aes(x = lon, y = lat), size = 2) +
  facet_wrap(~ species, ncol = 2) + 
  theme(plot.margin = unit(rep(.1, 4), "cm"),
        plot.background = element_rect(fill = "green"),
        panel.background = element_rect(fill = "blue"))

ggpd <- getDims(ggp, maxwidth = 7, maxheight = 8)
```

The new dims are `r ggpd`.

```{r showME,results="asis",fig.height=ggpd$height, fig.width=ggpd$width}
# make me shine
ggp
```

@infotroph
Copy link
Owner Author

infotroph commented Aug 18, 2022

This is very cool! I hadn't thought of either the mapping or Rmarkdown use cases for this, but I'm glad this turned out to be useful for them.

As you probably noticed, I don't maintain this package actively anymore and I know that ggplot has changed a lot since I last tested get_dims, so I'm pleased it worked for you with so little modification.

Since it's been so long (and the package has no test coverage to help me remember :( ), I no longer remember whether there were cases that would fail without that initial check or if it was solely to allow returning early in cases where we knew before doing any work that the answer was going to be [maxheight, maxwidth]. If it's the latter, adding your changes into the package will be a win for everyone.

@fraupflaume would you be willing to turn this into formal PR? I'm happy to review and help test it.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

2 participants