Skip to content

Commit 56e8612

Browse files
committed
Tease apart themes
* Focus +.gg on ggplot2 objects * Move current theme accessors to new file * Use explicit environment to manage current theme * Other doc improvements
1 parent 84f5d9c commit 56e8612

File tree

9 files changed

+207
-206
lines changed

9 files changed

+207
-206
lines changed

DESCRIPTION

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ License: GPL-2 | file LICENSE
4747
URL: http://ggplot2.tidyverse.org, https://github.com/tidyverse/ggplot2
4848
BugReports: https://github.com/tidyverse/ggplot2/issues
4949
LazyData: true
50-
Collate:
50+
Collate:
5151
'ggproto.r'
5252
'aaa-.r'
5353
'aes-calculated.r'
@@ -201,9 +201,10 @@ Collate:
201201
'stat-unique.r'
202202
'stat-ydensity.r'
203203
'summary.r'
204-
'theme-defaults.r'
205204
'theme-elements.r'
206205
'theme.r'
206+
'theme-defaults.r'
207+
'theme-current.R'
207208
'translate-qplot-ggplot.r'
208209
'translate-qplot-lattice.r'
209210
'utilities-break.r'

R/plot-construction.r

Lines changed: 16 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -1,62 +1,44 @@
1-
#' Add a new component to a ggplot or theme object.
1+
#' Add components to a plot
22
#'
3-
#' This operator allows you to add objects to a ggplot or theme object.
3+
#' \code{+} is the key to constructing sophisticated ggplot2 graphics. It
4+
#' allows you to start simple, then get more and more complex, checking your
5+
#' work at each step.
46
#'
5-
#' @section Adding on to a ggplot object:
7+
#' @section What can you add?:
68
#' You can add any of the following types of objects:
79
#'
810
#' \itemize{
9-
#' \item \code{uneval}: replace current aesthetics
10-
#' \item \code{layer}: add new layer
11-
#' \item \code{theme}: update plot theme
12-
#' \item \code{scale}: replace current scale
13-
#' \item \code{coord}: override current coordinate system
14-
#' \item \code{facet}: override current coordinate faceting
15-
#' \item \code{list}: a list of any of the above.
11+
#' \item A \code{\link{aes}()} objects replaces the default aesthetics.
12+
#' \item A layer created by a \code{geom_} or \code{stat_} function adds
13+
#' new layer.
14+
#' \item A \code{scale} overrides the existing scale.
15+
#' \item A \code{\link{theme}} modifies the current theme.
16+
#' \item A \code{coord} overrides current coordinate system.
17+
#' \item A \code{facet} specificatio override current faceting.
1618
#' }
1719
#'
1820
#' To replace the current default data frame, you must use \code{\%+\%},
1921
#' due to S3 method precedence issues.
2022
#'
21-
#' @section Adding on to a theme:
22-
#'
23-
#' \code{+} and \code{\%+replace\%} can be used to modify elements in themes.
24-
#'
25-
#' \code{+} updates the elements of e1 that differ from elements specified (not
26-
#' NULL) in e2. Thus this operator can be used to incrementally add or modify
27-
#' attributes of a ggplot theme.
28-
#'
29-
#' In contrast, \code{\%+replace\%} replaces the entire element; any element of
30-
#' a theme not specified in e2 will not be present in the resulting theme (i.e.
31-
#' NULL). Thus this operator can be used to overwrite an entire theme.
23+
#' You can also supply a list, in which case each element of the list will
24+
#' be added in turn.
3225
#'
3326
#' @param e1 An object of class \code{\link{ggplot}} or a \code{\link{theme}}.
34-
#' @param e2 A component, or list of components to add to \code{e1}.
27+
#' @param e2 A plot component, as described below.
3528
#' @seealso \code{\link{theme}}
3629
#' @export
3730
#' @method + gg
3831
#' @rdname gg-add
3932
#' @examples
40-
#' # Adding on to a plot -----------------------------------------------
41-
#'
4233
#' base <- ggplot(mpg, aes(displ, hwy)) + geom_point()
4334
#' base + geom_smooth()
4435
#'
4536
#' # To override the data, you must use %+%
4637
#' base %+% subset(mpg, fl == "p")
4738
#'
4839
#' # Alternatively, you can add multiple components with a list.
49-
#' # This can be useful to return from a list.
40+
#' # This can be useful to return from a function.
5041
#' base + list(subset(mpg, fl == "p"), geom_smooth())
51-
#'
52-
#' # Adding on to a theme ----------------------------------------------
53-
#'
54-
#' # Compare these results of adding theme objects to other theme objects
55-
#' add_el <- theme_grey() + theme(text = element_text(family = "Times"))
56-
#' rep_el <- theme_grey() %+replace% theme(text = element_text(family = "Times"))
57-
#'
58-
#' add_el$text
59-
#' rep_el$text
6042
"+.gg" <- function(e1, e2) {
6143
# Get the name of what was passed in as e2, and pass along so that it
6244
# can be displayed in error messages

R/theme-current.R

Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
#' @include theme-defaults.r
2+
#' @include theme-elements.r
3+
theme_env <- new.env(parent = emptyenv())
4+
theme_env$current <- theme_gray()
5+
6+
#' Get, set and update themes
7+
#'
8+
#' Use \code{theme_get} to get the current theme, and \code{theme_set} to
9+
#' completely override it. \code{theme_update} and \code{theme_replace} are
10+
#' shorthands for changing individual elements in the current theme.
11+
#'
12+
#' @section Adding on to a theme:
13+
#'
14+
#' \code{+} and \code{\%+replace\%} can be used to modify elements in themes.
15+
#'
16+
#' \code{+} updates the elements of e1 that differ from elements specified (not
17+
#' NULL) in e2. Thus this operator can be used to incrementally add or modify
18+
#' attributes of a ggplot theme.
19+
#'
20+
#' In contrast, \code{\%+replace\%} replaces the entire element; any element of
21+
#' a theme not specified in e2 will not be present in the resulting theme (i.e.
22+
#' NULL). Thus this operator can be used to overwrite an entire theme.
23+
#'
24+
#' \code{theme_update} uses the \code{+} operator, so that any unspecified
25+
#' values in the theme element will default to the values they are set in the
26+
#' theme. \code{theme_replace} uses \code{\%+replace\%} tocompletely replace
27+
#' the element, so any unspecified values will overwrite the current value in
28+
#' the theme with \code{NULL}s.
29+
#'
30+
#' @param ... named list of theme settings
31+
#' @param e1,e2 Theme and element to combine
32+
#' @seealso \code{\link{+.gg}}
33+
#' @export
34+
#' @examples
35+
#' p <- ggplot(mtcars, aes(mpg, wt)) +
36+
#' geom_point()
37+
#' p
38+
#' old <- theme_set(theme_bw())
39+
#' p
40+
#' theme_set(old)
41+
#' p
42+
#'
43+
#' #theme_replace NULLs out the fill attribute of panel.background,
44+
#' #resulting in a white background:
45+
#' theme_get()$panel.background
46+
#' old <- theme_replace(panel.background = element_rect(colour = "pink"))
47+
#' theme_get()$panel.background
48+
#' p
49+
#' theme_set(old)
50+
#'
51+
#' #theme_update only changes the colour attribute, leaving the others intact:
52+
#' old <- theme_update(panel.background = element_rect(colour = "pink"))
53+
#' theme_get()$panel.background
54+
#' p
55+
#' theme_set(old)
56+
#'
57+
#' theme_get()
58+
#'
59+
#'
60+
#' ggplot(mtcars, aes(mpg, wt)) +
61+
#' geom_point(aes(color = mpg)) +
62+
#' theme(legend.position = c(0.95, 0.95),
63+
#' legend.justification = c(1, 1))
64+
#' last_plot() +
65+
#' theme(legend.background = element_rect(fill = "white", colour = "white", size = 3))
66+
#'
67+
#' # Adding on to a theme ----------------------------------------------
68+
#'
69+
#' # Compare these results of adding theme objects to other theme objects
70+
#' add_el <- theme_grey() + theme(text = element_text(family = "Times"))
71+
#' rep_el <- theme_grey() %+replace% theme(text = element_text(family = "Times"))
72+
#'
73+
#' add_el$text
74+
#' rep_el$text
75+
theme_get <- function() {
76+
theme_env$current
77+
}
78+
79+
#' @rdname theme_get
80+
#' @param new new theme (a list of theme elements)
81+
#' @export
82+
theme_set <- function(new) {
83+
missing <- setdiff(names(theme_gray()), names(new))
84+
if (length(missing) > 0) {
85+
warning("New theme missing the following elements: ",
86+
paste(missing, collapse = ", "), call. = FALSE)
87+
}
88+
89+
old <- theme_env$current
90+
theme_env$current <- new
91+
invisible(old)
92+
}
93+
94+
#' @rdname theme_get
95+
#' @export
96+
theme_update <- function(...) {
97+
theme_set(theme_get() + theme(...))
98+
}
99+
100+
#' @rdname theme_get
101+
#' @export
102+
theme_replace <- function(...) {
103+
theme_set(theme_get() %+replace% theme(...))
104+
}
105+
106+
#' @rdname theme_get
107+
#' @export
108+
"%+replace%" <- function(e1, e2) {
109+
if (!is.theme(e1) || !is.theme(e2)) {
110+
stop("%+replace% requires two theme objects", call. = FALSE)
111+
}
112+
113+
# Can't use modifyList here since it works recursively and drops NULLs
114+
e1[names(e2)] <- e2
115+
e1
116+
}

R/theme-defaults.r

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@
5757
#' @name ggtheme
5858
NULL
5959

60+
#' @include theme.r
6061
#' @export
6162
#' @rdname ggtheme
6263
theme_grey <- function(base_size = 11, base_family = "") {

R/theme.r

Lines changed: 12 additions & 114 deletions
Original file line numberDiff line numberDiff line change
@@ -1,68 +1,3 @@
1-
#' Get, set and update themes.
2-
#'
3-
#' Use \code{theme_get} to get the current theme, and \code{theme_set} to
4-
#' completely override it. \code{theme_update} and \code{theme_replace} are
5-
#' shorthands for changing individual elements in the current theme.
6-
#' \code{theme_update} uses the \code{+} operator, so that any unspecified
7-
#' values in the theme element will default to the values they are set in the
8-
#' theme. \code{theme_replace} will completely replace the element, so any
9-
#' unspecified values will overwrite the current value in the theme with \code{NULL}s.
10-
#'
11-
#'
12-
#' @param ... named list of theme settings
13-
#' @seealso \code{\link{\%+replace\%}} and \code{\link{+.gg}}
14-
#' @export
15-
#' @examples
16-
#' p <- ggplot(mtcars, aes(mpg, wt)) +
17-
#' geom_point()
18-
#' p
19-
#' old <- theme_set(theme_bw())
20-
#' p
21-
#' theme_set(old)
22-
#' p
23-
#'
24-
#' #theme_replace NULLs out the fill attribute of panel.background,
25-
#' #resulting in a white background:
26-
#' theme_get()$panel.background
27-
#' old <- theme_replace(panel.background = element_rect(colour = "pink"))
28-
#' theme_get()$panel.background
29-
#' p
30-
#' theme_set(old)
31-
#'
32-
#' #theme_update only changes the colour attribute, leaving the others intact:
33-
#' old <- theme_update(panel.background = element_rect(colour = "pink"))
34-
#' theme_get()$panel.background
35-
#' p
36-
#' theme_set(old)
37-
#'
38-
#' theme_get()
39-
#'
40-
#'
41-
#' ggplot(mtcars, aes(mpg, wt)) +
42-
#' geom_point(aes(color = mpg)) +
43-
#' theme(legend.position = c(0.95, 0.95),
44-
#' legend.justification = c(1, 1))
45-
#' last_plot() +
46-
#' theme(legend.background = element_rect(fill = "white", colour = "white", size = 3))
47-
#'
48-
theme_update <- function(...) {
49-
theme_set(theme_get() + theme(...))
50-
}
51-
52-
#' @rdname theme_update
53-
#' @export
54-
theme_replace <- function(...) {
55-
theme_set(theme_get() %+replace% theme(...))
56-
}
57-
58-
#' Reports whether x is a theme object
59-
#' @param x An object to test
60-
#' @export
61-
is.theme <- function(x) inherits(x, "theme")
62-
63-
#' @export
64-
print.theme <- function(x, ...) utils::str(x)
65-
661
#' Set theme elements
672
#'
683
#' Theme elements can inherit properties from other theme elements.
@@ -441,61 +376,16 @@ plot_theme <- function(x) {
441376
defaults(x$theme, theme_get())
442377
}
443378

444-
445-
.theme <- (function() {
446-
theme <- theme_gray()
447-
448-
list(
449-
get = function() theme,
450-
set = function(new) {
451-
missing <- setdiff(names(theme_gray()), names(new))
452-
if (length(missing) > 0) {
453-
warning("New theme missing the following elements: ",
454-
paste(missing, collapse = ", "), call. = FALSE)
455-
}
456-
457-
old <- theme
458-
theme <<- new
459-
invisible(old)
460-
}
461-
)
462-
})()
463-
464-
465-
#' @rdname theme_update
466-
#' @export
467-
theme_get <- .theme$get
468-
#' @rdname theme_update
469-
#' @param new new theme (a list of theme elements)
470-
#' @export
471-
theme_set <- .theme$set
472-
473-
474-
#' @rdname gg-add
475-
#' @export
476-
"%+replace%" <- function(e1, e2) {
477-
if (!is.theme(e1) || !is.theme(e2)) {
478-
stop("%+replace% requires two theme objects", call. = FALSE)
479-
}
480-
481-
# Can't use modifyList here since it works recursively and drops NULLs
482-
e1[names(e2)] <- e2
483-
e1
484-
}
485-
486-
487379
#' Modify properties of an element in a theme object
488380
#'
489381
#' @param t1 A theme object
490382
#' @param t2 A theme object that is to be added to \code{t1}
491383
#' @param t2name A name of the t2 object. This is used for printing
492384
#' informative error messages.
493-
#'
494-
#' @seealso +.gg
495-
#'
385+
#' @keywords internal
496386
add_theme <- function(t1, t2, t2name) {
497387
if (!is.theme(t2)) {
498-
stop("Don't know how to add ", t2name, " to a theme object",
388+
stop("Don't know how to add RHS to a theme object",
499389
call. = FALSE)
500390
}
501391

@@ -586,6 +476,8 @@ update_theme <- function(oldtheme, newtheme) {
586476
#' @param element The name of the theme element to calculate
587477
#' @param theme A theme object (like theme_grey())
588478
#' @param verbose If TRUE, print out which elements this one inherits from
479+
#' @keywords internal
480+
#' @export
589481
#' @examples
590482
#' t <- theme_grey()
591483
#' calc_element('text', t)
@@ -599,8 +491,6 @@ update_theme <- function(oldtheme, newtheme) {
599491
#' t$axis.text.x
600492
#' t$axis.text
601493
#' t$text
602-
#'
603-
#' @export
604494
calc_element <- function(element, theme, verbose = FALSE) {
605495
if (verbose) message(element, " --> ", appendLF = FALSE)
606496

@@ -670,3 +560,11 @@ combine_elements <- function(e1, e2) {
670560

671561
e1
672562
}
563+
564+
#' Reports whether x is a theme object
565+
#' @param x An object to test
566+
#' @export
567+
is.theme <- function(x) inherits(x, "theme")
568+
569+
#' @export
570+
print.theme <- function(x, ...) utils::str(x)

man/add_theme.Rd

Lines changed: 1 addition & 3 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)