05. Themes

Chapter 2

The final piece in the ggplot2 equation is theming. The powerful theme system does not affect any perceptual properties of the plot, but it help us make production-quality graphs through a fine control over things like fonts, ticks, panel strips, backgrounds…

Iñaki Ucar (Department of Statistics)
2022-10-05

Source: _tutorials/05/05.Rmd

Introduction

The central part of the theme system is the theme() function, which has 40+ configurable elements divided into 5 main categories: plot, axis, legend, panel and faceting elements. In those categories, individual elements are arranged in a hierarchical structure with self-explanatory names. For instance, plot.title controls the appearance of the plot title; axis.ticks.x, the ticks on the x axis, etc.

Each of these elements can be set with one of three basic types: element_text(), for text elements; element_line(), for lines, and element_rect(), for borders and backgrounds. Additionally, elements can be unset, or removed, with element_blank(), which draws nothing.

Themes can be built with a + theme(...) invocation by specifying each one of these elements to the desired style via the corresponding element function. However, there are many predefined themes available, and oftentimes it is much simpler to specify the theme that best suits our needs and then change small details via the theme() function. For example, here we change the default theme to theme_bw() and then we tweak the legend so that is displayed inside the plot, on the top-right corner.

library(ggplot2)

p <- ggplot(mpg) +
  aes(displ, hwy) +
  geom_point(aes(color=factor(cyl)))

p
p +
  theme_bw() +
  theme(
    legend.position = c(0.99, 0.99),
    legend.justification = c(1, 1)
  )

Predefined themes

ggplot2 comes with a number of built-in themes, and there are many ggplot2 extensions that are specifically dedicated to provide new themes. These themes can be set on a per-plot basis, as we did in the previous example, or you can set a global theme that replaces the default one.

theme_set(theme_classic())

p # now this uses the classic theme by default

This global theme can be tweaked too via the theme_update() function, which accepts the same arguments as theme().

theme_update(
  legend.position = c(0.99, 0.99),
  legend.justification = c(1, 1)
)

p # now the legend is inside the plot by default

Built-in themes

ggplot’s characteristic visual signature (grey background with white grid lines) is defined in theme_grey(), which is applied by default to all plots unless replaced as done previously.

theme_set(theme_grey())

p # back to the default

There are seven other built-in themes:

Internally, themes are just a call to theme() that sets the complete set of parameters available to the appropriate values, like theme_grey:

# execute this line as is, without parentheses, to inspect the internals
theme_grey

Others are based on some theme, and then perform some tweaks:

theme_bw # based on theme_grey

Checkpoint 1

  1. See the manual page for the built-in set of themes, and play with the examples to familiarize yourself with the different styles.

  2. In which theme is based each one of the built-in themes?

More themes

There are numerous packages that provide themes as part of their functionality, but also entire packages specifically dedicated to this. This is a non-comprehensive list that highlights some noteworthy themes:

Modifying theme components

Once we have set a predefined theme that is close enough to what we try to achieve, it is time for tweaking. To modify individual theme components, you use code like plot + theme(element.name = element_type(...)). Apart from element_blank(), which unsets an element,

p <- ggplot(mpg) +
  aes(displ, hwy) +
  labs(title="This is a title")

p
p + theme(panel.grid.major = element_blank())

there are three basic types of elements (text, line, rect), and the setter function must match that type.

Text elements

element_text() draws labels and headings. You can control the font family, face, colour, size (in points), hjust, vjust, angle (in degrees) and lineheight (as ratio of fontcase). More details on the parameters can be found in vignette("ggplot2-specs").

p + theme(plot.title = element_text(size = 30))
p + theme(plot.title = element_text(face = "bold", color = "red"))
p + theme(plot.title = element_text(hjust = 1))

Line elements

element_line() draws lines parametrized by color, size and linetype.

p + theme(panel.grid.major = element_line(color = "black"))
p + theme(panel.grid.major = element_line(color = "black", size = 2))
p + theme(panel.grid.major = element_line(color = "black", linetype = "dotted"))

Rectangle elements

element_rect() draws rectangles, mostly used for backgrounds, parametrized by fill color and border color, size and linetype.

p + theme(plot.background = element_rect(fill = "grey80", color = NA))
p + theme(plot.background = element_rect(color = "red", size = 2))
p + theme(panel.background = element_rect(fill = "linen"))

Theme elements

There are four general elements that affect all elements of their kind:

Element Setter Description
line element_line() Affects all line elements.
rect element_rect() Affects all rectangular elements.
text element_text() Affects all text elements.
title element_text() Affects all title elements.
plot.margin unit() Margin around the plot.

All individual elements that can be set with element_line() inherit from line, and so on and so forth. Then, individual elements can be fine-tuned via their specific configuration option, that can be subdivided into plot, axis, legend, panel and faceting elements. These individual elements have a hierarchical structure too, meaning that e.g. axis.title.*.*, if not specified, inherits from axis.title.

