How to write a ggplot '+'-pipeable function that can refer to the input plot

128 Views Asked by At

I'm trying to write a function that can be called using the '+'-based ggplot2 syntax.

myplot + myfunction

Specifically, the function I'm writing symmetrizes the y-axis about zero, so it needs to determine the y-axis range for the input plot.

So let,

ylim_sym <- function(p){
    get_y_range <- function(p){
        ggplot2::ggplot_build(p)$layout$panel_ranges[[1]]$y.range
        }
    max_offset <- max(abs(get_y_range(p)))
    p + ylim(- max_offset, max_offset)
}

With this function, the following works:

qplot(x = 1:10, y = exp(rnorm(10))) %>% ylim_sym()

But this doesn't work because of some precedence issue between +.gg and %>%:

qplot(x = 1:10, y = exp(rnorm(10))) +
    geom_abline(slope = 0) %>%
    ylim_sym()

(I could write the latter (all_my_ggplot_pipeline) %>% ylim_sym() but it's pretty ugly syntax).

Ideally, I'd like to be able to write ylim_sym such that it can be piped like so,

qplot(x = 1:10, y = exp(rnorm(10))) + ylim_sym()

but I can't work out how to access the plot on the LHS of + within ylim_sym

Any ideas?

2

There are 2 best solutions below

2
On BEST ANSWER

I was able to solve it by doing the following.

StatSymYLim <- ggproto(
  "StatSymYLim", Stat, 
  compute_group = function(data, scales) {
    out <- data.frame(
      x = median(data$x),
      y = c(-1, 1) * max(abs(data$y))
      )
    out
    },
    required_aes = c("x", "y")
  )

ylim_sym <- function(...){
  geom_blank(..., stat = StatSymYLim)
  }

Then the following works as required:

qplot(x = 1:10, y = exp(rnorm(10))) +
  geom_abline(slope = 0) +
  ylim_sym()

My understanding of ggplot2 internals is pretty shaky to be fair, so this might be a naive solution.

3
On

Note: your function needs an update as the structure of the object has slightly changed

Using package ggfun this would work:

# devtools::install_github("moodymudskipper/ggfun")
library(ggfun)

ylim_sym <- function(p){
  get_y_range <- function(p){
    ggplot2::ggplot_build(p)$layout$panel_params[[1]]$y.range
  }
  max_offset <- max(abs(get_y_range(p)))
  p + ylim(- max_offset, max_offset)
}

qplot(x = 1:10, y = exp(rnorm(10))) +
  geom_abline(slope = 0) +
  ylim_sym