User Guide: 4 Manipulation of plot layers

‘ggpmisc’ 0.2.16

Pedro J. Aphalo

2017-09-17

Preliminaries

library(ggplot2)
library(ggpmisc)
library(tibble)

We generate some artificial data.

set.seed(4321)
# generate artificial data
my.data <- data.frame(
  group = factor(rep(letters[1:4], each = 30)),
  panel = factor(rep(LETTERS[1:2], each = 60)),
  y = rnorm(40)
)

We change the default theme to an uncluttered one.

old_theme <- theme_set(theme_bw())

Introduction

The functions described here are not expected to be useful in everyday plotting as one can simply change the order in which layers are added to a ggplot. However, if one uses high level methods or functions that automatically produce a full plot using ‘ggplot2’ internally, one may need to add, move or delete layers so as to profit from such canned methods and retain enough flexibility.

In a ggplot object, layers reside in a list, and their positions in the list determine the plotting order when generating the graphical output. The Grammar of Graphics treats the list of layers as a stack using only push operations. In other words, always the most recently added layer resides at the end of the list, and over-plots all layers previously added. The functions described in this vignette allow overriding the normal syntax at the cost of breaking the expectations of the grammar. These functions are, as told above, to be used only in exceptional cases. This notwithstanding, they are rather easy to use. The user interface is consistent across all of them. Moreover, they are designed to return objects that are identical to objects created using the normal syntax rules of the Grammar of Graphics**.

Function Use
delete_layers() delete one or more layers
append_layers() append layers at a specific position
move_layers() move layers to an absolute position
shift_layers() move layers to a relative position
which_layers() obtain the index positions of layers
extract_layers() extract matched or indexed layers
num_layers() obtain number of layers
top_layer() obtain position of top layer
bottom_layer() obtain position of bottom layer

Although their definitions does not rely on code internal to ‘ggplot2’, they rely on the internal structure of objects belonging to class gg and ggplot. Consequently, long-term backwards compatibility cannot be guaranteed.

Examples

Preliminaries

We generate a plot to be used later to demonstrate the use of the functions.

p <- ggplot(my.data, aes(group, y)) + 
  geom_point() +
  stat_summary(fun.data = mean_se, colour = "red", size = 1.3) +
  facet_wrap(~panel, scales = "free_x", labeller = label_both)
p

To display textual information about a gg object we use method summary(), while methods print() and plot() will display the actual plot.

summary(p)
## data: group, panel, y [120x3]
## mapping:  x = group, y = y
## faceting: <ggproto object: Class FacetWrap, Facet>
##     compute_layout: function
##     draw_back: function
##     draw_front: function
##     draw_labels: function
##     draw_panels: function
##     finish_data: function
##     init_scales: function
##     map: function
##     map_data: function
##     params: list
##     render_back: function
##     render_front: function
##     render_panels: function
##     setup_data: function
##     setup_params: function
##     shrink: TRUE
##     train: function
##     train_positions: function
##     train_scales: function
##     super:  <ggproto object: Class FacetWrap, Facet>
## -----------------------------------
## geom_point: na.rm = FALSE
## stat_identity: na.rm = FALSE
## position_identity 
## 
## geom_pointrange: na.rm = FALSE
## stat_summary: fun.data = function (x, mult = 1) 
## {
##     x <- stats::na.omit(x)
##     se <- mult * sqrt(stats::var(x)/length(x))
##     mean <- mean(x)
##     data.frame(y = mean, ymin = mean - se, ymax = mean + se)
## }, fun.y = NULL, fun.ymax = NULL, fun.ymin = NULL, fun.args = list(), na.rm = FALSE
## position_identity

Layers in a plot are stored in a list. The default print() method for a list of layers displays only a small part of the information in a layer.

p$layers
## [[1]]
## geom_point: na.rm = FALSE
## stat_identity: na.rm = FALSE
## position_identity 
## 
## [[2]]
## geom_pointrange: na.rm = FALSE
## stat_summary: fun.data = function (x, mult = 1) 
## {
##     x <- stats::na.omit(x)
##     se <- mult * sqrt(stats::var(x)/length(x))
##     mean <- mean(x)
##     data.frame(y = mean, ymin = mean - se, ymax = mean + se)
## }, fun.y = NULL, fun.ymax = NULL, fun.ymin = NULL, fun.args = list(), na.rm = FALSE
## position_identity

To see all the fields, one needs to use str(), which we use here for a single layer.