Overview of theme elements. Figure from ggplot2 Theme Elements Demonstration by Henry Wang.

Plot elements

Some elements affect the plot as a whole:

Element Setter Description
plot.background element_rect() Background for the entire plot.
plot.title
plot.subtitle
plot.caption
plot.tag
element_rect() Plot title, subtitle, caption and tag. Elements subtitle, caption and tag inherit from title by default, but can be set separately.
plot.title.position
plot.caption.position
character Alignment of title (affects subtitle too) and caption (“panel” and “plot”, which align with and without margins respectively).
plot.tag.position character or numeric Aligment of tag (“topleft”, “top”, “topright”, “left”, “right”, “bottomleft”, “bottom”, “bottomright”, or a coordinate).
plot.margin unit() Margin around the plot.

By default, ggplot2 uses a white background to ensure that the plot can be read no matter where it is placed. Sometimes you may want to blend it with the background color of a document (e.g., a presentation), in which case it is useful to set a transparent background with fill=NA. In a similar way, if the destination document already defines margins around the plot, you may want to eliminate those.

p + theme(
  plot.background = element_rect(fill="blue", color="red", size=3),
  plot.title = element_text(size = 30),
  plot.margin = margin(50, 10, 10, 10)
)

Axis elements

The axis elements control the appearance of the axes:

Element Setter Description
axis.title
axis.title.x
axis.title.x.top
axis.title.x.bottom
axis.title.y
axis.title.y.left
axis.title.y.right
element_text() Axis titles.
axis.text
axis.text.x
axis.text.x.top
axis.text.x.bottom
axis.text.y
axis.text.y.left
axis.text.y.right
element_text() Axis tick labels.
axis.ticks
axis.ticks.x
axis.ticks.x.top
axis.ticks.x.bottom
axis.ticks.y
axis.ticks.y.left
axis.ticks.y.right
element_line() Axis tick marks.
axis.ticks.length
axis.ticks.length.x
axis.ticks.length.x.top
axis.ticks.length.x.bottom
axis.ticks.length.y
axis.ticks.length.y.left
axis.ticks.length.y.right
unit() Length of tick marks.
axis.line
axis.line.x
axis.line.x.top
axis.line.x.bottom
axis.line.y
axis.line.y.left
axis.line.y.right
element_line() Line parallel to axis (hidden in default theme).

Note that anything set for axis.line is inherited by axis.line.x and axis.line.y.

p + theme(
  axis.line = element_line(size=5),
  axis.line.y = element_line(color="red"),
)

A common task is to rotate labels to avoid overlap of very long labels.

p +
  scale_x_continuous(labels=scales::label_number(prefix="a very long label ")) +
  theme(axis.text.x = element_text(angle=45, vjust=1, hjust=1))

Legend elements

The legend elements control the appearance of all legends. You can also modify the appearance of individual legends by modifying the same elements in guide_legend() or guide_colourbar().

Element Setter Description
legend.background element_rect() Background of legend.
legend.margin margin() Margin around each legend.
legend.spacing
legend.spacing.x
legend.spacing.y
unit() Spacing between legends.
legend.key element_rect() Background of legend keys.
legend.key.size
legend.key.height
legend.key.width
unit() Legend key size. Height and width inherit from size unless specified separately.
legend.text element_text() Legend item labels.
legend.text.align 0–1 Legend label alignment (0 = right, 1 = left).
legend.title element_text() Title of legend.
legend.title.align 0–1 Legend title alignment (0 = right, 1 = left).
legend.position character or numeric Position of legends (“none”, “left”, “right”, “bottom”, “top” for outside placement; two-element numeric vector for inside placement: 0 = bottom/left, 1 = top/right).
legend.direction character Layout of items in legends (“horizontal” or “vertical”).
legend.justification character or numeric Anchor point for legend positioning (“center” or two-element numeric vector: 0 = bottom/left, 1 = top/right).
legend.box character Arrangement of multiple legends (“horizontal” or “vertical”).
legend.box.just character Justification of each legend (“top”, “bottom”, “left”, “right”).
legend.box.margin margin() Margin around full legend area.
legend.box.background element_rect() Background of legend area.
legend.box.spacing unit() Spacing between plotting area and legend box.

A common task is to modify legend placement.

p1 <- p + geom_point(aes(color=factor(cyl)))

p1
p1 + theme(
  legend.position = "top",
  legend.justification = "right"
)
p1 + theme(
  legend.position = c(1, 1),
  legend.justification = c(1, 1)
)
p1 + theme(
  legend.position = c(1, 1),
  legend.justification = c(1, 1),
  legend.background = element_rect(fill=NA)
)

Panel elements

Panel elements control the appearance of the plotting panels:

