use npc units in annotate()

1.5k views Asked by At

I have a ggplot object. I would like to add some text with annotate(), and I would like to specify the coordinates of the text in npc units. Is this possible?

This minimal example demonstrates how text is ordinarily positioned with annotate():

library(ggplot2)
p <- ggplot(mtcars, aes(mpg, wt)) + geom_point()
p + annotate("text", x = 30, y = 4.5, label = "hello")

I would like to achieve the same effect, but instead of specifying x and y in native coordinates, I would like to specify them with npc coordinates. For the purpose of this example, I am not worried about exactly translating x = 30 and y = 4.5 into npc units. I just want to know whether npc units can be used in annotate() at all.

There are two related strategies, but they're not entirely satisfactory:

  1. One can use npc units by specifying them to, say, grid::textGrob(). And one can then place the grob with annotation_custom(), as in this answer by @baptiste. But this solution is a bit more cumbersome than I would like.

  2. The "ggpmisc" package includes geom_text_npc(). But it doesn't yet work with annotate(). That is, annotate("text_npc", ...) doesn't seem to work. [Edit: it now works. See Pedro Aphalo's message below.]

There are also many related posts. In particular, Greg Snow has suggested using grid to create a viewport with the dimensions of p and then annotating that viewport. And @teunbrand has suggested a method that entails converting p to a "gtable" object (with ggplotGrob()) and then drawing that "gtable" object. Either of these strategies can probably be adapted to my purposes. But is there a more straightforward way to use npc coordinates with annotate()?

2

There are 2 answers

0
Allan Cameron On BEST ANSWER

Personally, I would use Baptiste's method but wrapped in a custom function to make it less clunky:

annotate_npc <- function(label, x, y, ...)
{
  ggplot2::annotation_custom(grid::textGrob(
    x = unit(x, "npc"), y = unit(y, "npc"), label = label, ...))
}

Which allows you to do:

p + annotate_npc("hello", 0.5, 0.5)

enter image description here

Note this will always draw your annotation in the npc space of the viewport of each panel in the plot, (i.e. relative to the gray shaded area rather than the whole plotting window) which makes it handy for facets. If you want to draw your annotation in absolute npc co-ordinates (so you have the option of plotting outside of the panel's viewport), your two options are:

  1. Turn clipping off with coord_cartesian(clip = "off") and reverse engineer the x, y co-ordinates from the given npc co-ordinates before using annotate. This is complicated but possible
  2. Draw it straight on using grid. This is far easier, but has the downside that the annotation has to be drawn over the plot rather than being part of the plot itself. You could do that like this:
annotate_npc_abs <- function(label, x, y, ...) 
{
  grid::grid.draw(grid::textGrob(
    label, x = unit(x, "npc"), y = unit(y, "npc"), ...))
}

And the syntax would be a little different:

p 
annotate_npc_abs("hello", 0.05, 0.75)

enter image description here

1
Pedro J. Aphalo On

As of 'ggpmisc' (>= 0.3.6) the following code works as expected (in CRAN as of 2020-09-10).

library(ggpmisc)
p <- ggplot(mtcars, aes(mpg, wt)) + geom_point()
# default justification is "inward"
p + annotate("text_npc", npcx = 0.8, npcy = 0.75, label = "hello")
# same justification as default for "geom_text()"
p + annotate("text_npc", npcx = 0.8, npcy = 0.75, label = "hello",
             hjust = "center", vjust = "middle")