ggplot2 themes

The theming system in ggplot2 enables a user to control non-data elements of a ggplot object. It is composed of the following:

This chapter describes the new theming system introduced in version 0.9.2. We begin with a motivating example, and then turn to describe the new theming system in more detail.

Motivating example

Consider the following simple scatterplot using the mpg data:

ggplot(mpg, aes(x = cty, y = hwy, color = factor(cyl))) +
  geom_jitter() +
  labs(
    x = "City mileage/gallon",
    y = "Highway mileage/gallon",
    color = "Cylinders"
  )

plot of chunk theme-ex1

Suppose we want to make the following modifications to this plot:

The first of these depends on the data itself, so we can use one of the scale_colour_*() functions for that purpose. The others influence the rendering of the graphic but are independent of the data being plotted. These are called theme elements, i.e., aspects of a ggplot object that are capable of modifying its appearance but are neither directly related to data nor aesthetics associated with data.

A list of theme elements and their default values can be gotten by calling the default theme function:

theme_grey()

The output of this call lists the properties of a ggplot object that can be accessed by users along with their default values, expressed as a list of lists. The theme() function allows you to locally modify properties of theme elements in a ggplot object.

One way of implementing the desired changes listed above is as follows, using the new theming system:

# Base plot
p <- ggplot(mpg, aes(x = cty, y = hwy, colour = factor(cyl))) +
  geom_jitter() +
  labs(
    x = "City mileage/gallon",
    y = "Highway mileage/gallon",
    colour = "Cylinders"
  ) +
  scale_colour_brewer(type = "seq", palette = "Oranges")

# Use theme() to modify theme elements
p + labs(title = "Highway vs. city mileage per gallon") +
  theme(
    axis.text = element_text(size = 14),
    legend.key = element_rect(fill = "navy"),
    legend.background = element_rect(fill = "white"),
    legend.position = c(0.14, 0.80),
    panel.grid.major = element_line(colour = "grey40"),
    panel.grid.minor = element_blank(),
    panel.background = element_rect(fill = "navy")
  )

plot of chunk theme-ex1a

The element_xx() functions modify theme elements with attributes (e.g., color, text size). Some theme elements are defined in terms of a unit of measurement, while others, such as legend.position, control the positioning of a theme element. These will be illustrated in greater detail in the examples to follow.

In the above call, element_text() modifies the size property of the axis.text theme element, element_rect() changes the fill color of the legend.key, legend.background and panel.background theme elements, element_line() alters the color of the major grid lines in both directions and element_blank() removes the minor grid lines from the display in both directions. Finally, the form of the legend.position() call moves the center of the legend grob to a position in coordinates relative to the size of the overall graphics device (not the graphics panel that delimits the graph itself). In this case, the default theme function is theme_grey(), so theme() locally changes the above settings in theme_grey() to the values specified in the call.

The equivalent code to produce the above graph in the old theming system is

# version 0.9.1 or lower
p + opts(
  title = "Highway vs. city mileage per gallon",
  axis.text.x = theme_text(size = 14),
  axis.text.y = theme_text(size = 14),
  panel.grid.minor = theme_blank(),
  legend.position = c(0.14, 0.78),
  legend.key = theme_rect(fill = "navy"),
  legend.background = theme_rect(fill = "white"),
  panel.grid.major = theme_line(colour = "grey40"),
  panel.grid.minor = theme_blank(),
  panel.background = theme_rect(fill = "navy")
)

The default theme for ggplot2 graphics is theme_grey(), but you can override it with another (complete) theme function. A second theme function built into the system is theme_bw(), which can be plugged in as a replacement for theme_grey() as follows:

p + labs(title = "Highway vs. city mileage per gallon") +
  theme_bw() +
  theme(axis.text = element_text(size = 14),
    legend.key = element_rect(fill = "navy"),
    legend.background = element_rect(fill = "white"),
    legend.position = c(0.14, 0.80),
    panel.grid.major = element_line(colour = "grey40"),
    panel.grid.minor = element_blank()
  )

plot of chunk theme-bw-ex1

Adding theme_bw() to the ggplot() call means that theme_bw becomes the default theme for the created ggplot object. The call theme_bw() produces a theme object which can be modified with theme().

Why a new theming system?

As part of the evolution of ggplot2, the theming system has been renovated to improve code efficiency, support inheritance of theme element properties and to provide some new features. The most visible changes are summarized in the following table:

Legacy system New system
opts() theme()
theme_xx() element_xx()

where xx is a placeholder for one of text, rect or line. The function opts() in the old system has been renamed theme() to emphasize its purpose; i.e., to locally modify theme elements in a ggplot object. Similarly, the old theme_xx() functions have been renamed element_xx() to clarify that their purpose is to modify characteristics of theme elements. One motivation for this change is to avoid confusion between functions that modify theme elements and functions that define themes, such as theme_grey().

