2

I'd like to create an html output with R markdown that includes a collection of plots made with ggmap(). When the map (or maps, in case of facets) has more width than height, there is white space above and below the plot in the html output, which I would like to remove automatically without much extra work.

White space has been discussed here previously. One solution I found is to specify fig.height and fig.width appropriately (by trying out manually). However, I would rather avoid having to try out suitable height/width values for each plot, as each of my plots comes in different height/width ratios.

A previous idea has been to figure out the width/height ratio of the plot and then to specify fig.asp: How to remove white space above and below image in R Markdown? And someone suggested a function to determine the ratio with help of an R function: Rmarkdown crop white space around ggplots But this works only when saving the plot as png as an intermediate step.

Is there a way to adjust either the margin of the plot or how it is included in markdown automatically (without a detour of saved images or manual adjustment of some height/width/asp values) to remove the extra white space above and below the plot?

A working example:

---
title: "Plot margins"
output: html_document
---

The following plot has some white space above and below it.

```{r, echo=FALSE, message=FALSE, cache=TRUE}
require(ggmap)
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")
ggmap(map_data) +
  geom_point(data = df,
             aes(x=lon, y=lat), size=2) +
  facet_wrap(~ species, ncol=2)
```

The next plot does not have that large white margin.

```{r, echo=FALSE, message=FALSE, cache=TRUE}
require(ggmap)
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")
ggmap(map_data) +
  geom_point(data = df,
             aes(x=lon, y=lat), size=2)
```

Some text below.
erica
  • 23
  • 4

1 Answers1

1

Updated:

Original content at the bottom

Considering the original answer, I looked at the function get_dims() to see how the data is collected.

I found that making it ignore the preset figure sizes was incredibly easy. I suggest that you add this function as a chunk in and onto itself. You won't need to call the DeLuciatoR library, either.

I left the original code from that function, get_dims(), renamed it getDims(), and commented out the part that causes the issues regarding RMarkdown's preset figure sizing. You could just delete that part, but I thought it might be useful to see the original code from the package.

```{r getterDims}
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))
}
```

Now when you use this function, it doesn't matter what the preset figure sizes are or whether you designate coord_fixed().

For example:

```{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
```

enter image description here



Originally:

I used the function get_dims() from the package DeLuciatoR. This isn't a Cran package; you have to get it through Github.

devtools::install_github("infotroph/DeLuciatoR")

If you ran your plot with this function, you'll get the best dimensions. However, if you preset the graph size and then run it, you'll get whatever your preset is (i.e., fig.height, fig.width). Why does this matter? Well, R Markdown presets the size of your graphs. You have to work around this constraint.

First, I'm going to show you the basic premise of how this works. Next, I'll show you how you can incorporate this into R Markdown.

For this first graph, I added color to the background, so you could see what I did with the margins. (What is there; what isn't there; all that jazz...)

I added borders to all the images so that you could see the difference between what is SO and the image.

library(tidyverse)
library(ggmap)
library(DeLuciatoR)
ggp <- ggmap(map_data) +
  geom_point(data = df,
             aes(x = lon, y = lat), size = 2) +
  facet_wrap(~ species, ncol = 2) + 
  coord_fixed() +                                       # lat/lon equidistant
  theme(plot.margin = unit(rep(.1, 4), "cm"),           # make the plot margin 1 mm
        plot.background = element_rect(fill = "green"),
        panel.background = element_rect(fill = "blue")) # show me my margins

enter image description here


Now that the graph object is created get the dimensions.

get_dims(ggp, maxwidth = 7, maxheight = 8)
# $height
# [1] 2.229751
# 
# $width
# [1] 7
#  

That's pretty awesome IMO.

coord_fixed() is part of what makes this work in R Markdown. (It forces the issue with the preset sizes.) An alternative would be to use this get_dims() call outside of your RMD (yuck). There are probably many ways to set this up so that RMD doesn't run over the best dimensions. You'll have to try it out and see what works for you.

Okay, incorporating this into RMD...

You're going to split the graphs into two separate chunks. First, call the graph to an object (ggp here) and call the dimensions (ggpd here). In a separate chunk, print the graph with the dimensions in the chunk options. In the 2nd chunk, ggpd$height is the setting for fig.height; ggpd$width is the fig.width.

My change; no issues!

```{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) + 
  coord_fixed() +
  theme(plot.margin = unit(rep(.1, 4), "cm"),
        plot.background = element_rect(fill = "green"),
        panel.background = element_rect(fill = "blue"))

ggpd <- get_dims(ggp, maxwidth = 7, maxheight = 8)
# $height
# [1] 2.229751
# 
# $width
# [1] 7
#      
```
The dims are `r ggpd`.

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

enter image description here


Let me know if you have any questions!

This is a shot of my entire RMD (colors are over the top, but point made). If you're not feeling the HUGE margins for html_document, you could add the style max-width: unset; for .main-container. The default is 940px, regardless of monitor size.

enter image description here

Kat
  • 15,669
  • 3
  • 18
  • 51
  • I think this might work for utm coordinates but doesn't entirely work for lon/lat. It relies on coord_equal(), and when applied to lon/lat this produces a map in which the projection/aspect ratio is not quite right (I compared with Google Maps and plots produced with coord_map() and coord_quickmap() options). Inspired by your answer, I used coord_quickmap() to get an aspect ratio for a map (which can then be pasted into fig.asp), given the data input (not the plot). This won't work as full solution, e.g. when using scale_x/y_continuous() to zoom into the plot at the plotting stage. – erica Aug 15 '22 at 10:39
  • I should add: I would be happy with UTM, but ggmap() only seems to work with lon/lat. Any suggestions to switch to utm with ggmap might be another route to a solution. – erica Aug 15 '22 at 10:56
  • I figured it out! I've updated my answer with the information. – Kat Aug 15 '22 at 15:29
  • 1
    This works, awesome. I can now save your function modification separately and use it whenever needed. You could even suggest to the authors of the get_dims() function to include your modification in their package, or as an option to the function. – erica Aug 17 '22 at 07:45
  • Done! I added this suggestion to the library's GitHub issues. – Kat Aug 17 '22 at 17:52