5

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?

Mike Wise
  • 22,131
  • 8
  • 81
  • 104
Russ Hyde
  • 2,154
  • 12
  • 21
  • 2
    Sorry but ggplot came along before pipes. And the `%>%` operator is very, very different than the `+` operator. You can't wrap the behavior in your function. Better to just suck it up and use the parenthesis: `(a+b) %>% c`. – MrFlick Dec 13 '17 at 17:23
  • Agreed. Or `ylim_sym <- function(y) { ylim(-max(abs(y)), max(abs(y))) }` for some convenience at least. – Martin Schmelzer Dec 13 '17 at 17:40

2 Answers2

5

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.

zx8754
  • 52,746
  • 12
  • 114
  • 209
Russ Hyde
  • 2,154
  • 12
  • 21
  • 1
    This is a great solution! You are welcome to `accept` your own answer when you ask a good question, as it may help others. – Brian Dec 13 '17 at 18:34
  • Thanks, you always find the solution after someone's told you it's impossible ... – Russ Hyde Dec 13 '17 at 20:33
0

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
moodymudskipper
  • 46,417
  • 11
  • 121
  • 167
  • Thanks. Which version of ggplot2 were you using when you received the update note? – Russ Hyde Dec 04 '18 at 11:03
  • I didn't receive any note, I just couldn't get your function to work so I explored the object :). I use ggplot `3.0.0`, I see now it's not the last so I hope this is still valid, it was released after your question though. do you have issues making this work ? – moodymudskipper Dec 04 '18 at 11:07
  • ah, sorry. I thought the italics were a ggplot-produced warning/note. – Russ Hyde Dec 04 '18 at 11:33