str(p$layers[[1]])
## Classes 'LayerInstance', 'Layer', 'ggproto' <ggproto object: Class LayerInstance, Layer>
##     aes_params: list
##     compute_aesthetics: function
##     compute_geom_1: function
##     compute_geom_2: function
##     compute_position: function
##     compute_statistic: function
##     data: waiver
##     draw_geom: function
##     finish_statistics: function
##     geom: <ggproto object: Class GeomPoint, Geom>
##         aesthetics: function
##         default_aes: uneval
##         draw_group: function
##         draw_key: function
##         draw_layer: function
##         draw_panel: function
##         extra_params: na.rm
##         handle_na: function
##         non_missing_aes: size shape colour
##         optional_aes: 
##         parameters: function
##         required_aes: x y
##         setup_data: function
##         use_defaults: function
##         super:  <ggproto object: Class Geom>
##     geom_params: list
##     inherit.aes: TRUE
##     layer_data: function
##     map_statistic: function
##     mapping: NULL
##     position: <ggproto object: Class PositionIdentity, Position>
##         compute_layer: function
##         compute_panel: function
##         required_aes: 
##         setup_data: function
##         setup_params: function
##         super:  <ggproto object: Class Position>
##     print: function
##     show.legend: NA
##     stat: <ggproto object: Class StatIdentity, Stat>
##         aesthetics: function
##         compute_group: function
##         compute_layer: function
##         compute_panel: function
##         default_aes: uneval
##         extra_params: na.rm
##         finish_layer: function
##         non_missing_aes: 
##         parameters: function
##         required_aes: 
##         retransform: TRUE
##         setup_data: function
##         setup_params: function
##         super:  <ggproto object: Class Stat>
##     stat_params: list
##     subset: NULL
##     super:  <ggproto object: Class Layer>

Manipulation of plot layers

We start by using which_layers() as it produces simply a vector of indexes into the list of layers. The third statement is useless here, but demonstrates how layers are selected in all the functions described in this document.

which_layers(p, "GeomPoint")
## Warning in if (is.na(idx)) {: the condition has length > 1 and only the
## first element will be used
## [1] 1
which_layers(p, "StatSummary")
## Warning in if (is.na(idx)) {: the condition has length > 1 and only the
## first element will be used
## [1] 2
which_layers(p, idx = 1L)
## Warning in if (is.na(idx)) {: the condition has length > 1 and only the
## first element will be used
## [1] 1

We can also easily extract matching layers with extract_layers(). Here one layer is returned, and displayed using the default print() method. Method str() can be used as shown above.

extract_layers(p, "GeomPoint")
## Warning in if (is.na(idx)) {: the condition has length > 1 and only the
## first element will be used
## [[1]]
## geom_point: na.rm = FALSE
## stat_identity: na.rm = FALSE
## position_identity

With delete_layers() we can remove layers from a plot, selecting them using the match to a class, as shown here, or by a positional index. This was shown above for which_layers().

delete_layers(p, "GeomPoint")
## Warning in if (is.na(idx)) {: the condition has length > 1 and only the
## first element will be used

delete_layers(p, idx = 1L)
## Warning in if (is.na(idx)) {: the condition has length > 1 and only the
## first element will be used

delete_layers(p, "StatSummary")
## Warning in if (is.na(idx)) {: the condition has length > 1 and only the
## first element will be used

With move_layers() we can alter the stacking order of layers. The layers to move are selected in the same way as in the examples above, while position gives where to move the layers to. Two character strings, "top" and "bottom" are accept as position argument, as well as integers. In the later case, the layer(s) is/are appended after the supplied position with reference to the list of layers not being moved.

move_layers(p, "GeomPoint", position = "top")
## Warning in if (is.na(idx)) {: the condition has length > 1 and only the
## first element will be used

Same operation but using a relative position. A positive value for shift is intertreted as an upward displacement and a negative one as downwards displacement.

shift_layers(p, "GeomPoint", shift = +1)
## Warning in if (is.na(idx)) {: the condition has length > 1 and only the
## first element will be used

## Warning in if (is.na(idx)) {: the condition has length > 1 and only the
## first element will be used

Here we show how to add a layer behind all other layers.

append_layers(p, geom_line(colour = "orange", size = 1), position = "bottom")
## Warning in if (is.na(idx)) {: the condition has length > 1 and only the
## first element will be used

It is also possible to append the new layer immediately above an arbitrary existing layer using a numeric index, which as shown here can be also obtained by matching to a class name. In this example we insert a new layer in-between two layers already present in the plot. As with the + operator of the Grammar of Graphics, object also accepts a list of layers as argument (no example shown).

append_layers(p, object = geom_line(colour = "orange", size = 1), 
              position = which_layers(p, "GeomPoint"))
## Warning in if (is.na(idx)) {: the condition has length > 1 and only the
## first element will be used

## Warning in if (is.na(idx)) {: the condition has length > 1 and only the
## first element will be used

Annotations add layers, so they can be manipulated in the same way as other layers.

p1 <- p + 
  annotate("text", label = "text label", x = 1.1, y = 0, hjust = 0)
p1

delete_layers(p1, "GeomText")
## Warning in if (is.na(idx)) {: the condition has length > 1 and only the
## first element will be used