From 5feb9e4ece1ef1594251baf15667b2ace35e6876 Mon Sep 17 00:00:00 2001 From: nhamilton1980 Date: Sun, 22 Oct 2017 13:47:14 +1100 Subject: [PATCH 01/11] Dear Hadley / Winston & Other Devs, This is not a huge update, but it is meaningful in my opinion. I have completely re-worked the element tree, now based of R6 Class, which permits user-defined theme elements, for use in package extentions. Checks have been implemented so that these user-defined theme elements DO NOT CLASH with the set of protected theme elements, that is to say, those that are otherwise required for the base ggplot2 functionality. What this boils down to, is that the base ggplot2 element tree structure is protected, whilst still permitting users to extend this structure. With this update, people will be able to create their own extensions to ggplot2, and design their own theme elements for whatever reason. I have added this functionality, primarily so I can re-work some of the painful aspects of the ggtern package, which depends on dozens and dozens of new theme elements to function correctly. Previously, the .element_tree object was hidden and not-exported. There was no way to extend this tree, which was presumably to stop people meddling around with something that is so fundamental, with potentially catastrophic consequences to the plot routines. This is a totally valid, and understanable concern, however, by integrating a 'fixed' and 'variable' tree in the proposed R6 class that has been included within this update the following outcomes have resulted: 1. The now publicly-visible tree (accessed via get_elements() function) is the combination of these fixed and variable components. If the both the fixed and variable trees are 'sane', then the combination will also be sane. Sanity checks are conducted when the user attempts to add a new theme element. 2. Given the above, the base tree can be protected and preserved, innoculated from the variable tree. Any proposed new elements (ie those that the user wants to add to the 'variable' tree) can be checked against the 'fixed' tree first, to make sure such elements are not reserved. 3. New elements can be added easily via the exported add_element(k,v) function, where k is the 'key' (non-clashing name of the desired element to be added to the tree) and 'v' is the value (instance of the el_def(...)) theme element definition. 4. Ultimately, from the perspective of the end user nothing has changed, essentially only one tree exists, none of the previous functionality has been altered, the same methods of indexing (ie .element_tree[[elname]]) behave as previously used within ggplot2, however. This change will only be appreciated by package developers like myself. 5. The guts of the validate_element routine has been incorporated within the R6 class. Anyway, this update will enable ggtern to make a number of changes, reducing package fragility and complexity. I am sure other package developers will benefit too. Any questions, please email me. Regards, Nicholas E. Hamilton UNSW Sydney. n.hamilton@unsw.edu.au --- NAMESPACE | 4 + NEWS.md | 6 + R/theme-elements.r | 395 ++++++++++++++++++++++++++++++--------------- 3 files changed, 274 insertions(+), 131 deletions(-) diff --git a/NAMESPACE b/NAMESPACE index 4ee9a7336b..c7298dcbe4 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -201,6 +201,7 @@ export(StatSummaryBin) export(StatSummaryHex) export(StatUnique) export(StatYdensity) +export(add_element) export(aes) export(aes_) export(aes_all) @@ -253,6 +254,7 @@ export(draw_key_text) export(draw_key_vline) export(draw_key_vpath) export(dup_axis) +export(el_def) export(element_blank) export(element_grob) export(element_line) @@ -311,6 +313,7 @@ export(geom_text) export(geom_tile) export(geom_violin) export(geom_vline) +export(get_elements) export(gg_dep) export(ggplot) export(ggplotGrob) @@ -525,6 +528,7 @@ export(zeroGrob) import(grid) import(gtable) import(scales) +importFrom(R6,R6Class) importFrom(lazyeval,f_eval) importFrom(plyr,as.quoted) importFrom(plyr,defaults) diff --git a/NEWS.md b/NEWS.md index 885897ebc6..25f3ddba77 100644 --- a/NEWS.md +++ b/NEWS.md @@ -1,5 +1,11 @@ # ggplot2 2.2.1.9000 +* Completely re-worked element tree, now based of R6 Class, which permits user-defined theme + elements, for use in package extentions etc... Checks have been implemented so that + these user-defined theme elements do not clash with the set of protected theme elements, + that are otherwise required for the base functioning of ggplot2. + (@nhamilton1980) + * Fixed bug when setting strips to `element_blank()` (@thomasp85). * Strips gain margins on all sides by default. This means that to fully justify diff --git a/R/theme-elements.r b/R/theme-elements.r index d54c9921ee..9806d7ffd9 100644 --- a/R/theme-elements.r +++ b/R/theme-elements.r @@ -244,144 +244,277 @@ element_grob.element_line <- function(element, x = 0:1, y = 0:1, ) } - - -# Define an element's class and what other elements it inherits from -# -# @param class The name of class (like "element_line", "element_text", -# or the reserved "character", which means a character vector (not -# "character" class) -# @param inherit A vector of strings, naming the elements that this -# element inherits from. +#' Define an element's class and what other elements it inherits from +#' +#' @param class The name of class (like "element_line", "element_text", +#' or the reserved "character", which means a character vector (not +#' "character" class) +#' @param inherit A vector of strings, naming the elements that this +#' element inherits from. +#' @export el_def <- function(class = NULL, inherit = NULL, description = NULL) { - list(class = class, inherit = inherit, description = description) + structure( + list(class = class, inherit = inherit, description = description), + class=c('el_def','list') + ) } +#' R6 Element Tree Definition +#' +#' This is an R6 Class definition, to hold the element tree structure. +#' Contains a reserved default set for use by ggplot2, and contains the methods +#' that permit the user to add additional definitions, provided they do not conflict +#' with the reserved structure. The Final structure and indexing functions, ie, those that +#' permit equal substitution with previous ggplot2 methods, is produced via combining +#' the default and user-defined element trees. +#' @author Nicholas Hamilton, UNSW Sydney +#' @importFrom R6 R6Class +R6ElementTree = R6::R6Class('element_tree', + public = list( + initialize = function(){ + self$reset(FALSE) + }, + reset = function(verbose=TRUE){ + if(verbose && length(private$varElementsUser) > 0) + warning("Resetting to default element tree, user elements have been discarded.",call.=FALSE) + private$varLocked = TRUE + private$varElementsFixed = private$varElementsFixedDefault + private$varElementsUser = list() + }, + get = function(){ + self$get_elements() + }, + add = function(k,v){ + if(!inherits(v,'el_def')) + stop("value must inherit from 'el_def'",call.=FALSE) + if(!inherits(k,'character') || length(k) != 1) + stop("key must be 'character' of length 1",call.=FALSE) + if(self$is_locked()){ + if(k %in% names(private$varElementsFixed)) + stop(sprintf("The key '%s' is a protected name, reserved for base ggplot2.",k),call.=FALSE) + if(k %in% names(private$varElementsUser)) + warning(sprintf("The key '%s' exists in the user list, overwriting.",k),call.=FALSE) + private$varElementsUser[[ k ]] = v + }else{ + if(k %in% names(private$varElementsFixed)) + warning(sprintf("The key '%s' exists in the protected list, overwriting.",k),call.=FALSE) + if(k %in% names(private$varElementsUser)) + private$varElementsUser[[ k ]] = NULL + private$varElementsFixed[[ k ]] = v + } + invisible(self) + }, + at = function(k){ + self$get()[[k]] %||% stop(sprintf("'%s' is not a valid theme element name.",as.character(k)),call.=FALSE) + }, + + add_element = function(k,v){ + self$add(k,v) + }, + get_elements = function(){ + c(self$get_elements_fixed(), + self$get_elements_user()) + }, + get_elements_fixed = function(){ + private$varElementsFixed + }, + get_elements_user = function(){ + private$varElementsUser + }, + get_private = function(){ + self$`.__enclos_env__`$private + }, + is_locked = function(){ + private$varLocked + }, + print = function(){ + print(self$get()) + invisible(self) + }, + validate_element = function(el, elname){ + eldef <- self$at(elname) + + # NULL values for elements are OK + if (is.null(el)) return() + + if (eldef$class == "character") { + # Need to be a bit looser here since sometimes it's a string like "top" + # but sometimes its a vector like c(0,0) + if (!is.character(el) && !is.numeric(el)) + stop("Element ", elname, " must be a string or numeric vector.",call.=FALSE) + } else if (eldef$class == "margin") { + if (!is.unit(el) && length(el) == 4) + stop("Element ", elname, " must be a unit vector of length 4.",call.=FALSE) + } else if (!inherits(el, eldef$class) && !inherits(el, "element_blank")) { + stop("Element ", elname, " must be a ", eldef$class, " object.",call.=FALSE) + } + invisible() + } + ), + private = list( + lock = function(){ + private$varLocked = TRUE + invisible(self) + }, + unlock = function(){ + private$varLocked = FALSE + invisible(self) + }, + varElementsFixedDefault = list( + line = el_def("element_line"), + rect = el_def("element_rect"), + text = el_def("element_text"), + title = el_def("element_text", "text"), + axis.line = el_def("element_line", "line"), + axis.text = el_def("element_text", "text"), + axis.title = el_def("element_text", "title"), + axis.ticks = el_def("element_line", "line"), + legend.key.size = el_def("unit"), + panel.grid = el_def("element_line", "line"), + panel.grid.major = el_def("element_line", "panel.grid"), + panel.grid.minor = el_def("element_line", "panel.grid"), + strip.text = el_def("element_text", "text"), + + axis.line.x = el_def("element_line", "axis.line"), + axis.line.x.top = el_def("element_line", "axis.line.x"), + axis.line.x.bottom = el_def("element_line", "axis.line.x"), + axis.line.y = el_def("element_line", "axis.line"), + axis.line.y.left = el_def("element_line", "axis.line.y"), + axis.line.y.right = el_def("element_line", "axis.line.y"), + axis.text.x = el_def("element_text", "axis.text"), + axis.text.x.top = el_def("element_text", "axis.text.x"), + axis.text.x.bottom = el_def("element_text", "axis.text.x"), + axis.text.y = el_def("element_text", "axis.text"), + axis.text.y.left = el_def("element_text", "axis.text.y"), + axis.text.y.right = el_def("element_text", "axis.text.y"), + axis.ticks.length = el_def("unit"), + axis.ticks.x = el_def("element_line", "axis.ticks"), + axis.ticks.x.top = el_def("element_line", "axis.ticks.x"), + axis.ticks.x.bottom = el_def("element_line", "axis.ticks.x"), + axis.ticks.y = el_def("element_line", "axis.ticks"), + axis.ticks.y.left = el_def("element_line", "axis.ticks.y"), + axis.ticks.y.right = el_def("element_line", "axis.ticks.y"), + axis.title.x = el_def("element_text", "axis.title"), + axis.title.x.top = el_def("element_text", "axis.title.x"), + axis.title.x.bottom = el_def("element_text", "axis.title.x"), + axis.title.y = el_def("element_text", "axis.title"), + axis.title.y.left = el_def("element_text", "axis.title.y"), + axis.title.y.right = el_def("element_text", "axis.title.y"), + + legend.background = el_def("element_rect", "rect"), + legend.margin = el_def("margin"), + legend.spacing = el_def("unit"), + legend.spacing.x = el_def("unit", "legend.spacing"), + legend.spacing.y = el_def("unit", "legend.spacing"), + legend.key = el_def("element_rect", "rect"), + legend.key.height = el_def("unit", "legend.key.size"), + legend.key.width = el_def("unit", "legend.key.size"), + legend.text = el_def("element_text", "text"), + legend.text.align = el_def("character"), + legend.title = el_def("element_text", "title"), + legend.title.align = el_def("character"), + legend.position = el_def("character"), # Need to also accept numbers + legend.direction = el_def("character"), + legend.justification = el_def("character"), + legend.box = el_def("character"), + legend.box.just = el_def("character"), + legend.box.margin = el_def("margin"), + legend.box.background = el_def("element_rect", "rect"), + legend.box.spacing = el_def("unit"), + + panel.background = el_def("element_rect", "rect"), + panel.border = el_def("element_rect", "rect"), + panel.spacing = el_def("unit"), + panel.spacing.x = el_def("unit", "panel.spacing"), + panel.spacing.y = el_def("unit", "panel.spacing"), + panel.grid.major.x = el_def("element_line", "panel.grid.major"), + panel.grid.major.y = el_def("element_line", "panel.grid.major"), + panel.grid.minor.x = el_def("element_line", "panel.grid.minor"), + panel.grid.minor.y = el_def("element_line", "panel.grid.minor"), + panel.ontop = el_def("logical"), + + strip.background = el_def("element_rect", "rect"), + strip.text.x = el_def("element_text", "strip.text"), + strip.text.y = el_def("element_text", "strip.text"), + strip.placement = el_def("character"), + strip.placement.x = el_def("character", "strip.placement"), + strip.placement.y = el_def("character", "strip.placement"), + strip.switch.pad.grid = el_def("unit"), + strip.switch.pad.wrap = el_def("unit"), + + plot.background = el_def("element_rect", "rect"), + plot.title = el_def("element_text", "title"), + plot.subtitle = el_def("element_text", "title"), + plot.caption = el_def("element_text", "title"), + plot.margin = el_def("margin"), + + aspect.ratio = el_def("character") + ), + varElementsFixed = list( + ), + varElementsUser = list( + ), + varLocked = TRUE + ) +) +`[[.element_tree` = function(e,i){ e$get_elements()[[i]] } +`[.element_tree` = function(e,i){ e$get_elements()[i] } # This data structure represents the theme elements and the inheritance # among them. -.element_tree <- list( - line = el_def("element_line"), - rect = el_def("element_rect"), - text = el_def("element_text"), - title = el_def("element_text", "text"), - axis.line = el_def("element_line", "line"), - axis.text = el_def("element_text", "text"), - axis.title = el_def("element_text", "title"), - axis.ticks = el_def("element_line", "line"), - legend.key.size = el_def("unit"), - panel.grid = el_def("element_line", "line"), - panel.grid.major = el_def("element_line", "panel.grid"), - panel.grid.minor = el_def("element_line", "panel.grid"), - strip.text = el_def("element_text", "text"), - - axis.line.x = el_def("element_line", "axis.line"), - axis.line.x.top = el_def("element_line", "axis.line.x"), - axis.line.x.bottom = el_def("element_line", "axis.line.x"), - axis.line.y = el_def("element_line", "axis.line"), - axis.line.y.left = el_def("element_line", "axis.line.y"), - axis.line.y.right = el_def("element_line", "axis.line.y"), - axis.text.x = el_def("element_text", "axis.text"), - axis.text.x.top = el_def("element_text", "axis.text.x"), - axis.text.x.bottom = el_def("element_text", "axis.text.x"), - axis.text.y = el_def("element_text", "axis.text"), - axis.text.y.left = el_def("element_text", "axis.text.y"), - axis.text.y.right = el_def("element_text", "axis.text.y"), - axis.ticks.length = el_def("unit"), - axis.ticks.x = el_def("element_line", "axis.ticks"), - axis.ticks.x.top = el_def("element_line", "axis.ticks.x"), - axis.ticks.x.bottom = el_def("element_line", "axis.ticks.x"), - axis.ticks.y = el_def("element_line", "axis.ticks"), - axis.ticks.y.left = el_def("element_line", "axis.ticks.y"), - axis.ticks.y.right = el_def("element_line", "axis.ticks.y"), - axis.title.x = el_def("element_text", "axis.title"), - axis.title.x.top = el_def("element_text", "axis.title.x"), - axis.title.x.bottom = el_def("element_text", "axis.title.x"), - axis.title.y = el_def("element_text", "axis.title"), - axis.title.y.left = el_def("element_text", "axis.title.y"), - axis.title.y.right = el_def("element_text", "axis.title.y"), - - legend.background = el_def("element_rect", "rect"), - legend.margin = el_def("margin"), - legend.spacing = el_def("unit"), - legend.spacing.x = el_def("unit", "legend.spacing"), - legend.spacing.y = el_def("unit", "legend.spacing"), - legend.key = el_def("element_rect", "rect"), - legend.key.height = el_def("unit", "legend.key.size"), - legend.key.width = el_def("unit", "legend.key.size"), - legend.text = el_def("element_text", "text"), - legend.text.align = el_def("character"), - legend.title = el_def("element_text", "title"), - legend.title.align = el_def("character"), - legend.position = el_def("character"), # Need to also accept numbers - legend.direction = el_def("character"), - legend.justification = el_def("character"), - legend.box = el_def("character"), - legend.box.just = el_def("character"), - legend.box.margin = el_def("margin"), - legend.box.background = el_def("element_rect", "rect"), - legend.box.spacing = el_def("unit"), - - panel.background = el_def("element_rect", "rect"), - panel.border = el_def("element_rect", "rect"), - panel.spacing = el_def("unit"), - panel.spacing.x = el_def("unit", "panel.spacing"), - panel.spacing.y = el_def("unit", "panel.spacing"), - panel.grid.major.x = el_def("element_line", "panel.grid.major"), - panel.grid.major.y = el_def("element_line", "panel.grid.major"), - panel.grid.minor.x = el_def("element_line", "panel.grid.minor"), - panel.grid.minor.y = el_def("element_line", "panel.grid.minor"), - panel.ontop = el_def("logical"), - - strip.background = el_def("element_rect", "rect"), - strip.text.x = el_def("element_text", "strip.text"), - strip.text.y = el_def("element_text", "strip.text"), - strip.placement = el_def("character"), - strip.placement.x = el_def("character", "strip.placement"), - strip.placement.y = el_def("character", "strip.placement"), - strip.switch.pad.grid = el_def("unit"), - strip.switch.pad.wrap = el_def("unit"), - - plot.background = el_def("element_rect", "rect"), - plot.title = el_def("element_text", "title"), - plot.subtitle = el_def("element_text", "title"), - plot.caption = el_def("element_text", "title"), - plot.margin = el_def("margin"), - - aspect.ratio = el_def("character") -) - +.element_tree <- R6ElementTree$new() -# Check that an element object has the proper class -# -# Given an element object and the name of the element, this function -# checks it against the element inheritance tree to make sure the -# element is of the correct class -# -# It throws error if invalid, and returns invisible() if valid. -# -# @param el an element -# @param elname the name of the element -validate_element <- function(el, elname) { - eldef <- .element_tree[[elname]] +#' Add User Element to Element Tree +#' +#' \code{add_element} is a function that adds a user defined element, to the element tree, +#' if it does not conflict with the default element tree required for the +#' functioning of base \code{ggplot2}. +#' @param k the character index (key) for the element, +#' must be not present in the fixed (reserved) list of elements for the base ggplot2 package, +#' and an error will be thrown if either k is not a character of length 1, or k is protected. +#' @param v the \code{\link{el_def}} object (value) that the user wishes to create. +#' @examples +#' #1. Add a single element +#' add_element('mycustomtitle',el_def("element_text", "title")) +#' +#' #Without adding the element above, the following would otherwise throw an error, +#' #reporting 'mycustomtitle' is not a valid theme element +#' mytheme = theme(mycustomtitle = element_text()) +#' +#' #Try to add a reserved name, will throw an error, protecting functionality of base ggplot2 +#' \dontrun{add_element('plot.title',el_def('element_text','title'))} +#' +#' #2. Element Tree is based of R6 Class, Has been designed so that chaining is possible +#' add_element('A',el_def('element_text', 'title'))$add_element('B',el_def('element_rect','rect'))$add_element('C',el_def('element_line','line')) +#' +#' #Likewise, without adding the elements above, the following would otherwise throw the same error. +#' mytheme = theme(A = element_text(), +#' B = element_rect(), +#' C = element_line()) +#' +#' @author Nicholas Hamilton, UNSW Sydney +#' @export +#' @rdname element_tree +add_element = function(k,v){ .element_tree$add_element(k,v) } - if (is.null(eldef)) { - stop('"', elname, '" is not a valid theme element name.') - } +#' Get Complete List of Fixed and User-defined Elements +#' +#' \code{get_elements} is a function that returns the complete list of elements, +#' forming the fixed and user defined (if present) elements comprising the element tree. +#' @export +#' @rdname element_tree +get_elements = function(){ .element_tree$get() } - # NULL values for elements are OK - if (is.null(el)) return() - - if (eldef$class == "character") { - # Need to be a bit looser here since sometimes it's a string like "top" - # but sometimes its a vector like c(0,0) - if (!is.character(el) && !is.numeric(el)) - stop("Element ", elname, " must be a string or numeric vector.") - } else if (eldef$class == "margin") { - if (!is.unit(el) && length(el) == 4) - stop("Element ", elname, " must be a unit vector of length 4.") - } else if (!inherits(el, eldef$class) && !inherits(el, "element_blank")) { - stop("Element ", elname, " must be a ", eldef$class, " object.") - } - invisible() +#' Check that an element object has the proper class +#' +#' Given an element object and the name of the element, this function +#' checks it against the element inheritance tree to make sure the +#' element is of the correct class +#' +#' It throws error if invalid, and returns invisible() if valid. +#' +#' @param el an element +#' @param elname the name of the element +validate_element <- function(el, elname) { + .element_tree$validate_element(el,elname) } From 85ac94c77ee8631f5e81d840cc151c08a1d644ef Mon Sep 17 00:00:00 2001 From: nhamilton1980 Date: Sun, 22 Oct 2017 17:28:52 +1100 Subject: [PATCH 02/11] Corrected R6 imports in DESCRIPTION. --- DESCRIPTION | 3 ++- R/theme-elements.r | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/DESCRIPTION b/DESCRIPTION index d220535b68..682830b70f 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -24,7 +24,8 @@ Imports: stats, tibble, viridisLite, - withr + withr, + R6 Suggests: covr, dplyr, diff --git a/R/theme-elements.r b/R/theme-elements.r index 9806d7ffd9..99a976c0d5 100644 --- a/R/theme-elements.r +++ b/R/theme-elements.r @@ -269,7 +269,7 @@ el_def <- function(class = NULL, inherit = NULL, description = NULL) { #' the default and user-defined element trees. #' @author Nicholas Hamilton, UNSW Sydney #' @importFrom R6 R6Class -R6ElementTree = R6::R6Class('element_tree', +R6ElementTree = R6Class('element_tree', public = list( initialize = function(){ self$reset(FALSE) From 52b68f206b0243ec495d70341502b00e7733c20e Mon Sep 17 00:00:00 2001 From: nhamilton1980 Date: Mon, 23 Oct 2017 08:56:53 +1100 Subject: [PATCH 03/11] New Documentation Rd Files Included --- man/R6ElementTree.Rd | 23 +++++++++++++++++++ man/el_def.Rd | 20 +++++++++++++++++ man/element_tree.Rd | 50 +++++++++++++++++++++++++++++++++++++++++ man/validate_element.Rd | 22 ++++++++++++++++++ 4 files changed, 115 insertions(+) create mode 100644 man/R6ElementTree.Rd create mode 100644 man/el_def.Rd create mode 100644 man/element_tree.Rd create mode 100644 man/validate_element.Rd diff --git a/man/R6ElementTree.Rd b/man/R6ElementTree.Rd new file mode 100644 index 0000000000..470ab3b89b --- /dev/null +++ b/man/R6ElementTree.Rd @@ -0,0 +1,23 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/theme-elements.r +\docType{data} +\name{R6ElementTree} +\alias{R6ElementTree} +\title{R6 Element Tree Definition} +\format{An object of class \code{R6ClassGenerator} of length 24.} +\usage{ +R6ElementTree +} +\description{ +This is an R6 Class definition, to hold the element tree structure. +Contains a reserved default set for use by ggplot2, and contains the methods +that permit the user to add additional definitions, provided they do not conflict +with the reserved structure. The Final structure and indexing functions, ie, those that +permit equal substitution with previous ggplot2 methods, is produced via combining +the default and user-defined element trees. +} +\author{ +Nicholas Hamilton, UNSW Sydney +} +\keyword{datasets} + diff --git a/man/el_def.Rd b/man/el_def.Rd new file mode 100644 index 0000000000..d10b0a2e04 --- /dev/null +++ b/man/el_def.Rd @@ -0,0 +1,20 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/theme-elements.r +\name{el_def} +\alias{el_def} +\title{Define an element's class and what other elements it inherits from} +\usage{ +el_def(class = NULL, inherit = NULL, description = NULL) +} +\arguments{ +\item{class}{The name of class (like "element_line", "element_text", +or the reserved "character", which means a character vector (not +"character" class)} + +\item{inherit}{A vector of strings, naming the elements that this +element inherits from.} +} +\description{ +Define an element's class and what other elements it inherits from +} + diff --git a/man/element_tree.Rd b/man/element_tree.Rd new file mode 100644 index 0000000000..fc1ca5bec8 --- /dev/null +++ b/man/element_tree.Rd @@ -0,0 +1,50 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/theme-elements.r +\name{add_element} +\alias{add_element} +\alias{get_elements} +\title{Add User Element to Element Tree} +\usage{ +add_element(k, v) + +get_elements() +} +\arguments{ +\item{k}{the character index (key) for the element, +must be not present in the fixed (reserved) list of elements for the base ggplot2 package, +and an error will be thrown if either k is not a character of length 1, or k is protected.} + +\item{v}{the \code{\link{el_def}} object (value) that the user wishes to create.} +} +\description{ +\code{add_element} is a function that adds a user defined element, to the element tree, +if it does not conflict with the default element tree required for the +functioning of base \code{ggplot2}. + +\code{get_elements} is a function that returns the complete list of elements, +forming the fixed and user defined (if present) elements comprising the element tree. +} +\examples{ +#1. Add a single element +add_element('mycustomtitle',el_def("element_text", "title")) + +#Without adding the element above, the following would otherwise throw an error, +#reporting 'mycustomtitle' is not a valid theme element +mytheme = theme(mycustomtitle = element_text()) + +#Try to add a reserved name, will throw an error, protecting functionality of base ggplot2 +\dontrun{add_element('plot.title',el_def('element_text','title'))} + +#2. Element Tree is based of R6 Class, Has been designed so that chaining is possible +add_element('A',el_def('element_text', 'title'))$add_element('B',el_def('element_rect','rect'))$add_element('C',el_def('element_line','line')) + +#Likewise, without adding the elements above, the following would otherwise throw the same error. +mytheme = theme(A = element_text(), + B = element_rect(), + C = element_line()) + +} +\author{ +Nicholas Hamilton, UNSW Sydney +} + diff --git a/man/validate_element.Rd b/man/validate_element.Rd new file mode 100644 index 0000000000..b9245d0c44 --- /dev/null +++ b/man/validate_element.Rd @@ -0,0 +1,22 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/theme-elements.r +\name{validate_element} +\alias{validate_element} +\title{Check that an element object has the proper class} +\usage{ +validate_element(el, elname) +} +\arguments{ +\item{el}{an element} + +\item{elname}{the name of the element} +} +\description{ +Given an element object and the name of the element, this function +checks it against the element inheritance tree to make sure the +element is of the correct class +} +\details{ +It throws error if invalid, and returns invisible() if valid. +} + From 814420870f70d299cdc133797476883b92ad93bb Mon Sep 17 00:00:00 2001 From: nhamilton1980 Date: Mon, 23 Oct 2017 09:10:43 +1100 Subject: [PATCH 04/11] Fixed missing parameter in el_def function --- R/theme-elements.r | 1 + man/el_def.Rd | 2 ++ 2 files changed, 3 insertions(+) diff --git a/R/theme-elements.r b/R/theme-elements.r index 99a976c0d5..e21d397c0e 100644 --- a/R/theme-elements.r +++ b/R/theme-elements.r @@ -251,6 +251,7 @@ element_grob.element_line <- function(element, x = 0:1, y = 0:1, #' "character" class) #' @param inherit A vector of strings, naming the elements that this #' element inherits from. +#' @param description The optional description of the particular element. #' @export el_def <- function(class = NULL, inherit = NULL, description = NULL) { structure( diff --git a/man/el_def.Rd b/man/el_def.Rd index d10b0a2e04..017debab43 100644 --- a/man/el_def.Rd +++ b/man/el_def.Rd @@ -13,6 +13,8 @@ or the reserved "character", which means a character vector (not \item{inherit}{A vector of strings, naming the elements that this element inherits from.} + +\item{description}{The optional description of the particular element.} } \description{ Define an element's class and what other elements it inherits from From 4a8d9c0e55e8275c4bd196dcf5cb1e107eca0b88 Mon Sep 17 00:00:00 2001 From: nhamilton1980 Date: Mon, 23 Oct 2017 09:15:15 +1100 Subject: [PATCH 05/11] Removed long example exceeding 100 characters --- R/theme-elements.r | 10 +--------- man/element_tree.Rd | 10 +--------- 2 files changed, 2 insertions(+), 18 deletions(-) diff --git a/R/theme-elements.r b/R/theme-elements.r index e21d397c0e..362f0eeb69 100644 --- a/R/theme-elements.r +++ b/R/theme-elements.r @@ -475,7 +475,7 @@ R6ElementTree = R6Class('element_tree', #' and an error will be thrown if either k is not a character of length 1, or k is protected. #' @param v the \code{\link{el_def}} object (value) that the user wishes to create. #' @examples -#' #1. Add a single element +#' #Add a single element #' add_element('mycustomtitle',el_def("element_text", "title")) #' #' #Without adding the element above, the following would otherwise throw an error, @@ -485,14 +485,6 @@ R6ElementTree = R6Class('element_tree', #' #Try to add a reserved name, will throw an error, protecting functionality of base ggplot2 #' \dontrun{add_element('plot.title',el_def('element_text','title'))} #' -#' #2. Element Tree is based of R6 Class, Has been designed so that chaining is possible -#' add_element('A',el_def('element_text', 'title'))$add_element('B',el_def('element_rect','rect'))$add_element('C',el_def('element_line','line')) -#' -#' #Likewise, without adding the elements above, the following would otherwise throw the same error. -#' mytheme = theme(A = element_text(), -#' B = element_rect(), -#' C = element_line()) -#' #' @author Nicholas Hamilton, UNSW Sydney #' @export #' @rdname element_tree diff --git a/man/element_tree.Rd b/man/element_tree.Rd index fc1ca5bec8..5c94c35f70 100644 --- a/man/element_tree.Rd +++ b/man/element_tree.Rd @@ -25,7 +25,7 @@ functioning of base \code{ggplot2}. forming the fixed and user defined (if present) elements comprising the element tree. } \examples{ -#1. Add a single element +#Add a single element add_element('mycustomtitle',el_def("element_text", "title")) #Without adding the element above, the following would otherwise throw an error, @@ -35,14 +35,6 @@ mytheme = theme(mycustomtitle = element_text()) #Try to add a reserved name, will throw an error, protecting functionality of base ggplot2 \dontrun{add_element('plot.title',el_def('element_text','title'))} -#2. Element Tree is based of R6 Class, Has been designed so that chaining is possible -add_element('A',el_def('element_text', 'title'))$add_element('B',el_def('element_rect','rect'))$add_element('C',el_def('element_line','line')) - -#Likewise, without adding the elements above, the following would otherwise throw the same error. -mytheme = theme(A = element_text(), - B = element_rect(), - C = element_line()) - } \author{ Nicholas Hamilton, UNSW Sydney From ede7da2aa27708e9a55f2d0252bb48df5182c61c Mon Sep 17 00:00:00 2001 From: nhamilton1980 Date: Sun, 12 Nov 2017 12:38:07 +1100 Subject: [PATCH 06/11] Fix Description Conflict --- DESCRIPTION | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/DESCRIPTION b/DESCRIPTION index 682830b70f..e5ef274f32 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -24,8 +24,8 @@ Imports: stats, tibble, viridisLite, - withr, R6 + withr (>= 2.0.0) Suggests: covr, dplyr, @@ -38,6 +38,7 @@ Suggests: maptools, mgcv, multcomp, + munsell, nlme, testthat (>= 0.11.0), vdiffr, @@ -80,6 +81,7 @@ Collate: 'autolayer.r' 'autoplot.r' 'axis-secondary.R' + 'backports.R' 'bench.r' 'bin.R' 'coord-.r' @@ -233,6 +235,6 @@ Collate: 'zxx.r' 'zzz.r' VignetteBuilder: knitr -RoxygenNote: 6.0.1 +RoxygenNote: 6.0.1.9000 Roxygen: list(markdown = TRUE) Encoding: UTF-8 From eaade1d22893f61f8d118be21d4595db565d4b06 Mon Sep 17 00:00:00 2001 From: nhamilton1980 Date: Sun, 12 Nov 2017 12:40:06 +1100 Subject: [PATCH 07/11] Fix Description Conflict --- DESCRIPTION | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/DESCRIPTION b/DESCRIPTION index e5ef274f32..b5d56832fb 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -24,7 +24,6 @@ Imports: stats, tibble, viridisLite, - R6 withr (>= 2.0.0) Suggests: covr, @@ -48,7 +47,8 @@ Suggests: rpart, rmarkdown, sf (>= 0.3-4), - svglite (>= 1.2.0.9001) + svglite (>= 1.2.0.9001), + R6 Remotes: hadley/scales, hadley/svglite, From 1fa09712a903624c35ec5640a86c5c2769f06fae Mon Sep 17 00:00:00 2001 From: nhamilton1980 Date: Sun, 12 Nov 2017 12:42:24 +1100 Subject: [PATCH 08/11] Fix News Conflict --- NEWS | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/NEWS b/NEWS index 12746fc39f..93359b5650 100644 --- a/NEWS +++ b/NEWS @@ -57,6 +57,12 @@ NEW FEATURES https://github.com/hadley/ggplot2/wiki/labeller#writing-new-labellers (@stefanedwards, #910) +* Completely re-worked element tree, now based of R6 Class, which permits user-defined theme + elements, for use in package extentions etc... Checks have been implemented so that + these user-defined theme elements do not clash with the set of protected theme elements, + that are otherwise required for the base functioning of ggplot2. + (@nhamilton1980) + BUG FIXES AND MINOR IMPROVEMENTS * `aes()` no more treats variables like `a..x..b` as a calculated aesthetic. From f027eac52c551d59b5cce7090500b81520e6a805 Mon Sep 17 00:00:00 2001 From: nhamilton1980 Date: Sun, 12 Nov 2017 12:47:32 +1100 Subject: [PATCH 09/11] Fix Conflict in News.md --- NEWS | 6 -- NEWS.md | 323 ++++++++++++++++++++++++++++++++++---------------------- 2 files changed, 196 insertions(+), 133 deletions(-) diff --git a/NEWS b/NEWS index 93359b5650..12746fc39f 100644 --- a/NEWS +++ b/NEWS @@ -57,12 +57,6 @@ NEW FEATURES https://github.com/hadley/ggplot2/wiki/labeller#writing-new-labellers (@stefanedwards, #910) -* Completely re-worked element tree, now based of R6 Class, which permits user-defined theme - elements, for use in package extentions etc... Checks have been implemented so that - these user-defined theme elements do not clash with the set of protected theme elements, - that are otherwise required for the base functioning of ggplot2. - (@nhamilton1980) - BUG FIXES AND MINOR IMPROVEMENTS * `aes()` no more treats variables like `a..x..b` as a calculated aesthetic. diff --git a/NEWS.md b/NEWS.md index 25f3ddba77..11fcb7548e 100644 --- a/NEWS.md +++ b/NEWS.md @@ -1,12 +1,75 @@ # ggplot2 2.2.1.9000 -* Completely re-worked element tree, now based of R6 Class, which permits user-defined theme - elements, for use in package extentions etc... Checks have been implemented so that - these user-defined theme elements do not clash with the set of protected theme elements, - that are otherwise required for the base functioning of ggplot2. - (@nhamilton1980) +## New features -* Fixed bug when setting strips to `element_blank()` (@thomasp85). +* ggplot2 now works on R 3.1 onwards, and uses the + [vdiffr](https://github.com/lionel-/vdiffr) package for visual testing. + +* Alternative syntax for calculated aesthetics. Instead of using + `aes(y = ..count..)` you can (and should!) now use `aes(y = calc(count))`. + `calc()` is a real function with documentation which hopefully will make + this part of ggplot2 less confusing. It's particularly nice if for more complex + calculation because you only need to specify once: + `aes(y = calc(count / max(count))) (#2059) + +* Boxplot position is now controlled by `position_dodge2()`, which can also be + used for bars and rectangles. `position_dodge2()` compares the `xmin` and + `xmax` values of each element to determine which ones overlap, and dodges them + accordingly. This makes it possible to dodge box plots created with + `geom_boxplot(varwidth = TRUE)`. The `padding` parameter adds a small amount + of padding between elements (@karawoo, #2143). A `reverse` parameter allows + you to reverse the placement order of bars and boxes (@karawoo, #2171). + +* The `expand` argument for `scale_*_continuous()` and `scale_*_discrete()` + now accepts separate expansion values for the lower and upper range + limits. The expansion limits can be specified using the convenience + function `expand_scale()`. + + Separate expansion limits may be useful for bar charts, e.g. if one + wants to have the bottom of the bars being flush with the x axis but + still leave some (automatically calculated amount of) space above them: + + ```R + ggplot(mtcars) + + geom_bar(aes(x = factor(cyl))) + + scale_y_continuous(expand = expand_scale(mult = c(0, .1))) + ``` + + It can also be useful for line charts, e.g. for counts over time, + where one wants to have a ’hard’ lower limit of y = 0 but leave the + upper limit unspecified (and perhaps differing between panels), + but with some extra space above the highest point on the line. + (With symmetrical limits, the extra space above the highest point + could in some cases cause the lower limit to be negative.) + + The old syntax for the `expand` argument will of course continue + to work. (@huftis, #1669) + +* Added `stat_qq_line()` to make it easy to add a simple line to a Q-Q plot. This + line makes it easier to judge the fit of the theoretical distribution + (@nicksolomon). + +### Scales + +* New `scale_colour_viridis_c()`/`scale_fill_viridis_c()` (continuous) and + `scale_colour_viridis_d()`/`scale_fill_viridis_d()` (discrete) make it + easy to use Viridis colour scales. + (@karawoo, #1526). + +* Default colour maps for continuous data are now controlled by global options + `ggplot2.continuous.colour` and `ggplot2.continuous.fill`. These can be + set to `"gradient"` (the default) or `"viridis"` (@karawoo). + +* Improved support for mapping date/time variables to `alpha`, `size`, `colour`, + and `fill` aesthetics, including `date_breaks` and `date_labels` arguments + (@karawoo, #1526), and new `scale_alpha()` variants (@karawoo, #1526). + +* Improved support for ordered factors. Ordered factors throw a warning when + mapped to shape (unordered factors do not). Ordered factors do not throw + warnings when mapped to size or alpha (unordered factors do). Viridis used as + default colour and fill scale for ordered factors (@karawoo, #1526). + +### Margins * Strips gain margins on all sides by default. This means that to fully justify text to the edge of a strip, you will need to also set the margins to 0 @@ -19,174 +82,180 @@ text, meaning that in y facets the strip text can be placed at either end of the strip using `hjust` (@karawoo). -* Added `stat_qq_line()` to make it easy to add a simple line to a Q-Q plot. This - line makes it easier to judge the fit of the theoretical distribution (@nicksolomon). +* Legend titles and labels get a little extra space around them. Legend titles + will no longer overlap the legend at large font sizes (@karawoo, #1881). -* The `ggsave()` DPI parameter now supports 3 string options: "retina" (320 - DPI), "print" (300 DPI), and "screen" (72 DPI) (@foo-bar-baz-qux, #2156). +### sf -* `position_dodge2()` now has a `reverse` parameter that allows you to reverse - the placement order of bars and boxes (@karawoo, #2171). +ggplot2 now has full support for sf with `geom_sf()` and `coord_sf()`: -* Box plot position is now controlled by `position_dodge2()`, which can also be - used for bars and rectangles. `position_dodge2()` compares the `xmin` and - `xmax` values of each element to determine which ones overlap, and dodges them - accordingly. This makes it possible to dodge box plots created with - `geom_boxplot(varwidth = TRUE)`. The `padding` parameter adds a small amount - of padding between elements (@karawoo, #2143). +```R +nc <- sf::st_read(system.file("shape/nc.shp", package = "sf"), quiet = TRUE) +ggplot(nc) + + geom_sf(aes(fill = AREA)) +``` +It supports all simple features, automatically aligns CRS across layer, sets +up correct aspect ratio, and draws a graticule. -* `fortify()` gains a method for tbls (@karawoo, #2218) +## Extension points -* `stat_summary_bin()` now understands the `breaks` parameter (@karawoo, #2214) +* New `autolayer()` S3 generic (@mitchelloharawild, #1974). This is similar + to `autoplot()` but produces layers rather than complete plots. -* `coord_trans()` now generates a warning when a transformation results in x or y - values being non-finite (@foo-bar-baz-qux, #2147). - -* Legend titles and labels get a little extra space around them. Legend titles - will no longer overlap the legend at large font sizes (@karawoo, #1881). +* Custom objects can now be added using `+` if a `ggplot_add` method has been + defined for the class of the object (@thomasp85). -* Ordered factors now behave differently from unordered factors in some cases. - Ordered factors throw a warning when mapped to shape (unordered factors do - not). Ordered factors do not throw warnings when mapped to size or alpha - (unordered factors do). Viridis is the default colour and fill scale for - ordered factors (@karawoo, #1526). +* `scale_type()` generic is now exported and documented. Use this if you + want to extend ggplot2 to work with a new type of vector. -* The `show.legend` parameter now accepts a named logical vector to hide/show - only some aesthetics in the legend (@tutuchan, #1798) +* Theme elements can now be subclassed. Add a `merge_element` method to control + how properties are inherited from parent element. Add `element_grob` method + to define how elements are rendered into grobs (@thomasp85, #1981). -* Default colour maps for continuous data are controlled by global options - `ggplot2.continuous.colour` and `ggplot2.continuous.fill`, which can be set to - either `"gradient"` or `"viridis"` (@karawoo). +* Coords have gained new extension mechanisms. + + If you have an existing coord extension you will need to revise the + specification of the `train()` method. It is now called `setup_panel_params()` + (better reflecting what it actually does) and now has arguments + `scale_x`, and `scale_y` (the x and y scales respectively) and + `param`, a list of plot specific parameters generated by `setup_params()`. -* Adds built-in support for `viridis` and related colour maps. Use the functions - `scale_colour_viridis_c()`/`scale_fill_viridis_c()` for continuous data and - `scale_colour_viridis_d()`/`scale_fill_viridis_d()` for discrete data - (@karawoo, #1526). +* What was formerly called `scale_details` (in coords), `panel_ranges` + (in layout) and `panel_scales` (in geoms) are now consistently called + `panel_params` (#1311). These are parameters of the Coord that vary from + panel to panel. -* Updated datetime scales for `alpha`, `size`, `colour`, and `fill` can take - `date_breaks` and `date_labels` arguments (@karawoo, #1526). +## Minor bug fixes and improvements -* `scale_alpha()` gains date and date-time variants (@karawoo, #1526). +### Facetting -* Axes positioned on the top and to the right can now customize their ticks and - lines separately (@thomasp85, #1899) +* `facet_grid()` gives a more informative error message if you try to use + a variable in both rows and cols (#1928) -* `geom_segment` now also takes a `linejoin` parameter. This allows more control over the appearance of the segments, which is especially useful for plotting thick arrows (@Ax3man, #774). +* `facet_wrap()` and `facet_grid()` both give better error messages if you + attempt to use an unsupported coord with free scales (#2049) -* Theme elements can now be subclassed. Add a `merge_element` method to control - how properties are inherited from parent element. Add `element_grob` method - to define how elements are rendered into grobs (@thomasp85, #1981). +* `label_parsed()` works once again (#2279) -* Theme functions now have the optional parameters `base_line_size` and - `base_rect_size` to control the default sizes of line and rectangle elements - (@karawoo, #2176). +* You can now style the background of horizontal and vertical strips + independently with `strip.background.x` and `strip.background.y` + theme settings (#2249). -* Fixed bug in `coord_polar` that prevented secondary axis ticks and labels - from being drawn (@dylan-stark, #2072) +### Scales -* Use `rel()` to set line widths in theme defaults (@baptiste). +* `discrete_scale()` documentation updated to match `continuous_scale()` + (@alistaire47, #2052). -* `geom_density` drops groups with fewer than two data points and throws a - warning. For groups with two data points, the density values are now - calculated with `stats::density` (@karawoo, #2127). +* `scale_identity()` once again produces legends by default (#2112). -* `geom_smooth` now orders by the `x` aesthetic, making it easier to pass - pre-computed values without manual ordering (@izahn, #2028). +* Fix bug in secondary axis that would lead to incorrectly placed ticks with + strong transforms (@thomasp85, #1992). -* Fixed bug in `coord_polar` that prevented moving the radius axis - to the right (@thomasp85, #2005). +* Missing line types now reliably generate missing lines (with standard + warning) (#2206). -* `discrete_scale` documentation updated to match functionality and - `continuous_scale` (@alistaire47, #2052). +* Legends no longer try and use set aesthetics that are not length one (#1932). -* `geom_smooth()` now knows it has `ymin` and `ymax` aesthetics (#1939). +### Layers -* Automatic visual unit tests with vdiffr. +* In all layers that use it, `linemitre` now defaults to 10 (instead of 1) + to better match base R. -* Layers no longer warn about unknown aesthetics who's value is set to - `NULL` (overriding a set aesthetic in the plot) (#1909). +* `geom_density()` drops groups with fewer than two data points and throws a + warning. For groups with two data points, the density values are now + calculated with `stats::density` (@karawoo, #2127). -* `scale_type()` generic is now exported and documented. Use this if you - want to extend ggplot2 to work with a new type of vector (#) +* `geom_segment()` now also takes a `linejoin` parameter. This allows more + control over the appearance of the segments, which is especially useful for + plotting thick arrows (@Ax3man, #774). + +* `geom_smooth()` now reports the formula used when `method = "auto"` + (@davharris #1951). It also orders by the `x` aesthetic, making it easier + to pass pre-computed values without manual ordering (@izahn, #2028). + It also now knows it has `ymin` and `ymax` aesthetics (#1939). * `position_dodge()` gains an `preserve` argument that allows you to control whether the `total` width at each `x` value is preserved (the current default), or ensure that the width of a `single` element is preserved (what many people want) (#1935). -* `stat_bin` now accepts functions for `binwidth`. This allows better binning when faceting along variables with different ranges (@botanize). +* `position_jitter()` gains a `seed` argument that allows specifying a random + seed for reproducible jittering (#1996, @krlmlr). -* Legends no longer try and use set aesthetics that are not length one - (fixes #1932). +* `stat_density()` has better behaviour if all groups are dropped because they + are too small (#2282). -* Added `autolayer()` S3 generic (@mitchelloharawild, #1974). +* `stat_summary_bin()` now understands the `breaks` parameter (@karawoo, #2214) -* Fix warning when using the `weight` aesthetic with `stat_bin()` (through - `geom_histogram()` in particular) (@jiho, #1921). - -* `geom_smooth`'s message for `method="auto"` now reports the formula used, - in addition to the name of the smoothing function (@davharris #1951). - -* The `expand` argument for `scale_*_continuous()` and `scale_*_discrete()` - now accepts separate expansion values for the lower and upper range - limits. The expansion limits can be specified using the convenience - function `expand_scale()`. - - Separate expansion limits may be useful for bar charts, e.g. if one - wants to have the bottom of the bars being flush with the x axis but - still leave some (automatically calculated amount of) space above them: - - ```R - ggplot(mtcars) + - geom_bar(aes(x = factor(cyl))) + - scale_y_continuous(expand = expand_scale(mult = c(0, .1))) - ``` - - It can also be useful for line charts, e.g. for counts over time, - where one wants to have a ’hard’ lower limit of y = 0 but leave the - upper limit unspecified (and perhaps differing between panels), - but with some extra space above the highest point on the line. - (With symmetrical limits, the extra space above the highest point - could in some cases cause the lower limit to be negative.) +* `stat_bin()` now accepts functions for `binwidth`. This allows better binning + when faceting along variables with different ranges (@botanize). + +* `stat_bin()` / `geom_histogram()` no longer incorrectly when using the + `weight` aesthetic with (@jiho, #1921). + +* `update_geom_defaults()` and `update_stat_defaults()` to allow American + spelling of aesthetic parameters (@foo-bar-baz-qux, #2299). + +* The `show.legend` parameter now accepts a named logical vector to hide/show + only some aesthetics in the legend (@tutuchan, #1798) + +* Layers no longer warn about unknown aesthetics with value `NULL` (#1909). + +* Restored correct scaling for computed variable `ndensity` when binning (@timgoodman, #2324) + +### Coords + +* Like scales, coordinate systems now give you a message when you're + replacing any existing coordiante system (#2264) + +* `coord_polar()` now draws secondary axis ticks and labels + (@dylan-stark, #2072), and can draw the radius axis on the right + (@thomasp85, #2005). + +* `coord_trans()` now generates a warning when a transformation generates + non-finite values (@foo-bar-baz-qux, #2147). - The old syntax for the `expand` argument will of course continue - to work. (@huftis, #1669) +### Themes -* `print.ggplot()` now returns the original ggplot object, instead of the output from `ggplot_build()`. Also, the object returned from `ggplot_build()` now has the class `"ggplot_built"`. (#2034) +* Complete themes now always override all elements of the default theme + (@has2k1, #2058, #2079) -* Added new functions `summarise_layout()`, `summarise_coord()`, `summarise_layers()`, which provide summaries of the layout, coordinate systems, and layers, of a built ggplot object. (#2034) +* Fixed bug when setting strips to `element_blank()` (@thomasp85). -* `ggproto()` produces objects with class `c("ggproto", "gg")`. This was added so that when layers, scales, or other ggproto objects are added together, an informative error message is raised (@jrnold, #2056). +* Axes positioned on the top and to the right can now customize their ticks and + lines separately (@thomasp85, #1899) -* `position_jitter()` gains a `seed` argument that allows specifying a random seed for reproducible jittering (#1996, @krlmlr). +* Themes gain parameters `base_line_size` and `base_rect_size` which control + the default sizes of line and rectangle elements (@karawoo, #2176). +* Default themes use `rel()` to set line widths (@baptiste). -### sf +* Completely re-worked element tree, now based of R6 Class, which permits user-defined theme + elements, for use in package extentions etc... Checks have been implemented so that + these user-defined theme elements do not clash with the set of protected theme elements, + that are otherwise required for the base functioning of ggplot2. + (@nhamilton1980) -ggplot2 now has full support for sf with `geom_sf()` and `coord_sf()`: +### Other -```R -nc <- sf::st_read(system.file("shape/nc.shp", package = "sf"), quiet = TRUE) -ggplot(nc) + - geom_sf(aes(fill = AREA)) -``` -It supports all simple features, automatically aligns CRS across layer, sets -up correct aspect ratio, and draws a graticule. +* `fortify()` gains a method for tbls (@karawoo, #2218) -### Coordinate extensions +* `ggproto()` produces objects with class `c("ggproto", "gg")`, allowing for + a more informative error message when adding layers, scales, or other ggproto + objects (@jrnold, #2056). -* Coords have gained new extension mechanisms. - - If you have an existing coord extension you will need to revise the - specification of the `train()` method. It is now called `setup_panel_params()` - (better reflecting what it actually does) and now has arguments - `scale_x`, and `scale_y` (the x and y scales respectively) and - `param`, a list of plot specific parameters generated by `setup_params()`. +* `ggsave()`'s DPI argument now supports 3 string options: "retina" (320 + DPI), "print" (300 DPI), and "screen" (72 DPI) (@foo-bar-baz-qux, #2156). -* What was formerly called `scale_details` (in coords), `panel_ranges` - (in layout) and `panel_scales` (in geoms) are now consistently called - `panel_params` (#1311). These are parameters of the Coord that vary from - panel to panel. +* `print.ggplot()` now returns the original ggplot object, instead of the + output from `ggplot_build()`. Also, the object returned from + `ggplot_build()` now has the class `"ggplot_built"`. (#2034) + +* `map_data()` now works when purrr is loaded (tidyverse#66) + +* New functions `summarise_layout()`, `summarise_coord()`, and `summarise_layers()` + summarise the layout, coordinate systems, and layers, of a built ggplot object + (#2034, @wch). This provides a tested API that (e.g.) shiny can depend on. # ggplot2 2.2.1 From fbad1547caa7b240f22d802ee8a1de2ed8cba626 Mon Sep 17 00:00:00 2001 From: nhamilton1980 Date: Sun, 12 Nov 2017 12:53:50 +1100 Subject: [PATCH 10/11] Update to Fix Conflict --- R/theme-elements.r | 173 +++++++++++++++++++++++---------------------- 1 file changed, 87 insertions(+), 86 deletions(-) diff --git a/R/theme-elements.r b/R/theme-elements.r index 362f0eeb69..7c4ea80dab 100644 --- a/R/theme-elements.r +++ b/R/theme-elements.r @@ -244,6 +244,7 @@ element_grob.element_line <- function(element, x = 0:1, y = 0:1, ) } + #' Define an element's class and what other elements it inherits from #' #' @param class The name of class (like "element_line", "element_text", @@ -362,94 +363,96 @@ R6ElementTree = R6Class('element_tree', invisible(self) }, varElementsFixedDefault = list( - line = el_def("element_line"), - rect = el_def("element_rect"), - text = el_def("element_text"), - title = el_def("element_text", "text"), - axis.line = el_def("element_line", "line"), - axis.text = el_def("element_text", "text"), - axis.title = el_def("element_text", "title"), - axis.ticks = el_def("element_line", "line"), - legend.key.size = el_def("unit"), - panel.grid = el_def("element_line", "line"), - panel.grid.major = el_def("element_line", "panel.grid"), - panel.grid.minor = el_def("element_line", "panel.grid"), - strip.text = el_def("element_text", "text"), - - axis.line.x = el_def("element_line", "axis.line"), - axis.line.x.top = el_def("element_line", "axis.line.x"), - axis.line.x.bottom = el_def("element_line", "axis.line.x"), - axis.line.y = el_def("element_line", "axis.line"), - axis.line.y.left = el_def("element_line", "axis.line.y"), - axis.line.y.right = el_def("element_line", "axis.line.y"), - axis.text.x = el_def("element_text", "axis.text"), - axis.text.x.top = el_def("element_text", "axis.text.x"), - axis.text.x.bottom = el_def("element_text", "axis.text.x"), - axis.text.y = el_def("element_text", "axis.text"), - axis.text.y.left = el_def("element_text", "axis.text.y"), - axis.text.y.right = el_def("element_text", "axis.text.y"), - axis.ticks.length = el_def("unit"), - axis.ticks.x = el_def("element_line", "axis.ticks"), - axis.ticks.x.top = el_def("element_line", "axis.ticks.x"), - axis.ticks.x.bottom = el_def("element_line", "axis.ticks.x"), - axis.ticks.y = el_def("element_line", "axis.ticks"), - axis.ticks.y.left = el_def("element_line", "axis.ticks.y"), - axis.ticks.y.right = el_def("element_line", "axis.ticks.y"), - axis.title.x = el_def("element_text", "axis.title"), - axis.title.x.top = el_def("element_text", "axis.title.x"), - axis.title.x.bottom = el_def("element_text", "axis.title.x"), - axis.title.y = el_def("element_text", "axis.title"), - axis.title.y.left = el_def("element_text", "axis.title.y"), - axis.title.y.right = el_def("element_text", "axis.title.y"), - - legend.background = el_def("element_rect", "rect"), - legend.margin = el_def("margin"), - legend.spacing = el_def("unit"), - legend.spacing.x = el_def("unit", "legend.spacing"), - legend.spacing.y = el_def("unit", "legend.spacing"), - legend.key = el_def("element_rect", "rect"), - legend.key.height = el_def("unit", "legend.key.size"), - legend.key.width = el_def("unit", "legend.key.size"), - legend.text = el_def("element_text", "text"), - legend.text.align = el_def("character"), - legend.title = el_def("element_text", "title"), - legend.title.align = el_def("character"), - legend.position = el_def("character"), # Need to also accept numbers - legend.direction = el_def("character"), - legend.justification = el_def("character"), - legend.box = el_def("character"), - legend.box.just = el_def("character"), - legend.box.margin = el_def("margin"), + line = el_def("element_line"), + rect = el_def("element_rect"), + text = el_def("element_text"), + title = el_def("element_text", "text"), + axis.line = el_def("element_line", "line"), + axis.text = el_def("element_text", "text"), + axis.title = el_def("element_text", "title"), + axis.ticks = el_def("element_line", "line"), + legend.key.size = el_def("unit"), + panel.grid = el_def("element_line", "line"), + panel.grid.major = el_def("element_line", "panel.grid"), + panel.grid.minor = el_def("element_line", "panel.grid"), + strip.text = el_def("element_text", "text"), + + axis.line.x = el_def("element_line", "axis.line"), + axis.line.x.top = el_def("element_line", "axis.line.x"), + axis.line.x.bottom = el_def("element_line", "axis.line.x"), + axis.line.y = el_def("element_line", "axis.line"), + axis.line.y.left = el_def("element_line", "axis.line.y"), + axis.line.y.right = el_def("element_line", "axis.line.y"), + axis.text.x = el_def("element_text", "axis.text"), + axis.text.x.top = el_def("element_text", "axis.text.x"), + axis.text.x.bottom = el_def("element_text", "axis.text.x"), + axis.text.y = el_def("element_text", "axis.text"), + axis.text.y.left = el_def("element_text", "axis.text.y"), + axis.text.y.right = el_def("element_text", "axis.text.y"), + axis.ticks.length = el_def("unit"), + axis.ticks.x = el_def("element_line", "axis.ticks"), + axis.ticks.x.top = el_def("element_line", "axis.ticks.x"), + axis.ticks.x.bottom = el_def("element_line", "axis.ticks.x"), + axis.ticks.y = el_def("element_line", "axis.ticks"), + axis.ticks.y.left = el_def("element_line", "axis.ticks.y"), + axis.ticks.y.right = el_def("element_line", "axis.ticks.y"), + axis.title.x = el_def("element_text", "axis.title"), + axis.title.x.top = el_def("element_text", "axis.title.x"), + axis.title.x.bottom = el_def("element_text", "axis.title.x"), + axis.title.y = el_def("element_text", "axis.title"), + axis.title.y.left = el_def("element_text", "axis.title.y"), + axis.title.y.right = el_def("element_text", "axis.title.y"), + + legend.background = el_def("element_rect", "rect"), + legend.margin = el_def("margin"), + legend.spacing = el_def("unit"), + legend.spacing.x = el_def("unit", "legend.spacing"), + legend.spacing.y = el_def("unit", "legend.spacing"), + legend.key = el_def("element_rect", "rect"), + legend.key.height = el_def("unit", "legend.key.size"), + legend.key.width = el_def("unit", "legend.key.size"), + legend.text = el_def("element_text", "text"), + legend.text.align = el_def("character"), + legend.title = el_def("element_text", "title"), + legend.title.align = el_def("character"), + legend.position = el_def("character"), # Need to also accept numbers + legend.direction = el_def("character"), + legend.justification = el_def("character"), + legend.box = el_def("character"), + legend.box.just = el_def("character"), + legend.box.margin = el_def("margin"), legend.box.background = el_def("element_rect", "rect"), - legend.box.spacing = el_def("unit"), - - panel.background = el_def("element_rect", "rect"), - panel.border = el_def("element_rect", "rect"), - panel.spacing = el_def("unit"), - panel.spacing.x = el_def("unit", "panel.spacing"), - panel.spacing.y = el_def("unit", "panel.spacing"), - panel.grid.major.x = el_def("element_line", "panel.grid.major"), - panel.grid.major.y = el_def("element_line", "panel.grid.major"), - panel.grid.minor.x = el_def("element_line", "panel.grid.minor"), - panel.grid.minor.y = el_def("element_line", "panel.grid.minor"), - panel.ontop = el_def("logical"), - - strip.background = el_def("element_rect", "rect"), - strip.text.x = el_def("element_text", "strip.text"), - strip.text.y = el_def("element_text", "strip.text"), - strip.placement = el_def("character"), - strip.placement.x = el_def("character", "strip.placement"), - strip.placement.y = el_def("character", "strip.placement"), + legend.box.spacing = el_def("unit"), + + panel.background = el_def("element_rect", "rect"), + panel.border = el_def("element_rect", "rect"), + panel.spacing = el_def("unit"), + panel.spacing.x = el_def("unit", "panel.spacing"), + panel.spacing.y = el_def("unit", "panel.spacing"), + panel.grid.major.x = el_def("element_line", "panel.grid.major"), + panel.grid.major.y = el_def("element_line", "panel.grid.major"), + panel.grid.minor.x = el_def("element_line", "panel.grid.minor"), + panel.grid.minor.y = el_def("element_line", "panel.grid.minor"), + panel.ontop = el_def("logical"), + + strip.background = el_def("element_rect", "rect"), + strip.background.x = el_def("element_rect", "strip.background"), + strip.background.y = el_def("element_rect", "strip.background"), + strip.text.x = el_def("element_text", "strip.text"), + strip.text.y = el_def("element_text", "strip.text"), + strip.placement = el_def("character"), + strip.placement.x = el_def("character", "strip.placement"), + strip.placement.y = el_def("character", "strip.placement"), strip.switch.pad.grid = el_def("unit"), strip.switch.pad.wrap = el_def("unit"), - plot.background = el_def("element_rect", "rect"), - plot.title = el_def("element_text", "title"), - plot.subtitle = el_def("element_text", "title"), - plot.caption = el_def("element_text", "title"), - plot.margin = el_def("margin"), + plot.background = el_def("element_rect", "rect"), + plot.title = el_def("element_text", "title"), + plot.subtitle = el_def("element_text", "title"), + plot.caption = el_def("element_text", "title"), + plot.margin = el_def("margin"), - aspect.ratio = el_def("character") + aspect.ratio = el_def("character") ), varElementsFixed = list( ), @@ -508,6 +511,4 @@ get_elements = function(){ .element_tree$get() } #' #' @param el an element #' @param elname the name of the element -validate_element <- function(el, elname) { - .element_tree$validate_element(el,elname) -} +validate_element <- function(el, elname) { .element_tree$validate_element(el,elname) } From 9304f290bef9dd59d4d334a190c2fd046ec16e6a Mon Sep 17 00:00:00 2001 From: nhamilton1980 Date: Sun, 12 Nov 2017 13:03:51 +1100 Subject: [PATCH 11/11] Fixing conflict in theme-elements --- R/theme-elements.r | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/R/theme-elements.r b/R/theme-elements.r index 7c4ea80dab..ea2b8391b2 100644 --- a/R/theme-elements.r +++ b/R/theme-elements.r @@ -245,6 +245,7 @@ element_grob.element_line <- function(element, x = 0:1, y = 0:1, } + #' Define an element's class and what other elements it inherits from #' #' @param class The name of class (like "element_line", "element_text", @@ -271,7 +272,8 @@ el_def <- function(class = NULL, inherit = NULL, description = NULL) { #' the default and user-defined element trees. #' @author Nicholas Hamilton, UNSW Sydney #' @importFrom R6 R6Class -R6ElementTree = R6Class('element_tree', +R6ElementTree = R6Class( + 'element_tree', public = list( initialize = function(){ self$reset(FALSE)