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