Some notable changes in the new theming system:

  1. Plot titles are handled differently in the new theming system. Define them within labs() using the title = argument, or alternatively, with the ggtitle() function. However, the properties of plot titles are still controlled by the plot.title theme element.

  2. The new system recognizes the distinction between complete theme objects and incomplete theme objects. Typically, a call to theme() results in an incomplete theme object (since it locally modifies the current theme) whereas a call to a theme function such as theme_grey() or theme_bw() returns a complete theme object. In particular, complete themes are added in a ggplot() or qplot() call. This subject is taken up in greater depth in the last section of the chapter, as it is relevant to those who want to create their own ggplot2 theme functions.

  3. The object returned by a call to a complete theme function is now a nested list of theme elements and their properties, which enables the new theming system to support inheritance of properties. For example, to change the size of both axis labels in the motivating example, it is sufficient to type

    axis.text = element_text(size = 14)
    

    because it will be passed down to the theme elements axis.text.x and axis.text.y by inheritance.

  4. A happy consequence of inheritance is that it enables relative sizing in the new theming system. This allows size parameters to be set in top level theme elements and then modified on a relative rather than an absolute basis, potentially saving a significant amount of code.

  5. The old theme element functions theme_line() and theme_segment() have been merged into the new function element_line() as an efficiency measure.

We now turn attention to theme elements and the functions that modify their properties. We start with theme element functions, since they are relatively few in number and rather easy to understand. We then turn to consider groups of theme elements along with examples to illustrate how they can be applied.

Theme element functions

Most theme elements have several properties that can be modified through a corresponding element function. The available functions are listed in the table below. For reference, their names under the old and new theming systems are placed side by side.

Legacy system New system
theme_text() element_text()
theme_line(), theme_segment() element_line()
theme_rect() element_rect()
theme_blank() element_blank()

The theme_segment() function is dropped in the new theming system, its functionality being subsumed into element_line(). In the legacy theming system, theme_line() and theme_segment() had the same set of arguments, so merging them into one function is simply more efficient.

We now consider the individual theme element functions in more detail. The arguments of a given element function correspond to the attributes of any theme element to which it applies.

element_text()

Purpose: To control the drawing of labels and headings.

The table below lists the arguments of element_text() and their corresponding default values.

Argument Description Default value
family font family “”
face font face “plain”
colour font color “black”
size font size (pts) 10
hjust horizontal justification 0.5
vjust vertical justification 0.5
angle text angle 0
lineheight line height 1.1

element_line()

Purpose: To draw lines and segments such as graphics region boundaries, axis tick marks and grid lines.

Argument Description Default value
colour line color “black”
size line thickness 0.5
linetype type of line 1

element_rect()

Purpose: To draw rectangles. It is mostly used for background elements and legend keys.

Argument Description Default value
fill fill color NA (none)
colour border color “black”
size thickness of border line 0.5
linetype type of border line 1 (solid)

element_blank()

Purpose: To draw nothing.

Arguments: none.

The element_blank() function can be applied to any theme element controlled by a theme element function.

With these functions in place, we now turn to the theme elements themselves.

Theme elements

The default theme function theme_grey() contains 38 individual theme elements. The first three of these are line, rect and text, whose default properties are defined through their respective theme element functions element_line(), element_rect() and element_text(). The remaining theme elements can be grouped in various ways, but we have chosen the following: elements associated with axis, legends, panel strips, graphics panels and the entire plot region.

Axis attributes

The table below lists each theme element associated with the rendering of axes, the theme function or unit function that controls it, and a short description of the element.

Element Theme function Unit Description
axis.line line line parallel to axis
axis.text text tick labels
axis.text.x text x-axis tick labels
axis.text.y text y-axis tick labels
axis.title text axis titles
axis.title.x text x-axis title
axis.title.y text y-axis title
axis.ticks line axis tick marks
axis.ticks.length cm length of tick marks
axis.ticks.margin cm width of axis tick margin

Most of the theme elements listed above use theme element functions to modify their properties. However, the length and margin of axis ticks are set to individual values relative to a defined unit of measurement; e.g., unit(0.5, "cm"). The unit() function comes from the grid package, so grid should be loaded along with ggplot2 if you intend to (re)set theme elements defined in terms of measurement units.

Our first example shows how to modify axis lines. The properties of the axis.line theme element are exactly the same as the arguments of the function element_line(); therefore, the function is used to modify properties of axis.line. (This is true for any theme element controlled by an element function.)

The first call below changes each of the available properties in the axis.line theme element so that the changes propagate to both axis.line.x and axis.line.y, taking advantage of inheritance. The plot on the left therefore has axis lines with long dashes of thickness 2 in red in both the x and y directions.

The second call uses inheritance somewhat differently. The attributes changed in axis.line propagate to both axis.line.x and axis.line.y. However, in the second line of code corresponding to axis.line.y, the specified color and linetype override the values inherited from axis.line. As a result, in the right hand plot, axis.line.x will be a solid blue line with thickness 2 while axis.line.y will be a dashed orange line with the same thickness.

p + theme(
  axis.line = element_line(colour = "red", size = 2, linetype = "longdash")
)

p + theme(
  axis.line = element_line(colour = "blue", size = 2),
  axis.line.y = element_line(colour = "orange", linetype = "dashed")
)

plot of chunk axis-line-ex plot of chunk axis-line-ex

The following code chunk illustrates how to modify properties of the axis tick labels, associated with the axis.text theme element, through the element_text() function. Like the previous example, the first call applies to both axis tick labels (left plot), while the second call provides additional modifications to the y-axis label (right), including a relative size reduction.

p + theme(
  axis.text = element_text(color = "blue", size = 15, face = "italic")
)

p + theme(
  axis.text = element_text(colour = "blue", size = 15, face = "italic"),
  axis.text.y = element_text(angle = 90, size = rel(0.7), hjust = 0)
)

plot of chunk axis-text-ex plot of chunk axis-text-ex

The code chunk below modifies properties of axis ticks, including some modifications involving measurement units.

