Skip to contents
## Using: 'ggplot2' == 4.0.1 and 'ggpp' == 0.6.0.9000

The geom element of ‘ggplot2’ themes

Version 4.0.0 of package ‘ggplot2’ introduced several enhancements to the use of themes. Of these, the addition of several default aesthetics of geoms to the theme tree and of element_geom() are relevant to ‘ggpp’. The code in the geometries defined in ‘ggpp’ had to be updated to enable support for themes when used together with ‘ggplot2’ (>= 4.0.0). Examples of their use are given in this section.

To avoid a hard dependency on ‘ggplot2’ (>= 4.0.0) a fall-back was implemented. The fall back simply provides the same defaults as the default theme from ‘ggplot2’ (< 4.0.0). This fall-back mechanism has been tested with ‘ggplot2’ (== 3.5.2).

One aim of this article is to test what already works and what does not yet work in the version of ‘ggpp’ used to render this article. This article was rendered on 2026-01-18 with ‘ggpp’ version 0.6.0.9000 and ‘ggplot2’ version 4.0.1.

In ‘ggplot2’ (>= 4.0.0) it is possible to change the defaults used by geoms in coordination with the styling of the plot as a whole.

We will use p as a base plot for testing the different geometries. Because of this, we add mappings to the aesthetics required by all the different geometries that we will test.

p0 <-
  ggplot(mtcars, aes(x = wt, y = mpg)) +
  geom_point()

Using the default theme.

p0

In a call to theme() it is now easy to modify the default for colour and other aesthetics when used as part of the overall graphic design. Taking into account that using set_theme() to set a theme as default we can set the style of all subsequent plots, this new feature is very convenient.

p0 + theme(geom = element_geom(ink = "blue", paper = "wheat"))

The geom slot of themes was introduced in ‘ggplot2’ (== 4.0.0). As seen above, the entry for geom can be modified as other slots by calling element_geom() instead of element_text(), element_rect(), etc.

The defaults from ‘ggplot2’ version 4.0.1 are:

## <ggplot2::element_geom>
##  @ ink        : chr "black"
##  @ paper      : chr "white"
##  @ accent     : chr "#3366FF"
##  @ linewidth  : num 0.5
##  @ borderwidth: num 0.5
##  @ linetype   : int 1
##  @ bordertype : int 1
##  @ family     : chr ""
##  @ fontsize   : num 3.87
##  @ pointsize  : num 1.5
##  @ pointshape : num 19
##  @ colour     : NULL
##  @ fill       : NULL

We can see that the defaults for colour and fill are set to NULL. Unless set explicitly, colour takes its default value value from ink. While we have only one aesthetic called linetype used for both lines and column and label borders, for the plot as a whole, they can be set differently.

To test if the geometries from ‘ggpp’ obey defaults set through these theme settings, we modify them in theme_minimal() and set this as the default theme. Using very unsightly but contrasting colour we can check more easily if the geometries obey the theme settings.

set_theme(theme_minimal() +
            theme(
              geom = element_geom(
                ink = "darkgreen",
                paper = "wheat",
                accent = "darkred",
                family = "serif",
                linewidth = 0.3,
                linetype = "dotted",
                borderwidth = 1,
                bordertype = "dotted",
                pointsize = 1,
                pointshape = "circle open"
              )
            )
)

We can use the same code as above to retrieve the currently active theme. As expected, it reflects the changes we introduced above.

## <ggplot2::element_geom>
##  @ ink        : chr "darkgreen"
##  @ paper      : chr "wheat"
##  @ accent     : chr "darkred"
##  @ linewidth  : num 0.3
##  @ borderwidth: num 1
##  @ linetype   : chr "dotted"
##  @ bordertype : chr "dotted"
##  @ family     : chr "serif"
##  @ fontsize   : num 3.87
##  @ pointsize  : num 1
##  @ pointshape : chr "circle open"
##  @ colour     : NULL
##  @ fill       : NULL
p0

It is also possible to modify them for a specific geom, here, geom_point().

update_theme(geom.point = element_geom(colour = "blue"))
p0

We will use p as a base plot for testing the different geometries. Because of this, we add mappings to the aesthetics required by all the different geometries that we will test.

# systematically extract one out of each four rows
my.cars <- mtcars[c(TRUE, FALSE, FALSE, FALSE), ]
my.cars$name <- rownames(my.cars)

p1 <-
  ggplot(my.cars, 
         aes(x = wt, y = mpg, label = name, 
             xintercept = wt, yintercept = mpg,
             npcx = (wt - min(wt)) / diff(range(wt)), 
             npcy = (mpg - (min(mpg))) / diff(range(mpg)))) +
  geom_point()