Element Setter Description
aspect.ratio numeric Aspect ratio of the panel.
panel.background element_rect() Background of plotting area (under data).
panel.border element_rect() Border around plotting area (over data).
panel.grid
panel.grid.major
panel.grid.major.x
panel.grid.major.y
panel.grid.minor
panel.grid.minor.x
panel.grid.minor.y
element_line() Grid lines. Major and minor grid lines, as well as individual ones for axis x and y, can be specified separately.
panel.ontop logical Option to place the panel over the data layers (usually used with a transparent or blank panel.background).

Note that aspect.ratio fixes the aspect ratio of just the panel, but not the plot.

p + theme(aspect.ratio = 2/1)

Faceting elements

The following theme elements are associated with faceted ggplots:

Element Setter Description
panel.spacing
panel.spacing.x
panel.spacing.y
unit() Spacing between facets.
strip.background
strip.background.x
strip.background.y
element_rect() Background of facet labels.
strip.placement character Placement of facet labels with respect to axes (“inside” or “outside”).
strip.text
strip.text.x
strip.text.y
element_text() Facet labels.
strip.switch.pad.grid
strip.switch.pad.wrap
unit() Space between facet labels and axes when strips are switched.

Note that strip.text.x affects both facet_wrap() and facet_grid(), but strip.text.y only affects facet_grid().

Checkpoint 2

  1. theme_dark() makes the inside of the plot dark, but not the outside. Change the plot background to black, and then update the text settings so you can still read the labels.

Advanced font usage

Setting fonts is a tricky issue. The reason is that we have to rely on the few fonts available to R by default. If a font is not available, a default one will be used instead. For example, compare this to Futura:

p + 
  theme(text = element_text(family="Futura")) +
  labs(subtitle="This is not Futura")

Even if this font is installed in your system (which most probably will not, because it is a non-free font), it is not available to R by default. There are packages, such as extrafont, that makes it easier to use your system fonts, or arbitrary font files, in R. But there is a better way.

The easiest way of ensuring reproducible results across platforms is to select a free font available in Google Fonts, and then apply it to your theme. For this, we need to install the showtext package, and a couple of additional commands. In this example, we apply the nice handwritten font Caveat:

sysfonts::font_add_google("Caveat", family="caveat")
showtext::showtext_auto()

p + theme(text = element_text(family="caveat"))

Note also that font selection is so important for the look and feel of a graphic that many predefined themes provide specific arguments to change the font family and size.

p + theme_bw(base_family="caveat")

Setting the DPI

The DPI, or Dots Per Inch, is a measure of image resolution. RStudio’s Plot pane uses a resolution of only 96 DPI for speed reasons, but ggsave() uses a default of 300 DPI, which is the standard quality for printing. The issue with showtext is that DPI is not automatically detected. It works well for 96 DPI (so plots in the panel look fine), but it needs to be set manually for higher DPIs. E.g., the font here will look too small:

ggsave("test_96.png", p + theme_bw(base_family="caveat"))

Therefore, you must set the proper DPI beforehand:

showtext::showtext_opts(dpi=300)
ggsave("test_300.png", p + theme_bw(base_family="caveat"))
showtext::showtext_opts(dpi=96) # reset default

For R Markdown documents, there is a special chunk option, fig.showtext=TRUE, that performs this for you:

```{r, fig.showtext=TRUE}
p + theme_bw(base_family="caveat")
```

See this issue for further discussion.

Checkpoint 3

  1. Find two fonts of your liking in Google Fonts. Apply one of them to all text elements except for the subtitle, that should use the second font.

Figuring out a font

Sometimes we find a nice font that we would like to use in a graph (or, in general, in any document). How to figure out which font it is?, or, at least, a similar one… There are two levels of difficulty here depending on the source medium:

  1. A webpage: If the intended font is included in some webpage, you are in luck, because these are the easiest to figure out. One way is to manually search in the source code of the webpage: with a right-click on the text, you need to choose the menu item that opens the page’s code (Inspect in Chrome), then you can look for a CSS property called font-family in the Styles tab. An even easier way is to just copy the text and paste it in a document (e.g. a Google Docs). The font family will be automatically recognized.

  2. An image: (Or anything that can be saved as an image). There are multiple online services dedicated to identifying fonts in images. A popular one is What The Font.

Reuse

Text and figures are licensed under Creative Commons Attribution CC BY 4.0. The figures that have been reused from other sources don't fall under this license and can be recognized by a note in their caption: "Figure from ...".

Citation

For attribution, please cite this work as

Ucar (2022, Oct. 5). Data visualization | MSc CSS: 05. Themes. Retrieved from https://csslab.uc3m.es/dataviz/tutorials/05/

BibTeX citation

@misc{ucar202205.,
  author = {Ucar, Iñaki},
  title = {Data visualization | MSc CSS: 05. Themes},
  url = {https://csslab.uc3m.es/dataviz/tutorials/05/},
  year = {2022}
}