p + theme(
  axis.ticks = element_line(colour = "purple", size = 1),
  axis.ticks.length = unit(0.5, "cm")
)

# double the relative size of axis ticks in the x direction
p + theme(
  axis.ticks = element_line(colour = "violet"),
  axis.ticks.x = element_line(size = rel(2)),
  axis.ticks.margin = unit(0.4, "in")
)

plot of chunk axis-ticks-ex plot of chunk axis-ticks-ex

The last code chunk in this subsection shows how to modify attributes of the axis titles.

p + theme(
  axis.title = element_text(size = 20, color = "maroon"),
  axis.title.y = element_text(vjust = 1, angle = 30, face = "bold")
)

p + theme(
  axis.title = element_text(size = 20, color = "maroon"),
  axis.title.y = element_text(vjust = 0, angle = 120, face = "bold",
    size = rel(0.5))
)

plot of chunk axis-title-ex plot of chunk axis-title-ex

Legend attributes

The following theme elements are associated with the appearance of legends:

Element Theme function Unit Default value Description
legend.background rect legend background
legend.key rect background of legend keys
legend.key.size lines
legend.key.height cm legend key height
legend.key.width cm legend key width
legend.margin lines legend margin
legend.text text legend labels
legend.text.align NULL legend label alignment
legend.title text legend name
legend.title.align NULL legend name alignment
legend.position “right” position of legend
legend.direction NULL direction of legend keys
legend.justification “center” justification of legend
legend.box NULL position of multiple legend boxes

As with axis-related theme elements, some are associated with theme element functions and some with measurement units. However, several theme elements associated with legends also modify position, and these are indicated by non-empty values in the column headed Default value.

if legend.position is set by a numeric vector, then the element legend.justification can be applied; it controls the position of the anchor point of the legend grob.

The following table summarizes the possible values for each legend theme element associated with positioning:

Theme element Potential values
legend.direction, legend.box “vertical”, “horizontal”
legend.position “top”, “bottom”, “left”, “right” “none”
legend.justification 1st value: “left”, “center”, “right”
2nd value: “top”, “center”, “bottom”

legend.position and (conditionally) legend.justification also accept two-element numeric vectors of the form c(x, y) where both x and y take values between 0 and 1. Theme elements legend.title.align and legend.text.align also assume values between 0 and 1, where 0 corresponds to left and 1 to right.

Theme elements associated with legends fall roughly into two groups: those that modify the position of the legend grob and those that modify properties of the legend grob. We begin with elements that modify position.

The first step is to create a simple data frame that generates a base plot with a single legend.

# toy data frame
DF1 <- data.frame(x = 1:10, y = 1:10, gp = factor(rep(1:2, each = 5)))
# base ggplot object
p0 <- ggplot(DF1, aes(x = x, y = y, colour = gp)) + geom_point()

The following code chunk illustrates different ways of positioning the legend in a ggplot. The coordinates of the last call represent relative (or proportional) location on the graphics device where the legend is to be placed.

p0 + theme(legend.position = "top") + ggtitle("Top")
p0 + theme(legend.position = "bottom") + ggtitle("Bottom")
p0 + theme(legend.position = "left") + ggtitle("Left")
p0 + theme(legend.position = "none") + ggtitle("None")
p0 + theme(legend.position = c(0.15, 0.8)) + ggtitle("Inside")
p0 + ggtitle("Default")

plot of chunk legend-position-ex plot of chunk legend-position-ex plot of chunk legend-position-ex plot of chunk legend-position-ex plot of chunk legend-position-ex plot of chunk legend-position-ex

When a legend is positioned in terms of relative (x, y) location, one can use the legend.justification theme element. It indicates which part of the legend grob is positioned at (x, y) (the default is c("center", "center")). If only one argument is given, such as in the first call below, it is implicit that y will be in the vertical center of the grob. If you want to justify the legend grob vertically, you have to use “center” as the first value in a two-element character vector, as in the third call below.

p0 +
  theme(legend.position = c(0.5, 0.5), legend.justification = "left") +
  labs(title = "Left justified")

p0 +
  theme(legend.position = c(0.5, 0.5), legend.justification = "right") +
  labs(title = "Right justified")

p0 +
  theme(
    legend.position = c(0.5, 0.5),
    legend.justification = c("center", "top")) +
  labs(title = "Center top justified")

p0 +
  theme(
    legend.position = c(0.5, 0.5),
    legend.justification = c("left", "bottom")
  ) +
  labs(title = "Bottom left justified")

plot of chunk legend-just-ex1 plot of chunk legend-just-ex1 plot of chunk legend-just-ex1 plot of chunk legend-just-ex1

One can also specify legend justification with respect to a numeric vector of length 2:

p0 + theme(legend.position = c(0.5, 0.5), legend.justification = c(0.2, 0.8))
p0 + theme(legend.position = c(0.5, 0.5), legend.justification = c(0.6, 0.3))

plot of chunk legend-just-ex3 plot of chunk legend-just-ex3

The direction of a legend can either be horizontal or vertical, and you can achieve different looks by combining legend.position with legend.direction, as the following examples show.

p0 + theme(legend.position = "top")
p0 + theme(legend.position = "top", legend.direction = "vertical")

plot of chunk legend-direction-ex plot of chunk legend-direction-ex

The legend.box theme element applies only when more than one legend grob is drawn in a ggplot. The code below sets up another toy example with two aesthetics and separate legends and then uses legend.box to arrange the legends either vertically or horizontally.