p <- p1 + expand_limits(x = 6.5)
p

Variations on geom_text()

The expectation is that the text in all these plots look consistent with either green or blue text depending on the specific settings. The positions relative to the points is expected to differ when using roughly calculated NPC.

p + geom_text(hjust = 0, nudge_x = 0.1)

update_theme(geom.text = element_geom(colour = "blue"))
p + geom_text(hjust = 0, nudge_x = 0.1)

p + geom_text_s(hjust = 0, nudge_x = 0.1)

update_theme(geom.text.s = element_geom(colour = "blue"))
p + geom_text_s(hjust = 0, nudge_x = 0.1)

Variations on geom_label()

The expectation is that the labels in all these plots look consistent with either green or blue text on a yellow or transparent background depending on the specific settings. The positions relative to the points is expected to differ when using roughly calculated NPC.

p + geom_label(hjust = 0, nudge_x = 0.1)

update_theme(geom.label = element_geom(colour = "blue", fill = NA))
p + geom_label(hjust = 0, nudge_x = 0.1)

p + geom_label_s(hjust = 0, nudge_x = 0.1)

update_theme(geom.label.s = element_geom(colour = "blue", fill = NA))
p + geom_label_s(hjust = 0, nudge_x = 0.1)

Marks on plot margins

p1 + geom_rug()

p1 + 
  geom_x_margin_point(inherit.aes = TRUE) + 
  geom_y_margin_point(inherit.aes = TRUE)

update_theme(geom.x.margin.point = element_geom(colour = "red",
                                                pointshape = 19),
             geom.y.margin.point = element_geom(colour = "orange",
                                                pointshape = "triangle"))
p1 + 
  geom_x_margin_point(inherit.aes = TRUE) + 
  geom_y_margin_point(inherit.aes = TRUE)

p1 + 
  geom_x_margin_arrow(inherit.aes = TRUE) + 
  geom_y_margin_arrow(inherit.aes = TRUE)

update_theme(geom.x.margin.arrow = element_geom(colour = "green",
                                                pointshape = 19),
             geom.y.margin.arrow = element_geom(colour = "orange",
                                                pointshape = "triangle"))
p1 + 
  geom_x_margin_arrow(inherit.aes = TRUE) + 
  geom_y_margin_arrow(inherit.aes = TRUE)

Plot insets

p1 + annotate(geom = "plot", label = p, x = 6, y = 22)

Theme defaults for the inset plots are coded into p and not modified when added as an inset. The main and inset plots should both have blue points.

update_theme(geom.plot = element_geom(colour = "red"))

p1 + annotate(geom = "plot", label = p, x = 6, y = 22)

p1 + annotate(geom = "table", 
             label = data.frame(a = 1:2, b = 3:4), 
             x = 6, y = 22)

Currently, fontsize and family set in the plot theme (theme) override those in the table theme (ttheme) as defaults. The default colour and fill for the table are always from the table theme. All these defaults, as expected, can be overridden by mapping a variable or a constant to the respective aesthetics.

update_theme(geom.table = element_geom(fontsize = 15, family = "sans"))
p1 + annotate(geom = "table", 
             label = data.frame(a = 1:2, b = 3:4), 
             x = 6, y = 22)

Targetting of mapped aesthetics

Some geometries create simple graphical elements on the layer, and, thus, there is a single possible approach to apply aesthetics to them. For example, the colour of points or lines. Other geometries create graphical elements composed of multiple parts, such labels with text on a background enclosed by a border line. In these more complex graphical elements there are multiple ways in which an aesthetic like colour or transparency can be applied.

In this case two contrasting approaches are possible: 1) defining multiple aesthetics so that the same conceptual aesthetic (e.g., colour) can be independently mapped to different variables for different components of the graphical object, or 2) adding one parameter or more parameters to the geometry constructor that indicate to which graphical element(s) the aesthetic should be applied.

I consider that the targeting of the aesthetic mapping to different elements of a graphical object is related to graphical design and not part of the data representation. Thus ‘ggpp’ follows the second approach. The idea is that a single colour mapping assigns a meaning to the different colours in a single colour scale, and that this meaning should be unique.

The user interface used in ‘ggpp’ differs from that used in ‘ggplot2’ (>= 4.0.0) for geom_label(). It was introduced in ‘ggpp’ several years earlier and in the more complex geometries from ‘ggpp’ it remains preferable. Conceptually, the approaches are equivalent.

So, how does this work?

geom_label() compared to geom_label_s()

