99# ' about theme inheritance below.
1010# '
1111# ' @section Theme inheritance:
12- # ' Theme elements inherit properties from other theme elements heirarchically .
12+ # ' Theme elements inherit properties from other theme elements hierarchically .
1313# ' For example, `axis.title.x.bottom` inherits from `axis.title.x` which inherits
1414# ' from `axis.title`, which in turn inherits from `text`. All text elements inherit
1515# ' directly or indirectly from `text`; all lines inherit from
164164# ' `complete = TRUE` all elements will be set to inherit from blank
165165# ' elements.
166166# ' @param validate `TRUE` to run `validate_element()`, `FALSE` to bypass checks.
167+ # ' @param element_tree optional addition or modification to the element tree,
168+ # ' which specifies the inheritance relationship of the theme elements. The element
169+ # ' tree should be provided as a list of named element definitions created with
170+ # ' [`el_def()`]. See [`el_def()`] for more details.
167171# '
168172# ' @seealso
169173# ' [+.gg()] and \code{\link{\%+replace\%}},
@@ -358,9 +362,10 @@ theme <- function(line,
358362 strip.switch.pad.wrap ,
359363 ... ,
360364 complete = FALSE ,
361- validate = TRUE
365+ validate = TRUE ,
366+ element_tree = NULL
362367 ) {
363- elements <- find_args(... , complete = NULL , validate = NULL )
368+ elements <- find_args(... , complete = NULL , validate = NULL , element_tree = NULL )
364369
365370 if (! is.null(elements $ axis.ticks.margin )) {
366371 warning(" `axis.ticks.margin` is deprecated. Please set `margin` property " ,
@@ -392,11 +397,6 @@ theme <- function(line,
392397 elements $ legend.margin <- margin()
393398 }
394399
395- # Check that all elements have the correct class (element_text, unit, etc)
396- if (validate ) {
397- mapply(validate_element , elements , names(elements ))
398- }
399-
400400 # If complete theme set all non-blank elements to inherit from blanks
401401 if (complete ) {
402402 elements <- lapply(elements , function (el ) {
@@ -410,21 +410,69 @@ theme <- function(line,
410410 elements ,
411411 class = c(" theme" , " gg" ),
412412 complete = complete ,
413- validate = validate
413+ validate = validate ,
414+ element_tree = element_tree
414415 )
415416}
416417
417- is_theme_complete <- function (x ) isTRUE(attr(x , " complete" ))
418+ # check whether theme is complete
419+ is_theme_complete <- function (x ) isTRUE(attr(x , " complete" , exact = TRUE ))
418420
421+ # check whether theme should be validated
422+ is_theme_validate <- function (x ) {
423+ validate <- attr(x , " validate" , exact = TRUE )
424+ if (is.null(validate ))
425+ TRUE # we validate by default
426+ else
427+ isTRUE(validate )
428+ }
429+
430+ # obtain the full element tree from a theme,
431+ # substituting the defaults if needed
432+ complete_element_tree <- function (theme ) {
433+ element_tree <- attr(theme , " element_tree" , exact = TRUE )
434+
435+ # we fill in the element tree first from the current default theme,
436+ # and then from the internal element tree if necessary
437+ # this makes it easy for extension packages to provide modified
438+ # default element trees
439+ defaults(
440+ defaults(
441+ element_tree ,
442+ attr(theme_get(), " element_tree" , exact = TRUE )
443+ ),
444+ ggplot_global $ element_tree
445+ )
446+ }
419447
420448# Combine plot defaults with current theme to get complete theme for a plot
421449plot_theme <- function (x , default = theme_get()) {
422450 theme <- x $ theme
451+
452+ # apply theme defaults appropriately if needed
423453 if (is_theme_complete(theme )) {
424- theme
454+ # for complete themes, we fill in missing elements but don't do any element merging
455+ # can't use `defaults()` because it strips attributes
456+ missing <- setdiff(names(default ), names(theme ))
457+ theme [missing ] <- default [missing ]
425458 } else {
426- defaults(theme , default )
459+ # otherwise, we can just add the theme to the default theme
460+ theme <- default + theme
427461 }
462+
463+ # complete the element tree and save back to the theme
464+ element_tree <- complete_element_tree(theme )
465+ attr(theme , " element_tree" ) <- element_tree
466+
467+ # Check that all elements have the correct class (element_text, unit, etc)
468+ if (is_theme_validate(theme )) {
469+ mapply(
470+ validate_element , theme , names(theme ),
471+ MoreArgs = list (element_tree = element_tree )
472+ )
473+ }
474+
475+ theme
428476}
429477
430478# ' Modify properties of an element in a theme object
@@ -435,7 +483,7 @@ plot_theme <- function(x, default = theme_get()) {
435483# ' informative error messages.
436484# ' @keywords internal
437485add_theme <- function (t1 , t2 , t2name ) {
438- if (! is.theme (t2 )) {
486+ if (! is.list (t2 )) { # in various places in the code base, simple lists are used as themes
439487 stop(" Can't add `" , t2name , " ` to a theme object." ,
440488 call. = FALSE )
441489 }
@@ -457,6 +505,17 @@ add_theme <- function(t1, t2, t2name) {
457505 # make sure the "complete" attribute is set; this can be missing
458506 # when t1 is an empty list
459507 attr(t1 , " complete" ) <- is_theme_complete(t1 )
508+
509+ # Only validate if both themes should be validated
510+ attr(t1 , " validate" ) <-
511+ is_theme_validate(t1 ) && is_theme_validate(t2 )
512+
513+ # Merge element trees if provided
514+ attr(t1 , " element_tree" ) <- defaults(
515+ attr(t2 , " element_tree" , exact = TRUE ),
516+ attr(t1 , " element_tree" , exact = TRUE )
517+ )
518+
460519 t1
461520}
462521
@@ -484,30 +543,31 @@ add_theme <- function(t1, t2, t2name) {
484543calc_element <- function (element , theme , verbose = FALSE ) {
485544 if (verbose ) message(element , " --> " , appendLF = FALSE )
486545
487- # if theme is not complete, merge element with theme defaults,
488- # otherwise take it as is. This fills in theme defaults if no
489- # explicit theme is set for the plot.
490- if (! is_theme_complete(theme )) {
491- el_out <- merge_element(theme [[element ]], theme_get()[[element ]])
492- } else {
493- el_out <- theme [[element ]]
494- }
546+ el_out <- theme [[element ]]
495547
496548 # If result is element_blank, don't inherit anything from parents
497549 if (inherits(el_out , " element_blank" )) {
498550 if (verbose ) message(" element_blank (no inheritance)" )
499551 return (el_out )
500552 }
501553
554+ # Obtain the element tree and check that the element is in it
555+ # If not, try to retrieve the complete element tree. This is
556+ # needed for backwards compatibility and certain unit tests.
557+ element_tree <- attr(theme , " element_tree" , exact = TRUE )
558+ if (! element %in% names(element_tree )) {
559+ element_tree <- complete_element_tree(theme )
560+ }
561+
502562 # If the element is defined (and not just inherited), check that
503- # it is of the class specified in . element_tree
563+ # it is of the class specified in element_tree
504564 if (! is.null(el_out ) &&
505- ! inherits(el_out , ggplot_global $ element_tree [[element ]]$ class )) {
506- stop(element , " should have class " , ggplot_global $ element_tree [[element ]]$ class )
565+ ! inherits(el_out , element_tree [[element ]]$ class )) {
566+ stop(element , " should have class " , element_tree [[element ]]$ class )
507567 }
508568
509569 # Get the names of parents from the inheritance tree
510- pnames <- ggplot_global $ element_tree [[element ]]$ inherit
570+ pnames <- element_tree [[element ]]$ inherit
511571
512572 # If no parents, this is a "root" node. Just return this element.
513573 if (is.null(pnames )) {
0 commit comments