DF2 <- data.frame(
  x = seq_len(20),
  y = rnorm(20),
  gp1 = factor(rep(1:2, each = 10)),
  gp2 = factor(rep(c("A", "B"), each = 5))
)
# base plot
q0 <- ggplot(DF2, aes(x = x, y = y, colour = gp1, shape = gp2)) +
  geom_point(size = 3)

q0
q0 + theme(legend.position = "top")
q0 + theme(legend.position = "top", legend.box = "horizontal")
q0 + theme(legend.box = "horizontal")
q0 + theme(legend.direction = "horizontal", legend.box = "vertical")

plot of chunk legend-box-base plot of chunk legend-box-base plot of chunk legend-box-base plot of chunk legend-box-base plot of chunk legend-box-base

We now turn to consideration of legend theme elements that are associated with the legend grob itself. We begin with an example that shows how to modify the legend background:

p0 +
  theme(
    legend.background = element_rect(
      fill = "lemonchiffon",
      color = "black",
      size = 2,
      linetype = "longdash"
    )
  )

plot of chunk legend-background-ex

The code chunk below shows two ways of modifying the size of a legend key. The first call uses equal key width and key height (left)while the second call allows one to specify key width and key height independently (right).

p0 + theme(
  legend.key = element_rect(fill = "black", colour = "yellow"),
  legend.key.size = unit(0.5, "in")
)
p0 + theme(
  legend.key = element_rect(fill = "yellow", color = "orange"),
  legend.key.width = unit(0.5, "in"),
  legend.key.height = unit(0.2, "in")
)

plot of chunk legend-key-ex plot of chunk legend-key-ex

This code chunk illustrates how one can modify the legend key labels and then to modify the justification of the legend text.

p0 + theme(legend.text = element_text(
  face = "bold", size = 15, angle = 90, colour = "magenta"
))
last_plot() + theme(legend.text.align = 1)

plot of chunk legend-text-ex plot of chunk legend-text-ex

The next example modifies properties of the legend title. The second call shows that the legend.title.align element doesn't work in the release candidate: the title disappears altogether, just as the legend key labels disappear when legend.text.align is invoked.

p0 + theme(legend.title =
    element_text(face = "italic", size = 12, angle = 45, color = "grey70") )
last_plot() + theme(legend.title.align = 0)

plot of chunk legend-title-ex plot of chunk legend-title-ex

Panel attributes

Panel elements are associated with the graphics region in a ggplot. In faceted ggplots, these elements apply uniformly to each graphics panel.

Element Theme function Unit Description
panel.background rect background of graphics region
panel.border rect border of graphics region
panel.grid.major line major grid lines
panel.grid.minor line minor grid lines
panel.margin lines margin between facets

In the new theming system, both panel.grid.major and panel.grid.minor can be controlled in either the x or y direction. This will be illustrated in the examples below.

The first code chunk below compares the modifications to a graphics panel that take place when panel.background is called vis a vis panel.border. Both theme elements are modified by element_rect(), but the same calls to element_rect() produce very different outcomes.

p + theme(
  panel.background = element_rect(fill = "navy", color = "orange", size = 2)
)
p + theme(
  panel.border = element_rect(fill = "navy", colour = "orange", size = 2)
)

plot of chunk panel-bkgd-ex plot of chunk panel-bkgd-ex

This occurs because panel.border differs from panel.background: the former is implemented by drawing a rectangle on top of the graphics panel. The fix is to set the fill color to NA:

p + theme(
  panel.border = element_rect(fill = NA, colour = "orange", size = 2)
)

plot of chunk panel-border-fix

The next set of examples modifies the major and minor grid lines in a ggplot. The advantage gained by inheritance in the new theming system is that one can now modify grid lines in one direction.

The first call below modifies the color and thickness of the major grid lines in both directions, but then eliminates the major grid lines in the x direction only using element_blank(), which is used to suppress drawing of a particular theme element. The second call eliminates the minor grid lines in both directions, since both panel.grid.minor.x and panel.grid.minor.y inherit from panel.grid.minor. The last call modifies the linetype of the minor grid lines in both directions, and modifies the background fill color to make the change more apparent.

p + theme(
  panel.grid.major = element_line(color = "gray60", size = 0.8),
  panel.grid.major.x = element_blank(),
  legend.position = "none"
)

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

p + theme(
  panel.background = element_rect(fill = "lightblue"),
  panel.grid.minor = element_line(linetype = "dotted"),
  legend.position = "none"
)

plot of chunk panel-grid-ex plot of chunk panel-grid-ex plot of chunk panel-grid-ex

Theme element panel.margin is concerned with the amount of spacing between panels in a faceted ggplot. The first step is to generate a faceted plot using the mtcars data after which the spacing between facets is modified.

s <- ggplot(mtcars, aes(x = wt, y = mpg)) +
  geom_point() +
  facet_grid(vs ~ am, labeller = label_both)

s + theme(panel.margin = unit(0.5, "in"))

plot of chunk panel-margin-ex

Strip attributes

The following theme elements are associated with panel strips in faceted ggplots:

Theme element Theme function Description
strip.background rect background of panel strips
strip.text.x text horizontal strip text
strip.text.y text vertical strip text

Element strip.text.x can be used in either facet_wrap() or facet_grid(), but strip.text.y can only be used in connection with facet_grid().

The first example modifies the background features of panel strips.

