автокроп граненых участков, сделанных ggplot

При создании многослойных графиков в ggplot и изменении соотношения сторон обычно остается много белого пространства слева и справа или выше и ниже графика. Например:

library(ggplot2)
df <- data.frame(x=rep(1,3), y=rep(1,3), z=factor(letters[1:3]))
p <- ggplot(df, aes(x, y)) + geom_point() + coord_fixed(ratio=1) + facet_grid(z ~ .)
ggsave("plot.jpg", p, scale=1, device="jpeg")

Есть ли способ автоматически обрезать график?


person Wilbert    schedule 17.08.2017    source источник
comment
Вы имеете в виду пустое пространство между разделенными участками или вокруг всего участка? В первом случае это должно помочь: stackoverflow.com/questions/22945651/ & stackoverflow.com/questions/30983752/. Что касается последнего, я считаю, что по умолчанию ggsave использует размеры вашего текущего графического устройства, если вы не укажете высоту и ширину. Вы ищете способ автоматически получать разумные спецификации высоты / ширины на основе вашего участка?   -  person Z.Lin    schedule 17.08.2017
comment
Я имею в виду пространство вокруг всего сюжета. Мне было бы очень интересно найти способ автоматически получить спецификации высоты / ширины для ggsave.   -  person Wilbert    schedule 17.08.2017


Ответы (1)


Это то, что я придумал. Я тестировал его на вашем образце, и, похоже, он там работает нормально, но заранее извиняюсь, если он сломается где-то еще. Мне пришлось покопаться в версии grob для информации о ширине / высоте, и в зависимости от того, является ли график фасетным или нет, информация об атрибутах для «unit» находится в разных местах.

Надеюсь, что кто-то более разбирающийся в объекте unit пакета сетки сможет вмешаться, но вот что у меня есть:

# Note that you need to set an upper limit to the maximum height/width
# that the plot can occupy, in the max.dimension parameter (defaults to
# 10 inches in this function)

ggsave_autosize <- function(filename, plot = last_plot(), device = NULL, path = NULL, scale = 1, 
                            max.dimension = 10, units = c("in", "cm", "mm"), 
                            dpi=300, limitsize = TRUE){

  sumUnitNull <- function(x){
    res <- 0
    for(i in 1:length(x)){ 
      check.unit <- ifelse(!is.null(attr(x[i], "unit")), attr(x[i], "unit"), 
                           ifelse(!is.null(attr(x[i][[1]], "unit")), attr(x[i][[1]], "unit"), NA))
      if(!is.na(check.unit) && check.unit == "null") res <- res + as.numeric(x[i])
    }
    return(res)
  }

  # get width/height information from the plot object (likely in a mixture of different units)
  w <- ggplotGrob(plot)$widths
  h <- ggplotGrob(plot)$heights

  # define maximum dimensions
  w.max <- grid::unit(max.dimension, units) %>% grid::convertUnit("in") %>% as.numeric()
  h.max <- grid::unit(max.dimension, units) %>% grid::convertUnit("in") %>% as.numeric()

  # sum the inflexible size components of the plot object's width/height 
  # these components have unit = "in", "mm", "pt", "grobheight", etc
  w.in <- w %>% grid::convertUnit("in") %>% as.numeric() %>% sum()
  h.in <- h %>% grid::convertUnit("in") %>% as.numeric() %>% sum()

  # obtain the amount of space available for the flexible size components
  w.avail <- w.max - w.in
  h.avail <- h.max - h.in

  # sum the flexible sized components of the plot object's width/height 
  # these components have unit = "null"
  w.f <- sumUnitNull(w)
  h.f <- sumUnitNull(h)

  # shrink the amount of avilable space based on what the flexible components would actually take up
  if(w.f/h.f > w.avail/h.avail) h.avail <- w.avail/w.f*h.f else w.avail <- h.avail/h.f*w.f

  w <- w.in + w.avail
  h <- h.in + h.avail

  ggsave(filename, plot = plot, device = device, path = path, scale = scale,
         width = w, height = h, units = units, dpi = dpi, limitsize = limitsize)
}


p <- ggplot(mpg, aes(displ, cty)) + geom_point() + coord_fixed(ratio=1)
p <- p + facet_grid(. ~ cyl)

ggsave("pOriginal.png", p + ggtitle("original"))
ggsave_autosize("pAutoSize.png", p + ggtitle("auto-resize"))
ggsave_autosize("pAutoSize8.png", p + ggtitle("auto-resize, max dim = 8in x 8in"), max.dimension = 8, units = "in")

Оригинальная версия без обрезки. Слева / справа есть черное пространство:

оригинал

Автоматически обрезанная версия. Высота = 10 дюймов:

autosize

Автоматически обрезанная версия. Высота = 8 дюймов (поэтому шрифт выглядит немного больше):

autosize2

person Z.Lin    schedule 17.08.2017