For the examples below geom_label() from ‘ggplot2’ and geom_label_s() from ‘ggpp’ are compared, other geoms from ‘ggpp’ follow a similar approach to that used in geom_label_s(). geom_label_s() is similar to geom_label() but when the position is modified by a position functions from ‘ggpp’ it draws a segment or arrow connecting the displaced position to the original one, i.e., the position corresponding to the variable mapped to x and y aesthetics.

The colour targeting approach used in geom_label_s() is limited to two colours, a default one taken from the theme geom element ink colour or overridden by an argument passed to default.colour, and the colour mapped to the colour aesthetic (mapped either to a variable or to a constant).

opts_chunk$set(fig.align = 'center', 
               fig.show = 'hold', fig.width = 7, fig.height = 7)

We use a simple theme, and as we use nudging, expand the y limit to make space for the labels.

set_theme(theme_minimal())
p <- p1 + expand_limits(x = 6.5)
(p + geom_label(colour = "red", 
                nudge_x = 0.1, hjust = 0) + labs(title = "geom_label()")) / 
  (p + geom_label_s(colour = "red",
                    nudge_x = 0.1, hjust = 0) + labs(title = "geom_label_s()"))

(p + geom_label(colour = "red", 
                border.colour = "black", 
                nudge_x = 0.1, hjust = 0) + labs(title = "geom_label()")) / 
  (p + geom_label_s(colour = "red", 
                    colour.target = "text", 
                nudge_x = 0.1, hjust = 0) + labs(title = "geom_label_s()"))

(p + geom_label(colour = "red", 
                border.colour = "darkgreen", 
                nudge_x = 0.1, hjust = 0)
 + labs(title = "geom_label()")) / 
  (p + geom_label_s(colour = "red", 
                    default.colour = "darkgreen", 
                    colour.target = "text", 
                    nudge_x = 0.1, hjust = 0))

update_theme(geom = element_geom(colour = "red"))

(p + geom_label(nudge_x = 0.1, hjust = 0) + labs(title = "geom_label()")) / 
  (p + geom_label_s(nudge_x = 0.1, hjust = 0) + labs(title = "geom_label_s()"))

update_theme(geom = element_geom(colour = "black", borderwidth = 1.5))

(p + geom_label(nudge_x = 0.1, hjust = 0) + labs(title = "geom_label()")) /
  (p + geom_label_s(nudge_x = 0.1, hjust = 0) + labs(title = "geom_label_s()"))

update_theme(geom = element_geom(colour = "black"))
(p + geom_label(linewidth = 0, 
                nudge_x = 0.1, hjust = 0) + labs(title = "geom_label()")) /
  (p + geom_label_s(linewidth = 0, 
                    nudge_x = 0.1, hjust = 0) + labs(title = "geom_label_s()"))

opts_chunk$set(fig.align = 'center', 
               fig.show = 'hold', fig.width = 7, fig.height = 4)
set_theme(theme_minimal())
update_theme(geom = element_geom(ink = "blue"))
p + geom_label_s(colour = "red", colour.target = "text", 
                 nudge_x = 0.1, hjust = 0)

set_theme(theme_minimal() +
  theme(geom = element_geom(ink = "blue")))

p + 
  geom_label_s(colour = "red", 
               colour.target = "text", 
               nudge_x = 0.1, hjust = 0)

set_theme(theme_minimal())

p + 
  geom_label_s(colour = "red", 
               default.colour = "blue",
               colour.target = "text", 
               nudge_x = 0.1, hjust = 0)

If the connecting segment has an arrow head, it is also targetted as a component of the "segment".

set_theme(theme_minimal())

p + 
  geom_label_s(colour = "red", 
               colour.target = "segment",
               arrow = grid::arrow(length = unit(2, "mm")),
               point.padding = 1,
               nudge_x = 0.25, hjust = 0)

set_theme(theme_minimal())

p + 
  geom_label_s(colour = "red", 
               default.colour = "blue",
               colour.target = c("box.line", "segment"), 
               nudge_x = 0.1, hjust = 0)

geom_table

p1 + annotate(geom = "table",
              colour = "red",
              label = data.frame(a = 1:2, b = 3:4), 
              x = 6, y = 22)

p1 + annotate(geom = "table",
              colour = "red",
              colour.target = "table.rules",
              label = data.frame(a = 1:2, b = 3:4), 
              x = 6, y = 22)

p1 + annotate(geom = "table",
              colour = "red",
              colour.target = "all",
              label = data.frame(a = 1:2, b = 3:4), 
              x = 6, y = 22)

p1 + annotate(geom = "table",
              colour = "red",
              colour.target = "none",
              label = data.frame(a = 1:2, b = 3:4), 
              x = 6, y = 22)