s + theme(
  strip.background = element_rect(fill = "cornsilk", color = "maroon", size = 2)
)

plot of chunk strip-background-ex

Theme elements strip.text.x and strip.text.y both inherit from theme element strip.text, as shown in the example below. The modifications to strip.text propagate by default to both strip.text.x and strip.text.y, but the second line changes the font face in strip.text.y. A bit of panel.margin is added to space out the vertical axis labels a little.

s + theme(
  strip.text = element_text(face = "italic", size = 15, colour = "red"),
  strip.text.y = element_text(face = "bold"),
  panel.margin = unit(0.25, "cm")
)

plot of chunk strip-text-ex

Plot attributes

These elements pertain to the entire graphics region of a ggplot, but particularly outside a graphics panel (or the set of panels in a faceted plot).

Element Theme function Unit Description
plot.background rect background of entire plot
plot.title text plot title
plot.margin cm vector of margins around plot

Element plot.margin is a four-element vector associated with the margins to be placed outside the graphics region going clockwise from the top.

As noted earlier in this chapter, there are two new ways to produce a plot title: ggtitle() and the title = argument of labs(). The title element in the legacy theming system is deprecated. An example of each is shown in the figures below. These examples show how to modify the properties of a plot title; the second one shows how to add some space between the panel border and the plot title using the vjust attribute.

p + ggtitle("Plot title") + theme(
  plot.title = element_text(
    colour = "blue",
    size = 20,
    hjust = 0.2,
    vjust = 0.8,
    angle = 180
  )
)

p +
  labs(title = "Plot title") +
  theme(plot.title = element_text(size = 20, vjust = 2))

plot of chunk plot-title-ex plot of chunk plot-title-ex

The theme element plot.background modifies properties of the entire graphics region of a ggplot.

q0 + theme(
  plot.background = element_rect(
    fill = "lightblue",
    colour = "black",
    size = 2,
    linetype = "longdash")
  )

plot of chunk plot-bkgrd-ex

The plot.margin theme element controls the amount of space between the panel border and the edge of the graphics device region. It takes a four-element numeric vector as the first argument of unit(), corresponding to the top, right, bottom and left sides of the plot region. The border around the graphics region provides a reference against which to judge the size of the margins in each direction.

q0 + theme(plot.margin = unit(c(2, 0.5, 1, 1), "in"),
           plot.background = element_rect(colour = "black", size = 1))
q0 + theme(plot.margin = unit(c(1, 2, 3, 4), "cm"),
           plot.background = element_rect(colour = "black", size = 1))

plot of chunk plot-margin-ex plot of chunk plot-margin-ex

Finally, one can use the theming system to modify the aspect ratio of a ggplot. Here are a couple of examples:

q0 + theme(aspect.ratio = 1)
q0 + theme(aspect.ratio = 0.25)

plot of chunk aspect-ratio-ex plot of chunk aspect-ratio-ex

Theme functions

A theme function is an R function in ggplot2 that sets or parameterizes a collection of theme elements. By contrast, a theme object is the result of calling a theme function inside a ggplot() call. Although the default themes in ggplot2 produce attractive graphics, they may not necessarily correspond with user requirements. If you find yourself modifying the same theme elements repeatedly with theme() or need to adapt a set of theme elements to conform to the requirements of a journal or other publication, then you should consider writing your own theme function.

The purpose of a theme function is to either specify default settings for each theme element or modify the settings of an existing (complete) theme function to produce a new theme. For example, the foundational theme_grey function specifies default settings of each theme element, whereas theme_bw is a modification of theme_grey. There are several ways to write new theme functions, but some features that debut in the new theming system need to be discussed first.

Built-in theme functions: old and new

