Skip to content
Winston Chang edited this page May 29, 2012 · 8 revisions

New theme system

This is a description of the changes for the new ggplot2 theming system, to be in version 0.9.2. It shouldn't break any code from the previous versions of ggplot2, but it adds some new features, and makes some things easier.

Renamed and changed theme_xx functions

The functions theme_text, theme_rect, theme_line, and theme_segment have been renamed to element_xx. These new names are less confusing than the old ones -- these are theme elements as opposed to themes, like theme_grey().

The theme_xx functions still work, but they now print deprecation messages.

Previously, theme_line() and theme_segment() were distinct. Now, there is only element_line() -- the segment version was no longer necessary and was removed.

Inheritance

The theme system supports inheritance. That is, theme elements can inherit properties from other theme elements. For example, axis.title.x and axis.title.y inherit their properties from axis.title, which in tun inherits properties from text. If for axis.title.x, you set just the size with:

opts(axis.title.x = element_text(size = 8))

Then axis.title.x will inherit all the other properties (family, face, colour, etc) but size will be set to 8.

Some elements are never used directly, but are only used for inheritance. Examples include text and the axis.title.

All of the elements with text inherit from text, directly or indirectly. Similarly, all elements that have rectangle grobs inherit from rect.

The inheritance is defined by a data structure .element_tree, which you can see at the bottom of theme-elements.r.

Here is a graph of the inheritance structure, generated with igraph:

Element inheritance graph

Multiple inheritance is also supported by this system but it's not actually used for any existing elements.

The function calc_element will the calculate the properties of an element (with inheritance) and return an element object:

ggplot2:::calc_element("axis.ticks.y", theme_grey())
# $colour
# [1] "grey50"
#
# $size
# [1] 0.5
#
# $linetype
# [1] 1
#
# attr(,"class")
# [1] "element"      "element_line"

Theme objects are simple nested lists

Theme objects are now simple nested lists. Previously, a theme (such as the one returned by theme_grey()) was a list of mostly functions, which return grobs. Functions like theme_rect() would return functions which returned grobs.

Now a theme is a list of lists. element_rect() returns a list (of class element_rect), instead of a function that returns a grob.

You can see how themes are specified in theme-defaults.r. Presently, theme_grey is the default theme, and theme_bw is simply a modification of it.

Relative sizing

Relative sizing is specified with rel(). For example, axis.text inherits from text, and this would make axis.text have a font size that is half the size of text.

opts(axis.text = element_text(size=rel(0.5)))

It would also directly inherit all other properties, like family and colour.

Modifying a theme

You can still use opts() to modify theme settings for a particular graph.

To modify a theme object, you can do something like the following:

mytheme <- theme_grey()

mytheme$text <- element_text(family="Times", face="plain", colour="red", 
  size=16, hjust=0.5, vjust=0.5, angle=0, lineheight=0.9)

This is actually how theme_bw() is now defined. Notice that, for a top-level element like text, it's necessary to specify all the properties like family, face, colour, and so on; if these properties aren't specified, they default to NULL.

Another way to modify a theme is to change the nested list items directly. I'm not sure this is a good way to do it in the long term, though. For example, this would have the same effect as above:

mytheme <- theme_grey()

mytheme$text$family <- "Times"
mytheme$text$colour <-"red"
mytheme$text$size   <- 16

It seems to me that there should be a better way of modifying individual properties of an element, but it's not obvious to me at this point how to do that.

Independent control over some x and y elements

It's now possible to independently control the x and y components of following:

  • panel.grid.major.x and .y
  • panel.grid.minor.x and .y
  • axis.ticks.x and .y
  • axis.line.x and .y

For example, panel.grid.major.x inherits from panel.grid.major, and so if the .x version is not specified, it simply inherits all properties from panel.grid.major.

It is now possible to disable just horizontal or just vertical grid lines without any ugly hacks. For example:

# Remove horizontal grid lines
qplot(1:5, 1:5) +
  opts(panel.grid.major.y = element_blank(),
       panel.grid.minor.y = element_blank())

Notes

Element graph

This is the code that generates the element inheritance graph.

build_element_graph <- function(tree) {
  require(igraph)

  inheritdf <- function(name, item) {
    if (length(item$inherits) != 0)
      data.frame(child = name, parent = item$inherits)
    else
      data.frame()
  }

  edges <- rbind.fill(mapply(inheritdf, names(tree), tree))

  # Explicitly add vertices (since not all are in edge list)
  vertices <- data.frame(name = names(tree))
  graph.data.frame(edges, vertices = vertices)
}

g <- build_element_graph(.element_tree)
V(g)$label <- V(g)$name

png('element_graph.png', width=700, height=600)
set.seed(323)
par(mar=c(0,0,0,0)) # Remove unnecessary margins
plot(g, layout=layout.fruchterman.reingold, vertex.size=4, vertex.label.dist=.25)
dev.off()
Clone this wiki locally