Question Details

No question body available.

Tags

r ggplot2

Answers (3)

Accepted Answer Available
Accepted Answer
November 8, 2025 Score: 7 Rep: 178,791 Quality: Expert Completeness: 80%

A ggplot object is like a blueprint for building a house. Every time you draw a ggplot object, the ggplotbuild and ggplotgtable functions act like builders, turning your blueprint into a gtable, which is what is actually drawn on the screen. The gtable is like the finished house after the building process is complete. If you later build an extension onto your house, that does not magically change the original blueprint. Similarly, modifying a gtable after it is built can't affect the ggplot object.

To get a ggplot to generate a modified final gtable, what you would need to do is include the information for your annotation in the ggplot "blueprint", and write versions of the building functions ggplotbuild and ggplotgtable that know how to incorporate the annotation in your plot.

Fortunately, ggplotbuild and ggplotgtable are generic functions, which means that as long as your annotated ggplot has its own special class, you can write methods for these functions which only apply to your new special class. Now that ggplot2 is written using the S7 object-oriented system, we can use S7 inheritance to make this work. Essentially, your annomargin function should convert the ggplot object to a new sub-class of ggplot as well as including the annotation information, then your new build methods produce the modified gtable.

There are several caveats here. Firstly, this is probably far more complex than you were expecting. Secondly, the solution I have sketched here works in the console, but it makes use of some of ggplot2's unexported functions, and you couldn't do this inside a package unless you copied these functions from the ggplot2 code base into your own package. Thirdly, adding text grobs directly misses out on the benefits of the theme system, so at present there is no way to change the size, style and colour of your text. The easiest way to do this would be to add size, font, face and colour arguments into anno
margin which ultimately get passed to the builder methods, but ultimately it would be best to add new theme components which can be interrogated during the build process.

In view of all that, I think something like Jon Spring's answer is going to turn out to be more practical, but I thought I would show how it is possible to create a modifiable ggplot object that builds a modified gtable. If nothing else it gives a practical demonstration of the relatively new S7 system in action.


Let's start with the function that will actually modify your gtable. It takes an annotation and a side (t, r, b, l), and adds the annotation to the gtable at the correct side and at the correct angle:

library(S7)
library(ggplot2)

manipulate_gtable
November 11, 2025 Score: 3 Rep: 521 Quality: Medium Completeness: 80%

Allan Cameron wrote a great answer that solves all the hard parts of this question. His code has a limitation though: If you add multiple annotations to a plot, only the final one is actually applied -- the previous ones get discarded.

Here, I've slightly modified his code to fix this. The basic idea is to redefine Allan's classggplotanno so that its only new property is a list named annotations that can grow to store multiple annotations. So then ggplotadd.anno appends a new annotation to any existing annotations. Lastly, the new ggplotgtable method iteratively applies all the annotations to the gtable.

Aside from the above changes, this code comes from Allan's answer, and thus his explanation of the code still applies.

Update (2025-11-12): With a hint from Teun van den Brand, I've revised and majorly simplified the code. The most important change is that the custom ggplotbuild method is now just two lines of code instead of 60+ and does not rely on any unexported ggplot2 functions. Basically, instead of copying and modifying the source code for method(ggplotbuild, classggplot), I just call that method on our classggplotanno object and then convert the output to our custom classggplotbuiltanno with the new annotations property. This required changing classggplotbuiltanno so that it inherits from classggplotbuilt rather than S7::S7object, and the resulting definition of classggplotbuiltanno is now much simpler than before.

One quirk is that I had to use NextMethod (from the base package) to dispatch to the parent class; I couldn't get S7::super to work here. I think that's because the ggplot2 codebase is in a transitional state where it hasn't been fully converted to S7 yet. If ggplot2 does eventually go full S7, I'm not sure if NextMethod will still work and I may need to switch to using S7::super.

library(ggplot2)

manipulate
gtable
October 30, 2025 Score: 2 Rep: 70,715 Quality: Medium Completeness: 80%

I have not made this into a function yet, but I wanted to outline a potential approach using patchwork that might be simpler to manage. To be a function, this would require adding some bookkeeping to keep track of when we need 2 vs. 3 heights/widths. (And it's not clear to me why we need 3 here for b but only 2 for d.)

wrapelements(plot = grid::textGrob("Hello, top of the world!")) /
  p + plotlayout(height = c(1, 20)) -> a

Add grob on bottom

heights here needs to be 3 elements, for top / plot / bottom

a / wrapelements(plot = grid::textGrob("Hello, bottom of the world!")) + plotlayout(heights = c(1, 20, 1)) -> b

Add grob on left

wrapelements(plot = grid::textGrob("Hello, left of the world!", rot = 90)) + b + plotlayout(widths = c(1, 20)) -> c

Add another grob on top

wrapelements(plot = grid::textGrob("Hello again, top of the world!")) / c + plotlayout(height = c(1, 20)) -> d

enter image description here