The primary differences between theme objects in the old and the new theming system are these:

  1. The old system contained 33 theme elements, while the new system has 38. The new theme elements are line, rect, text, axis.text and strip.text.

  2. Theme objects in the old theming system were lists, whose components were associated with each of the theme elements. The values of the components were either theme element functions with specified arguments or vectors (often of length 1) expressed in a unit of measurement. For example, the first few components of the theme object theme_grey() [note the evaluation of the function] under the old theming system are

    > theme_grey()
    $axis.line
    theme_blank()
    
    $axis.text.x
    theme_text(family = base_family, colour = "grey50", size = base_size *
        0.8, vjust = 1, lineheight = 0.9)
    
    $axis.text.y
    theme_text(family = base_family, colour = "grey50", size = base_size *
        0.8, hjust = 1, lineheight = 0.9)
    
    $axis.ticks
    theme_segment(colour = "grey50")
    
    $axis.title.x
    theme_text(family = base_family, size = base_size, vjust = 0.5)
    
    $axis.title.y
    theme_text(family = base_family, size = base_size, vjust = 0.5,
        angle = 90)
    
    $axis.ticks.length
    [1] 0.15cm
    

    In the new theming system, theme objects are nested lists; more specifically, the output of the theme element functions is returned for each theme element that is controlled by an element function. The first few components of scale_grey() under the new system:

    theme_grey()[1:5]
    
    #> $line
    #> List of 4
    #>  $ colour  : chr "black"
    #>  $ size    : num 0.5
    #>  $ linetype: num 1
    #>  $ lineend : chr "butt"
    #>  - attr(*, "class")= chr [1:2] "element_line" "element"
    #> 
    #> $rect
    #> List of 4
    #>  $ fill    : chr "white"
    #>  $ colour  : chr "black"
    #>  $ size    : num 0.5
    #>  $ linetype: num 1
    #>  - attr(*, "class")= chr [1:2] "element_rect" "element"
    #> 
    #> $text
    #> List of 8
    #>  $ family    : chr ""
    #>  $ face      : chr "plain"
    #>  $ colour    : chr "black"
    #>  $ size      : num 12
    #>  $ hjust     : num 0.5
    #>  $ vjust     : num 0.5
    #>  $ angle     : num 0
    #>  $ lineheight: num 0.9
    #>  - attr(*, "class")= chr [1:2] "element_text" "element"
    #> 
    #> $axis.text
    #> List of 8
    #>  $ family    : NULL
    #>  $ face      : NULL
    #>  $ colour    : chr "grey50"
    #>  $ size      :Class 'rel'  num 0.8
    #>  $ hjust     : NULL
    #>  $ vjust     : NULL
    #>  $ angle     : NULL
    #>  $ lineheight: NULL
    #>  - attr(*, "class")= chr [1:2] "element_text" "element"
    #> 
    #> $strip.text
    #> List of 8
    #>  $ family    : NULL
    #>  $ face      : NULL
    #>  $ colour    : NULL
    #>  $ size      :Class 'rel'  num 0.8
    #>  $ hjust     : NULL
    #>  $ vjust     : NULL
    #>  $ angle     : NULL
    #>  $ lineheight: NULL
    #>  - attr(*, "class")= chr [1:2] "element_text" "element"
    
  3. Inheritance of theme elements is supported in the new theming system. The first three elements in the new theme object are text, line and rect, which, not coincidentally, are the same as the names of the basic theme element functions. In fact, the (default) values of the arguments to these functions comprise list objects from which other theme elements may inherit. For example, the theme elements axis.text, legend.text, strip.text and axis.title all inherit from text, while axis.text.x and axis.text.y further inherit from axis.text. This means the values of the components of the theme element text are passed on to axis.text as well as other elements that inherit from text or its children. You can override the default values of one or more theme elements by calling theme() and modifying the desired properties of theme elements therein.

  4. Because of the inheritance properties available in the new theming system, it is now possible to apply relative sizing. For example, since text.axis inherits from text, one can define the size of axis tick labels to be half that of the defined text size under the new system with code like the following:

    theme(axis.text = element_text(size = rel(0.5)))
    
  5. Because of the inheritance properties associated with theme elements under the new system, it is possible to control the x and y components of the following theme elements individually with theme():

    panel.grid.major
    panel.grid.minor
    axis.ticks
    axis.line
    

    Under the old theming system, only the axis.ticks element had x and y components. The new theming system allows for richer inheritance structures. Here's an example that will warm the hearts of those who have had to write code to remove the grid lines from one dimension of a graphic in the legacy system.

    # require("ggplot2")
    DF <- data.frame(x = seq_len(10), y = seq_len(10))
    ggplot(DF, aes(x, y)) + theme_bw() + geom_blank() +
        theme(panel.grid.major.x = element_blank(),
              panel.grid.minor.x = element_blank())
    

    plot of chunk grid-lines-ex

    This example sets the font size in axis.text and modifies it for axis.text.y:

    ggplot(DF, aes(x, y)) + theme_bw() + geom_blank() +
        theme(axis.text = element_text(size = 12),
              axis.text.y = element_text(size = rel(1.5), angle = 90))
    

    plot of chunk axis.text-ex

The following graphic summarises the inheritance structure:

Inheritance structure

Complete and incomplete theme objects

In the new theming system, it is useful to understand the difference between complete and incomplete theme objects. A complete theme object is one produced by calling a theme function with the attribute complete = TRUE. A corresponding theme function with this attribute is also said to be complete. Theme functions theme_grey and theme_bw are examples of complete theme functions. Calls to theme() produce incomplete theme objects, since they represent (local) modifications to a theme object rather than returning a complete theme object per se.

To clarify the above points, consider the following examples:

attr(theme_grey(), "complete")
#> [1] TRUE
attr(theme(text = element_text(colour = "red")), "complete")
#> [1] FALSE

When adding an incomplete theme to a complete one, the result is a complete theme:

attr(theme_grey() +
  theme(text = element_text(colour = "red")), "complete")
#> [1] TRUE
attr(theme(text = element_text(colour = "red")) + theme_grey(),
     "complete")
#> [1] TRUE

Therefore, using theme() to locally modify the settings of theme elements in a complete theme object results in a complete theme object. Moreover, we will see below that the same game can be played to produce new complete theme functions.

When adding two incomplete themes, the result is incomplete:

attr(theme(text = element_text(colour = "red")) +
     theme(axis.text = element_text(colour = "blue")), "complete")
#> [1] FALSE

Complete and incomplete themes behave somewhat differently when added to a ggplot object. When adding an incomplete theme object to a ggplot, it is in effect augmenting the current default theme object, replacing only those properties of elements defined in the call to theme(). In particular, any NULL element properties specified in theme() are ignored and do not affect the plot's appearance. This is because the + operator cannot be used to set properties of a theme element to NULL.

# Set the default theme
theme_set(theme_grey())
# Modify it with theme()
tt <- theme(axis.text = element_text(size=14, colour=NULL))

# theme_grey has axis.text$colour="grey50"
# Adding tt does not reset the colour to NULL
# (which would result in black text for the axis labels)
qplot(1:3, 1:3) + tt

