2

There are other variations of this question, such as:

In my opinion, these do not solve the general problem. The first simply pre-calculates the x and y ranges so that proportions can be used. The second two use the "trick" that one can pass +/- Inf to position text in a given corner.

Here are two improvements I think would make for a more generalized solution:

  • allow arbitrary positioning of a label via relative positioning
  • works with variables calculated on the fly via dplyr (rules out pre-calculating ranges/ratios)

For sample data:

data.frame(
  x = runif(100, min = sample(0:50, 1), max = sample(50:1000, 1)),
  y = runif(100, min = sample(0:1000, 1), max = sample(1000:10000, 1))
) %>% 
  mutate(z = x + y) %>% 
# code here to plot and put an annotation at e.g. x = 0.95, y = 0.1, relative to plot limits
Hendy
  • 10,182
  • 15
  • 65
  • 71

1 Answers1

1

I've been wrestling with this today and had a possible improvement to existing answers, leveraging some additional learning about how one can access the data from within the ggplot() call.

I found (see 1 and 2) that by surrounding the ggplot call in {} and passing . as the data argument, one can continue to refer to . throughout the call. This enables:

pos_x <- 0.95
pos_y <- 0.1

data.frame(
  x = runif(100, min = sample(0:50, 1), max = sample(50:1000, 1)),
  y = runif(100, min = sample(0:1000, 1), max = sample(1000:10000, 1))
) %>% 
  mutate(z = x + y) %>% {
  ggplot(., aes(x = x, y = z)) + geom_point() +
    annotate(geom = "text", label = "some label",
             x = min(.$x) + pos_x * diff(range(.$x)),
             y = min(.$z) + pos_y * diff(range(.$z)),
             hjust = 1, vjust = 1) +
    scale_x_continuous(limits = range(.$x)) +
    scale_y_continuous(limits = range(.$z))
}

You can re-run this and observe the plot label stay fixed even as the x/y axis ranges change significantly. For some improvement opportunities:

  • the lower axis limit varies with the data so just using y = number_close_to_zero*max(.$y) could be risky if min(.$y) is too high. For this reason, I manually specified the axis limits
  • similarly, for this reason the position isn't exact between plots if you just do pos_x_rel * max(.$x), so I used min(.$x) + diff(range(.$x)) instead
  • hjust and vjust aren't automatic; they need to be tweaked depending on the desired label location
  • it would be nice to automagically get the variable used for x/y vs. having to use the column name. In other words, if I wanted to change to aes(..., y = y), I wouldn't have to change instances of .$z to .$y.
Hendy
  • 10,182
  • 15
  • 65
  • 71
  • 1
    This is what `annotation_custom` does, without all the range calculations. To get a fixed text annotation, you can plot normally without the curly brackets and just add `+ annotation_custom(grid::textGrob("some label", pos_x, pos_y))` to get a fixed label, regardless of the axis limits. – Allan Cameron Sep 01 '22 at 22:17
  • Thanks @AllanCameron. I have run into that somewhere, but can't re-find it... and it wasn't in the linked questions. I wonder why none of those feature such a simpler approach. Thanks for the new tip; feel free to add it as an answer; I think it addresses all of the improvement opportunities I listed (maybe minus hjust/vjust). – Hendy Sep 01 '22 at 22:23
  • 1
    you can add vjust / hjust inside `textGrob`- use `grid::testGrob("some label", pos_x, pos_y, gp = grid::gpar(vjust = 0.75, hjust = 0))` etc – Allan Cameron Sep 01 '22 at 22:28
  • @AllanCameron: sorry, I wasn't clear. I meant that it didn't address my improvement opportunity that hjust/vjust would be automatically figured out. Like if this were properly implemented so someone could use `position = "top_right"` or `position = "bottom_left"`, probably `hjust/vjust` would not be the same in both cases. So, the custom annotation handles my calculation and variable reference issues, but can't address figuring out what one might want for justification. Hope that makes sense. – Hendy Sep 01 '22 at 22:32
  • I see. I think that would still be straightforward with a little wrapper function around `annotation_custom`. For example "top right" would set the parameters to x = 1, y = 1, vjust = 1, hjust = 1 and so on. Might be a handy little function! – Allan Cameron Sep 01 '22 at 22:46