plot of chunk null-ex

This feature can be problematic when you want to replace the default theme entirely. For example, suppose the default theme object is theme_grey() and you want to add theme_bw() to it, where for the purpose of this discussion, we pretend the latter is an incomplete theme object. Certain element properties are non-NULL in theme_grey() but NULL in theme_bw(). If you do something like qplot(1:3, 1:3) + theme_bw(), the properties set to NULL in theme_bw() are not reset in the ggplot theme object – they simply carry over from the default theme object theme_grey().

To deal with this problem, the concept of a complete theme (object) was introduced in the new theming system. In particular, theme_grey and theme_bw are both complete theme functions and return complete theme objects when called. When added to a plot, a complete theme object overrides the current default theme and in fact replaces it. This idea is implemented by endowing a theme function with the attribute "complete": its value is TRUE for complete themes and FALSE for incomplete themes. Examples are shown in the next section.

Modifying a theme function

There is a crucial distinction between a theme function and a theme object. A theme function defines settings for theme elements, whereas a theme object is the result of calling a theme function in ggplot2.

Here is an example of modifying an existing theme object:

themeMod <- theme_grey() +
  theme(text = element_text(family = "Times", colour = "blue", size = 14))

Modifying an existing theme is not the same as creating a new theme. In this example, themeMod is a complete theme object but it is not a complete theme function. The + operator is typically used to modify complete theme objects created by applying the default or replacement theme function to a ggplot object. The %+replace% operator is primarily used to create new themes. For example,

# Only change the 'colour' property of theme element 'text'
mytheme1 <- theme_grey() + theme(text = element_text(colour="red"))
mytheme1$text
#> List of 8
#>  $ family    : chr ""
#>  $ face      : chr "plain"
#>  $ colour    : chr "red"
#>  $ size      : num 12
#>  $ hjust     : num 0.5
#>  $ vjust     : num 0.5
#>  $ angle     : num 0
#>  $ lineheight: num 0.9
#>  - attr(*, "class")= chr [1:2] "element_text" "element"

# Replace the 'text' element entirely
mytheme2 <- theme_grey() %+replace% theme(text = element_text(colour="red"))
mytheme2$text
#> List of 8
#>  $ family    : NULL
#>  $ face      : NULL
#>  $ colour    : chr "red"
#>  $ size      : NULL
#>  $ hjust     : NULL
#>  $ vjust     : NULL
#>  $ angle     : NULL
#>  $ lineheight: NULL
#>  - attr(*, "class")= chr [1:2] "element_text" "element"

mytheme1 is a complete theme object because an incomplete theme object is added to the complete theme object produced by theme_grey(). In contrast, mytheme2 is a complete theme function whose text element is redefined in its entirety, setting all properties not defined in element_text() to NULL. Therefore, when using the %+replace% operator to create a new theme function, you need to be very careful about replacing theme elements at the top of the inheritance hierarchy such as text, line and rect.

The theme function theme_grey illustrates how to create a complete theme function under the new theming system by specifying the settings of each theme element in turn:

theme_grey
#> function(base_size = 12, base_family = "") {
#>   theme(
#>     # Elements in this first block aren't used directly, but are inherited
#>     # by others
#>     line =               element_line(colour = "black", size = 0.5, linetype = 1,
#>                             lineend = "butt"),
#>     rect =               element_rect(fill = "white", colour = "black", size = 0.5, linetype = 1),
#>     text =               element_text(family = base_family, face = "plain",
#>                             colour = "black", size = base_size,
#>                             hjust = 0.5, vjust = 0.5, angle = 0, lineheight = 0.9),
#>     axis.text =          element_text(size = rel(0.8), colour = "grey50"),
#>     strip.text =         element_text(size = rel(0.8)),
#> 
#>     axis.line =          element_blank(),
#>     axis.text.x =        element_text(vjust = 1),
#>     axis.text.y =        element_text(hjust = 1),
#>     axis.ticks =         element_line(colour = "grey50"),
#>     axis.title.x =       element_text(),
#>     axis.title.y =       element_text(angle = 90),
#>     axis.ticks.length =  unit(0.15, "cm"),
#>     axis.ticks.margin =  unit(0.1, "cm"),
#> 
#>     legend.background =  element_rect(colour = NA),
#>     legend.margin =      unit(0.2, "cm"),
#>     legend.key =         element_rect(fill = "grey95", colour = "white"),
#>     legend.key.size =    unit(1.2, "lines"),
#>     legend.key.height =  NULL,
#>     legend.key.width =   NULL,
#>     legend.text =        element_text(size = rel(0.8)),
#>     legend.text.align =  NULL,
#>     legend.title =       element_text(size = rel(0.8), face = "bold", hjust = 0),
#>     legend.title.align = NULL,
#>     legend.position =    "right",
#>     legend.direction =   NULL,
#>     legend.justification = "center",
#>     legend.box =         NULL,
#> 
#>     panel.background =   element_rect(fill = "grey90", colour = NA),
#>     panel.border =       element_blank(),
#>     panel.grid.major =   element_line(colour = "white"),
#>     panel.grid.minor =   element_line(colour = "grey95", size = 0.25),
#>     panel.margin =       unit(0.25, "lines"),
#>     panel.margin.x =     NULL,
#>     panel.margin.y =     NULL,
#> 
#>     strip.background =   element_rect(fill = "grey80", colour = NA),
#>     strip.text.x =       element_text(),
#>     strip.text.y =       element_text(angle = -90),
#> 
#>     plot.background =    element_rect(colour = "white"),
#>     plot.title =         element_text(size = rel(1.2)),
#>     plot.margin =        unit(c(1, 1, 0.5, 0.5), "lines"),
#> 
#>     complete = TRUE
#>   )
#> }
#> <environment: namespace:ggplot2>

The last line, complete = TRUE, sets the attribute complete of the theme function to TRUE. To see the result of this theme, type theme_grey() at the prompt.

The %+replace% operator is illustrated in the complete theme function theme_bw:

theme_bw
#> function(base_size = 12, base_family = "") {
#>   # Starts with theme_grey and then modify some parts
#>   theme_grey(base_size = base_size, base_family = base_family) %+replace%
#>     theme(
#>       axis.text         = element_text(size = rel(0.8)),
#>       axis.ticks        = element_line(colour = "black"),
#>       legend.key        = element_rect(colour = "grey80"),
#>       panel.background  = element_rect(fill = "white", colour = NA),
#>       panel.border      = element_rect(fill = NA, colour = "grey50"),
#>       panel.grid.major  = element_line(colour = "grey90", size = 0.2),
#>       panel.grid.minor  = element_line(colour = "grey98", size = 0.5),
#>       strip.background  = element_rect(fill = "grey80", colour = "grey50", size = 0.2)
#>     )
#> }
#> <environment: namespace:ggplot2>

Notice that the theme elements replaced in theme_bw primarily have NULL properties in theme_grey() since most of the default properties in the latter are defined in elements rect, line and text and passed down to their child elements. The %+replace% operator is used to set non-NULL properties in the selected elements specified in theme() with all undeclared properties set to NULL. This means you need to apply the %+replace% operator judiciously when creating new theme functions piggy-backed off of theme_grey.

Examples of user-defined theme functions

The first example, theme_black, illustrates writing a theme function in the style of theme_grey - i.e., each theme element is specified individually.

theme_black <- function(base_size = 12, base_family = "Helvetica") {
    theme(
    line =               element_line(colour = "black", size = 0.5, linetype = 1,
                            lineend = "butt"),
    rect =               element_rect(fill = "white", colour = "black", size = 0.5, linetype = 1),
    text =               element_text(family = base_family, face = "plain",
                            colour = "black", size = base_size,
                            hjust = 0.5, vjust = 0.5, angle = 0, lineheight = 0.9),
    axis.text =          element_text(size = rel(0.8), colour = "white"),
    strip.text =         element_text(size = rel(0.8), colour = "white"),

    axis.line =          element_blank(),
    axis.text.x =        element_text(vjust = 1),
    axis.text.y =        element_text(hjust = 1),
    axis.ticks =         element_line(colour = "white", size = 0.2),
    axis.title =         element_text(colour = "white"),
    axis.title.x =       element_text(vjust = 1),
    axis.title.y =       element_text(angle = 90),
    axis.ticks.length =  unit(0.3, "lines"),
    axis.ticks.margin =  unit(0.5, "lines"),

    legend.background =  element_rect(colour = NA),
    legend.margin =      unit(0.2, "cm"),
    legend.key =         element_rect(fill = "black", colour = "white"),
    legend.key.size =    unit(1.2, "lines"),
    legend.key.height =  NULL,
    legend.key.width =   NULL,
    legend.text =        element_text(size = rel(0.8), colour = "white"),
    legend.text.align =  NULL,
    legend.title =       element_text(size = rel(0.8), face = "bold", hjust = 0, colour = "white"),
    legend.title.align = NULL,
    legend.position =    "right",
    legend.direction =   "vertical",
    legend.justification = "center",
    legend.box =         NULL,

    panel.background =   element_rect(fill = "black", colour = NA),
    panel.border =       element_rect(fill = NA, colour = "white"),
    panel.grid.major =   element_line(colour = "grey20", size = 0.2),
    panel.grid.minor =   element_line(colour = "grey5", size = 0.5),
    panel.margin =       unit(0.25, "lines"),

    strip.background =   element_rect(fill = "grey30", colour = "grey10"),
    strip.text.x =       element_text(),
    strip.text.y =       element_text(angle = -90),

    plot.background =    element_rect(colour = "black", fill = "black"),
    plot.title =         element_text(size = rel(1.2)),
    plot.margin =        unit(c(1, 1, 0.5, 0.5), "lines"),

    complete = TRUE
  )
}
# Check that it is a complete theme
attr(theme_black(), "complete")
#> [1] TRUE
# Apply it
qplot(1:10, 1:10, geom = "point", colour = I("yellow")) + theme_black()

plot of chunk theme-black

An example of a theme function whose definition employs the %+replace operator in the new theming system is one that removes all of the borders, text, grid lines, axis ticks and axis labels. It is called theme_nothing, authored by David Kahle under the old theming system. To reproduce its effect in the new system, rewrite it as follows:

theme_nothing <- function(base_size = 12, base_family = "Helvetica")
  {
  theme_bw(base_size = base_size, base_family = base_family) %+replace%
      theme(
            rect             = element_blank(),
            line             = element_blank(),
            text             = element_blank(),
            axis.ticks.margin = unit(0, "lines")
           )
  }
# check that it is a complete theme
attr(theme_nothing(), "complete")
#> [1] TRUE
# Apply it:
qplot(1:10, 1:10) + theme_nothing()

plot of chunk theme-nothing