diff --git a/DESCRIPTION b/DESCRIPTION
index c74dcf83..e93e5950 100644
--- a/DESCRIPTION
+++ b/DESCRIPTION
@@ -1,7 +1,7 @@
Package: plot2
Type: Package
Title: Lightweight extension of base R plot
-Version: 0.0.3.9017
+Version: 0.0.3.9018
Authors@R:
c(
person(
diff --git a/NEWS.md b/NEWS.md
index 361cd153..2d1da2e5 100644
--- a/NEWS.md
+++ b/NEWS.md
@@ -1,6 +1,6 @@
# News
-## 0.0.3.917 (development version)
+## 0.0.3.918 (development version)
Website:
@@ -26,7 +26,7 @@ existing plot window. (#60 @grantmcdermott)
- `plot2` gains a new `facet` argument for drawing faceted plots. Users can
override the default square arrangement by passing the desired number of facet
rows or columns to the companion `facet.args` helper function. Facets can be
-combined with `by` grouping, or used on their own. (#83, #91, #94, #96
+combined with `by` grouping, or used on their own. (#83, #91, #94, #96, #101
@grantmcdermott)
- Users can now control `plot2`-specific graphical parameters globally via
the new `par2()` function (which is modeled on the base `par()` function). At
diff --git a/R/par2.R b/R/par2.R
index 376d6802..0b9aff72 100644
--- a/R/par2.R
+++ b/R/par2.R
@@ -13,6 +13,21 @@
#' @section Graphical Parameters:
#'
#' \tabular{lll}{
+#' `facet.cex` \tab\tab Expansion factor for facet titles. Defaults to `1`.\cr
+#' \tab\tab\cr
+#' \tab\tab\cr
+#' `facet.font` \tab\tab An integer corresponding to the desired font face for facet titles. For most font families and graphics devices, one of four possible values: `1` (regular), `2` (bold), `3` (italic), or `4` (bold italic). Defaults to `NULL`, which is equivalent to `1` (i.e., regular).\cr
+#' \tab\tab\cr
+#' \tab\tab\cr
+#' `facet.col` \tab\tab Character or integer specifying the facet text colour. If an integer, will correspond to the user's default global colour palette (see \code{\link[grDevices]{palette}}). Defaults to `NULL`, which is equivalent to "black".\cr
+#' \tab\tab\cr
+#' \tab\tab\cr
+#' `facet.bg` \tab\tab Character or integer specifying the facet background colour. If an integer, will correspond to the user's default colour palette (see \code{\link[grDevices]{palette}}). Passed \code{\link[graphics]{rect}}. Defaults to `NULL` (none).\cr
+#' \tab\tab\cr
+#' \tab\tab\cr
+#' `facet.border` \tab\tab Character or integer specifying the facet border colour. If an integer, will correspond to the users default colour palette (see \code{\link[grDevices]{palette}}). Passed \code{\link[graphics]{rect}}. Defaults to `NA` (none).\cr
+#' \tab\tab\cr
+#' \tab\tab\cr
#' `fmar` \tab\tab A numeric vector of form `c(b,l,t,r)` for controlling the (base) margin padding, in terms of lines, between the individual facets in a faceted plot. Defaults to `c(1,1,1,1)`, i.e. a single line of padding around each facet. If more that three facets are detected, the `fmar` parameter is scaled by 0.75 (i.e., three-quarters) to reduce the excess whitespace that would otherwise arise due to the absent axes lines and labels. (An exception is made for 2x2 plots to better match the `cex` expansion logic of the base graphics system under this particular layout.) Similarly, note that an extra 0.5 lines is subtracted from each side of the facet padding for plots that aren't framed, to reduce excess whitespace.\cr
#' \tab\tab\cr
#' \tab\tab\cr
@@ -29,6 +44,38 @@ par2 = function(...) {
par2_old = as.list(.par2)
nam = names(opts)
+ if (length(opts$facet.cex)) {
+ facet.cex = as.numeric(opts$facet.cex)
+ if(!is.numeric(facet.cex)) stop("facet.cex needs to be numeric")
+ if(length(facet.cex)!=1) stop("facet.cex needs to be of length 1")
+ .par2$facet.cex = facet.cex
+ }
+
+ if (length(opts$facet.font)) {
+ facet.font = as.numeric(opts$facet.font)
+ if(!is.numeric(facet.font)) stop("facet.font needs to be numeric")
+ if(length(facet.font)!=1) stop("facet.font needs to be of length 1")
+ .par2$facet.font = facet.font
+ }
+
+ if (length(opts$facet.col)) {
+ if(!is.numeric(facet.col) || !is.character(facet.col)) stop("facet.col needs to be a numeric or character")
+ if(length(facet.col)!=1) stop("facet.col needs to be of length 1")
+ .par2$facet.col = facet.col
+ }
+
+ if (length(opts$facet.bg)) {
+ if(!is.numeric(facet.bg) || !is.character(facet.bg)) stop("facet.bg needs to be a numeric or character")
+ if(length(facet.bg)!=1) stop("facet.bg needs to be of length 1")
+ .par2$facet.bg = facet.bg
+ }
+
+ if (length(opts$facet.border)) {
+ if(!is.numeric(facet.border) || !is.character(facet.border)) stop("facet.border needs to be a numeric or character")
+ if(length(facet.border)!=1) stop("facet.border needs to be of length 1")
+ .par2$facet.border = facet.border
+ }
+
if (length(opts$fmar)) {
fmar = as.numeric(opts$fmar)
if(!is.numeric(fmar)) stop("fmar needs to be numeric")
diff --git a/R/plot2.R b/R/plot2.R
index 6244b393..9e5ff5e6 100644
--- a/R/plot2.R
+++ b/R/plot2.R
@@ -1,7 +1,7 @@
#' @title Lightweight extension of the base R plotting function
#'
-#' @description Extends base R's
-#' default plotting function, particularly as it applies to scatter and
+#' @description Extends base R's graphics system,
+#' particularly as it applies to scatter and
#' line plots with grouped data. For example, `plot2` makes it easy to plot
#' different categories of a dataset in a single function call and highlight
#' these categories (groups) using modern colour palettes. Coincident with
@@ -9,35 +9,61 @@
#' for further customization. While the package also offers several other
#' enhancements, it tries as far as possible to be a drop-in replacement
#' for the equivalent base plot function. Users should generally be able to
-#' swap a valid `plot` call with `plot2` without any changes to the output.
+#' swap a valid \code{\link[graphics]{plot}} call with `plot2` without any
+#' changes to the output.
#'
#' @md
#' @param x,y the x and y arguments provide the x and y coordinates for the
-#' plot. Any reasonable way of defining the coordinates is acceptable. See
-#' the function xy.coords for details. If supplied separately, they must be
-#' of the same length.
+#' plot. Any reasonable way of defining the coordinates is acceptable; most
+#' likely the names of existing vectors or columns of data frames. See the
+#' 'Examples' section below, or the function
+#' \code{\link[grDevices]{xy.coords}} for details. If supplied separately, `x`
+#' and `y` must be of the same length.
#' @param by grouping variable(s). By default, groups will be represented
#' through colouring of the plot elements. However, this can be turned off
#' and other plot parameters (e.g., line types) can also take on grouping
#' behaviour via the special "by" keyword. See Examples.
-#' @param facet the faceting variable that you want arrange separate plot
-#' windows by. To facet by multiple variables, simply interact them, e.g.
-#' with `interaction(facet_var1, facet_var2)`. Also accepts the special "by"
-#' convenience keyword, in which case facets will match the grouping
-#' variable(s) above.
+#' @param facet the faceting variable(s) that you want arrange separate plot
+#' windows by. Can be specified in various ways:
+#' - In "atomic" form, e.g. `facet = fvar`. To facet by multiple variables in
+#' atomic form, simply interact them, e.g.
+#' `interaction(fvar1, fvar2)` or `factor(fvar1):factor(fvar2)`.
+#' - As a one-sided formula, e.g. `facet = ~fvar`. Multiple variables can be
+#' specified in the formula RHS, e.g. `~fvar1 + fvar2` or `~fvar1:fvar2`. Note
+#' that these multi-variable cases are _all_ treated equivalently and
+#' converted to `interaction(fvar1, fvar2, ...)` internally. (No distinction
+#' is made between different types of binary operators, for example, and so
+#' `f1+f2` is treated the same as `f1:f2`, is treated the same as `f1*f2`,
+#' etc.)
+#' - As a two-side formula, e.g. `facet = fvar1 ~ fvar2`. In this case, the
+#' facet windows are arranged in a fixed grid layout, with the formula LHS
+#' defining the facet rows and the RHS defining the facet columns. At present
+#' only single variables on each side of the formula are well supported. (We
+#' don't recommend trying to use multiple variables on either the LHS or RHS
+#' of the two-sided formula case.)
+#' - As a special `"by"` convenience keyword, in which case facets will match
+#' the grouping variable(s) passed to `by` above.
#' @param facet.args an optional list of arguments for controlling faceting
-#' behaviour. (Ignored if `facet` is NULL.) Currently only the following are
-#' supported:
+#' behaviour. (Ignored if `facet` is NULL.) Supported arguments are as
+#' follows:
#' - `nrow`, `ncol` for overriding the default "square" facet window
-#' arrangement. Only one of these should be specified; if not then the former
-#' will supersede the latter.
+#' arrangement. Only one of these should be specified, but `nrow` will take
+#' precedence if both are specified together. Ignored if a two-sided formula
+#' is passed to the main `facet` argument, since the layout is arranged in a
+#' fixed grid.
#' - `fmar` a vector of form `c(b,l,t,r)` for controlling the base margin
#' between facets in terms of lines. Defaults to the value of `par2("fmar")`,
#' which should be `c(1,1,1,1)`, i.e. a single line of padding around each
#' individual facet, assuming it hasn't been overridden by the user as part
-#' their global `par2` settings. Note some automatic adjustments are made for
-#' certain layouts, and depending on whether the plot is framed or not, to
-#' reduce excess whitespace. See \code{\link[plot2]{par2}} for more details.
+#' their global \code{\link[plot2]{par2}} settings. Note some automatic
+#' adjustments are made for certain layouts, and depending on whether the plot
+#' is framed or not, to reduce excess whitespace. See
+#' \code{\link[plot2]{par2}} for more details.
+#' - `cex`, `font`, `col`, `bg`, `border` for adjusting the facet title text
+#' and background. Default values for these arguments are inherited from
+#' \code{\link[plot2]{par2}} (where they take a "facet." prefix, e.g.
+#' `par2("facet.cex")`). The latter function can also be used to set these
+#' features globally for all `plot2` plots.
#' @param formula a `formula` that optionally includes grouping variable(s)
#' after a vertical bar, e.g. `y ~ x | z`. One-sided formulae are also
#' permitted, e.g. `~ y | z`. Note that the `formula` and `x` arguments
@@ -168,8 +194,8 @@
#' @param add logical. If TRUE, then elements are added to the current plot rather
#' than drawing a new plot window. Note that the automatic legend for the
#' added elements will be turned off.
-#' @param ... other `graphical` parameters (see `par` and also the "Details"
-#' section of `plot`).
+#' @param ... other graphical parameters. See \code{\link[graphics]{par}} or
+#' the "Details" section of \code{\link[graphics]{plot}}.
#'
#' @importFrom grDevices adjustcolor extendrange palette palette.colors palette.pals hcl.colors hcl.pals xy.coords
#' @importFrom graphics abline arrows axis Axis box grconvertX grconvertY lines par plot.default plot.new plot.window points polygon segments title mtext
@@ -193,17 +219,23 @@
#'
#' # Unlike vanilla plot, however, plot2 allows you to characterize groups
#' # (using either the `by` argument or equivalent `|` formula syntax).
-#' # Notice that we also get an automatic legend.
#'
-#' plot2(airquality$Day, airquality$Temp, by = airquality$Month)
-#' plot2(Temp ~ Day | Month, airquality)
+#' aq = transform(
+#' airquality,
+#' Month = factor(Month, labels = month.abb[unique(Month)])
+#' )
+#'
+#' with(aq, plot2(Day, Temp, by = Month)) ## atomic method
+#' plot2(Temp ~ Day | Month, data = aq) ## formula method
+#'
+#' # Notice that we also get an automatic legend.
#'
#' # Use standard base plotting arguments to adjust features of your plot.
#' # For example, change `pch` (plot character) to get filled points.
#'
#' plot2(
#' Temp ~ Day | Month,
-#' data = airquality,
+#' data = aq,
#' pch = 16
#' )
#'
@@ -212,7 +244,7 @@
#'
#' plot2(
#' Temp ~ Day | Month,
-#' data = airquality,
+#' data = aq,
#' type = "l"
#' )
#'
@@ -222,55 +254,56 @@
#'
#' plot2(
#' ~ Temp | Month,
-#' data = airquality,
+#' data = aq,
#' type = "density",
#' fill = "by"
#' )
#'
#' # Facet plots are supported too. Facets can be drawn on their own...
#'
-#' with(
-#' airquality,
-#' plot2(
-#' x = Day, y = Temp,
-#' facet = factor(Month, labels = month.abb[unique(Month)]),
+#' plot2(
+#' Temp ~ Day,
+#' facet = ~ Month,
+#' data = aq,
#' type = "area",
#' main = "Temperatures by month"
-#' )
#' )
#'
#' # ... or combined/contrasted with the by (colour) grouping.
#'
-#' airquality2 = transform(airquality, Summer = Month %in% 6:8)
-#' with(
-#' airquality2,
-#' plot2(
-#' x = Day, y = Temp,
-#' by = Summer,
-#' facet = factor(Month, labels = month.abb[unique(Month)]),
+#' aq = transform(aq, Summer = Month %in% c("Jun", "Jul", "Aug"))
+#' plot2(
+#' Temp ~ Day | Summer,
+#' facet = ~ Month,
+#' data = aq,
#' type = "area",
#' palette = "dark2",
#' main = "Temperatures by month and season"
-#' )
#' )
#'
#' # Users can override the default square window arrangement by passing `nrow`
#' # or `ncol` to the helper facet.args argument. Note that we can also reduce
#' # axis label repetition across facets by turning the plot frame off.
#'
-#' airquality2 = transform(airquality, Summer = Month %in% 6:8)
-#' with(
-#' airquality2,
-#' plot2(
-#' x = Day, y = Temp,
-#' by = Summer,
-#' facet = factor(Month, labels = month.abb[unique(Month)]),
-#' facet.args = list(nrow = 1),
-#' frame = FALSE,
+#' plot2(
+#' Temp ~ Day | Summer,
+#' facet = ~ Month, facet.args = list(nrow = 1),
+#' data = aq,
#' type = "area",
#' palette = "dark2",
+#' frame = FALSE,
#' main = "Temperatures by month and season"
-#' )
+#' )
+#'
+#' # Use a two-sided formula to arrange the facet windows in a fixed grid.
+#' # LHS -> facet rows; RHS -> facet columns
+#'
+#' aq$hot = ifelse(aq$Temp>=75, "hot", "cold")
+#' aq$windy = ifelse(aq$Wind>=15, "windy", "calm")
+#' plot2(
+#' Temp ~ Day,
+#' facet = windy ~ hot,
+#' data = aq
#' )
#'
#' # The (automatic) legend position and look can be customized using
@@ -280,7 +313,7 @@
#'
#' plot2(
#' Temp ~ Day | Month,
-#' data = airquality,
+#' data = aq,
#' type = "l",
#' legend = legend("bottom!", title = "Month of the year", bty = "o")
#' )
@@ -293,7 +326,7 @@
#'
#' plot2(
#' Temp ~ Day | Month,
-#' data = airquality,
+#' data = aq,
#' type = "l",
#' palette = "tableau"
#' )
@@ -304,7 +337,7 @@
#' par(family = "HersheySans", las = 1)
#' plot2(
#' Temp ~ Day | Month,
-#' data = airquality,
+#' data = aq,
#' type = "b", pch = 16,
#' palette = palette.colors(palette = "tableau", alpha = 0.5),
#' main = "Daily temperatures by month",
@@ -385,6 +418,11 @@ plot2.default = function(
if (!is.null(facet) && length(facet)==1 && facet=="by") {
by = as.factor(by) ## if by==facet, then both need to be factors
facet = by
+ } else if (!is.null(facet) && inherits(facet, "formula")) {
+ facet = get_facet_fml(facet, data = data)
+ if (isTRUE(attr(facet, "facet_grid"))) {
+ facet.args[["nrow"]] = attr(facet, "facet_nrow")
+ }
}
## Catch for density type: recycle through plot.density
@@ -456,12 +494,16 @@ plot2.default = function(
xord = order(by, x)
by = by[xord]
} else if (is.null(by)) {
+ facet_grid = attr(facet, "facet_grid")
xord = order(facet, x)
facet = facet[xord]
+ attr(facet, "facet_grid") = facet_grid
} else {
+ facet_grid = attr(facet, "facet_grid")
xord = order(by, facet, x)
by = by[xord]
facet = facet[xord]
+ attr(facet, "facet_grid") = facet_grid
}
x = x[xord]
y = y[xord]
@@ -701,12 +743,30 @@ plot2.default = function(
if (is.null(omar)) omar = par("mar")
+ # Grab some of the customizable facet args that we'll be using later
+ facet_rect = FALSE
+ facet_text = .par2[["facet.cex"]]
+ facet_font = .par2[["facet.font"]]
+ facet_col = .par2[["facet.col"]]
+ facet_bg = .par2[["facet.bg"]]
+ facet_border = .par2[["facet.border"]]
+ if (!is.null(facet.args)) {
+ if (!is.null(facet.args[["cex"]])) facet_text = facet.args[["cex"]]
+ if (!is.null(facet.args[["col"]])) facet_col = facet.args[["col"]]
+ if (!is.null(facet.args[["font"]])) facet_font = facet.args[["font"]]
+ if (!is.null(facet.args[["bg"]])) facet_bg = facet.args[["bg"]]
+ if (!is.null(facet.args[["border"]])) facet_border = facet.args[["border"]]
+ }
+ if (!is.null(facet_bg) || !is.null(facet_border)) facet_rect = TRUE
+
# Need extra adjustment to top margin if facet titles have "\n" newline
# separator. (Note that we'll also need to take account for this in the
# individual facet margins / gaps further below.)
facet_newlines = lengths(gregexpr("\n", grep("\\n", facets, value = TRUE)))
- if (length(facet_newlines)==0) facet_newlines = 0
- omar[3] = omar[3] + max(facet_newlines)
+ # if (length(facet_newlines)==0) facet_newlines = 0
+ # omar[3] = omar[3] + max(facet_newlines)
+ facet_newlines = ifelse(length(facet_newlines)==0, 0, max(facet_newlines))
+ omar[3] = omar[3] + facet_newlines * facet_text / cex_fct_adj
# apply the changes
par(mar = omar)
@@ -749,10 +809,12 @@ plot2.default = function(
# Bump top margin down for facet titles
fmar[3] = fmar[3] + 1
- # Need extra adjustment to top margin if facet titles have "\n" newline separator
- facet_newlines = lengths(gregexpr("\n", grep("\\n", facets, value = TRUE)))
- if (length(facet_newlines)==0) facet_newlines = 0
- fmar[3] = fmar[3] + max(facet_newlines)
+ if (isTRUE(attr(facet, "facet_grid"))) {
+ fmar[3] = max(0, fmar[3] - 1)
+ # Indent for RHS facet_grid title strip if "right!" legend
+ if (has_legend && ooma[4]>0) ooma[4] = ooma[4] + 1
+ }
+ fmar[3] = fmar[3] + facet_newlines * facet_text/cex_fct_adj
omar = par("mar")
@@ -778,6 +840,8 @@ plot2.default = function(
par(mfrow = c(nfacet_rows, nfacet_cols))
}
+ ## Loop over the individual facet windows and draw the plot region
+ ## components (axes, titles, box, grid, etc.)
for (ii in ifacet) {
# See: https://github.com/grantmcdermott/plot2/issues/65
@@ -829,6 +893,90 @@ plot2.default = function(
}
}
+ # facet titles
+ ## Note: facet titles could be done more simply with mtext... but then we
+ ## couldn't adjust background features (e.g., fill), or rotate the rhs
+ ## facet grid text. So we're rolling our own "manual" versions with text
+ ## and rect.
+ if (!is.null(facet)) {
+ # Get the four corners of plot area (x1, x2, y1, y2)
+ corners = par("usr")
+ # special logic for facet grids
+ if (is.null(facet_newlines) || facet_newlines==0) {
+ facet_title_lines = 1
+ } else {
+ facet_title_lines = 1 + facet_newlines
+ }
+ # different logic for facet grids versus regular facets
+ if (isTRUE(attr(facet, "facet_grid"))) {
+ ## top facet strips
+ if (ii %in% 1:nfacet_cols) {
+ if (isTRUE(facet_rect)) {
+ line_height = grconvertY(facet_title_lines + .1, from="lines", to="user") - grconvertY(0, from="lines", to="user")
+ line_height = line_height * facet_text / cex_fct_adj
+ rect(
+ corners[1], corners[4], corners[2], corners[4] + line_height,
+ col = facet_bg, border = facet_border,
+ xpd = NA
+ )
+ }
+ text(
+ x = mean(corners[1:2]),
+ y = corners[4] + grconvertY(0.4, from="lines", to="user") - grconvertY(0, from="lines", to="user"),
+ labels = sub("^(.*?)~.*", "\\1", facets[[ii]]),
+ adj = c(0.5, 0),
+ cex = facet_text/cex_fct_adj,
+ col = facet_col,
+ font = facet_font,
+ xpd = NA,
+ )
+ }
+ ## right facet strips
+ if (ii %% nfacet_cols == 0 || ii == nfacets) {
+ if (isTRUE(facet_rect)) {
+ line_height = grconvertX(facet_title_lines + .1, from="lines", to="user") - grconvertX(0, from="lines", to="user")
+ line_height = line_height * facet_text / cex_fct_adj
+ rect(
+ corners[2], corners[3], corners[2] + line_height, corners[4],
+ col = facet_bg, border = facet_border,
+ xpd = NA
+ )
+ }
+ text(
+ x = corners[2] + grconvertX(0.4, from="lines", to="user") - grconvertX(0, from="lines", to="user"),
+ y = mean(corners[3:4]),
+ labels = sub("^.*?~(.*)", "\\1", facets[[ii]]),
+ srt = 270,
+ adj = c(0.5, 0),
+ cex = facet_text/cex_fct_adj,
+ col = facet_col,
+ font = facet_font,
+ xpd = NA
+ )
+ }
+ } else {
+ if (isTRUE(facet_rect)) {
+ line_height = grconvertY(facet_title_lines + .1, from="lines", to="user") - grconvertY(0, from="lines", to="user")
+ line_height = line_height * facet_text / cex_fct_adj
+ rect(
+ corners[1], corners[4], corners[2], corners[4] + line_height,
+ col = facet_bg, border = facet_border,
+ xpd = NA
+ )
+ }
+ text(
+ x = mean(corners[1:2]),
+ y = corners[4] + grconvertY(0.4, from="lines", to="user") - grconvertY(0, from="lines", to="user"),
+ labels = paste(facets[[ii]]),
+ adj = c(0.5, 0),
+ cex = facet_text/cex_fct_adj,
+ col = facet_col,
+ font = facet_font,
+ xpd = NA
+ )
+ }
+ }
+
# plot frame
if (frame.plot) box()
@@ -856,11 +1004,6 @@ plot2.default = function(
}
}
- # facet titles
- if (!is.null(facet)) {
- mtext(paste(facets[[ii]]), side = 3, line = 0.1)
- }
-
} # end of ii facet loop
} # end of add check
@@ -1281,3 +1424,54 @@ plot2.density = function(
}
+
+
+# utility function for converting facet formulas into variables
+
+get_facet_fml = function(formula, data = NULL) {
+
+ xfacet = yfacet = NULL
+
+ ## catch one-sided formula ~ x or ~ x | z with no "y" variable
+ if (!inherits(formula, "formula")) formula = as.formula(formula)
+ no_yfacet = length(formula) == 2L
+ fml_rhs = if (no_yfacet) 2L else 3L
+
+ ## set up model frame
+ m = match.call(expand.dots = FALSE)
+
+ if (!is.null(data)) {
+ m = m[c(1L, match(c("formula", "data", "subset", "na.action", "drop.unused.levels"), names(m), 0L))]
+ }
+
+ m$formula = formula
+ ## need stats:: for non-standard evaluation
+ m[[1L]] = quote(stats::model.frame)
+ mf = eval.parent(m)
+
+ ## extract variables: x, y (if any)
+ if (no_yfacet) {
+ yfacet_loc = NULL
+ xfacet_loc = 1L
+ } else {
+ yfacet_loc = 1L
+ xfacet_loc = 2L
+ }
+ if (NCOL(mf) < xfacet_loc) stop("formula should specify at least one variable on the right-hand side")
+ yfacet = if (no_yfacet) NULL else mf[, yfacet_loc]
+ xfacet = mf[, xfacet_loc:NCOL(mf)]
+
+ ## return object
+ xfacet = interaction(xfacet, sep = ":")
+ if (no_yfacet) {
+ ret = xfacet
+ } else {
+ # yfacet = interaction(yfacet, sep = ":")
+ ## NOTE: We "swap" the formula LHS and RHS since mfrow plots rowwise
+ ret = interaction(xfacet, yfacet, sep = "~")
+ attr(ret, "facet_grid") = TRUE
+ attr(ret, "facet_nrow") = length(unique(yfacet))
+ }
+
+ return(ret)
+}
diff --git a/R/zzz.R b/R/zzz.R
index fe8d312e..539b850c 100644
--- a/R/zzz.R
+++ b/R/zzz.R
@@ -9,6 +9,13 @@
# Facet margin, i.e. gap between the individual facet windows
.par2$fmar <- if(is.null(getOption("plot2_fmar"))) c(1,1,1,1) else as.numeric(getOption("plot2_fmar"))
+ # Other facet options
+ .par2$facet.cex <- if(is.null(getOption("plot2_facet.cex"))) 1 else as.numeric(getOption("plot2_facet.cex"))
+ .par2$facet.font <- if(is.null(getOption("plot2_facet.font"))) NULL else as.numeric(getOption("plot2_facet.font"))
+ .par2$facet.col <- if(is.null(getOption("plot2_facet.col"))) NULL else getOption("plot2_facet.col")
+ .par2$facet.bg <- if(is.null(getOption("plot2_facet.bg"))) NULL else getOption("plot2_facet.bg")
+ .par2$facet.border <- if(is.null(getOption("plot2_facet.border"))) NA else getOption("plot2_facet.border")
+
# .par2$grid <- if(is.null(getOption("plot2_grid"))) FALSE else as.logical(getOption("plot2_grid"))
.par2$last_facet_par <- if(is.null(getOption("plot2_last_facet_par"))) NULL else getOption("plot2_last_facet_par")
diff --git a/README.md b/README.md
index 511822a1..2df75cef 100644
--- a/README.md
+++ b/README.md
@@ -120,16 +120,13 @@ plot2(
# Facet plots are supported too (combined with "by" grouping, or on their own)
iris2 = transform(iris, Sepals = ifelse(Sepal.Length>6, "Long", "Short"))
-with(
- iris2,
- plot2(
- x = Petal.Length, y = Sepal.Length,
- by = Sepals,
- facet = Species,
- palette = "classic",
- main = "Faceted Sepals!",
- grid = TRUE, frame = FALSE
- )
+plot2(
+ Sepal.Length ~ Petal.Length | Sepals, data = iris2,
+ facet = ~Species,
+ facet.args = list(bg = "grey90"),
+ palette = "classic",
+ main = "Faceted Sepals!",
+ grid = TRUE, frame = FALSE
)
```
diff --git a/README.qmd b/README.qmd
index 439327ba..98255c3e 100644
--- a/README.qmd
+++ b/README.qmd
@@ -114,16 +114,13 @@ plot2(
# Facet plots are supported too (combined with "by" grouping, or on their own)
iris2 = transform(iris, Sepals = ifelse(Sepal.Length>6, "Long", "Short"))
-with(
- iris2,
- plot2(
- x = Petal.Length, y = Sepal.Length,
- by = Sepals,
- facet = Species,
- palette = "classic",
- main = "Faceted Sepals!",
- grid = TRUE, frame = FALSE
- )
+plot2(
+ Sepal.Length ~ Petal.Length | Sepals, data = iris2,
+ facet = ~Species,
+ facet.args = list(bg = "grey90"),
+ palette = "classic",
+ main = "Faceted Sepals!",
+ grid = TRUE, frame = FALSE
)
```
diff --git a/inst/tinytest/_tinysnapshot/facet.svg b/inst/tinytest/_tinysnapshot/facet.svg
index 8a44327a..b6a4a374 100644
--- a/inst/tinytest/_tinysnapshot/facet.svg
+++ b/inst/tinytest/_tinysnapshot/facet.svg
@@ -52,8 +52,9 @@
202530
+
+4
-4
@@ -83,8 +84,9 @@
202530
+
+6
-6
@@ -114,8 +116,9 @@
202530
+
+8
-8
diff --git a/inst/tinytest/_tinysnapshot/facet_1x2.svg b/inst/tinytest/_tinysnapshot/facet_1x2.svg
index 9d8ee497..351fa1a3 100644
--- a/inst/tinytest/_tinysnapshot/facet_1x2.svg
+++ b/inst/tinytest/_tinysnapshot/facet_1x2.svg
@@ -52,8 +52,9 @@
202530
+
+0
-0
@@ -83,8 +84,9 @@
202530
+
+1
-1
diff --git a/inst/tinytest/_tinysnapshot/facet_1x2_formula.svg b/inst/tinytest/_tinysnapshot/facet_1x2_formula.svg
new file mode 100644
index 00000000..351fa1a3
--- /dev/null
+++ b/inst/tinytest/_tinysnapshot/facet_1x2_formula.svg
@@ -0,0 +1,134 @@
+
+
diff --git a/inst/tinytest/_tinysnapshot/facet_2x1.svg b/inst/tinytest/_tinysnapshot/facet_2x1.svg
index c054ae3c..aaf2f5fb 100644
--- a/inst/tinytest/_tinysnapshot/facet_2x1.svg
+++ b/inst/tinytest/_tinysnapshot/facet_2x1.svg
@@ -52,8 +52,9 @@
202530
+
+0
-0
@@ -83,8 +84,9 @@
202530
+
+1
-1
diff --git a/inst/tinytest/_tinysnapshot/facet_2x1_formula.svg b/inst/tinytest/_tinysnapshot/facet_2x1_formula.svg
new file mode 100644
index 00000000..aaf2f5fb
--- /dev/null
+++ b/inst/tinytest/_tinysnapshot/facet_2x1_formula.svg
@@ -0,0 +1,134 @@
+
+
diff --git a/inst/tinytest/_tinysnapshot/facet_2x2.svg b/inst/tinytest/_tinysnapshot/facet_2x2.svg
index 26fedaef..308cf3b9 100644
--- a/inst/tinytest/_tinysnapshot/facet_2x2.svg
+++ b/inst/tinytest/_tinysnapshot/facet_2x2.svg
@@ -52,8 +52,9 @@
202530
+
+0.0
-0.0
@@ -83,8 +84,9 @@
202530
+
+1.0
-1.0
@@ -114,8 +116,9 @@
202530
+
+0.1
-0.1
@@ -145,8 +148,9 @@
202530
+
+1.1
-1.1
diff --git a/inst/tinytest/_tinysnapshot/facet_2x2_formula.svg b/inst/tinytest/_tinysnapshot/facet_2x2_formula.svg
new file mode 100644
index 00000000..f9d93775
--- /dev/null
+++ b/inst/tinytest/_tinysnapshot/facet_2x2_formula.svg
@@ -0,0 +1,202 @@
+
+
diff --git a/inst/tinytest/_tinysnapshot/facet_args_ncol.svg b/inst/tinytest/_tinysnapshot/facet_args_ncol.svg
index e93f66a4..b2a1b028 100644
--- a/inst/tinytest/_tinysnapshot/facet_args_ncol.svg
+++ b/inst/tinytest/_tinysnapshot/facet_args_ncol.svg
@@ -53,8 +53,9 @@
202530
+
+4.0
-4.0
@@ -84,8 +85,9 @@
202530
+
+6.0
-6.0
@@ -115,8 +117,9 @@
202530
+
+8.0
-8.0
@@ -146,8 +149,9 @@
202530
+
+4.1
-4.1
@@ -177,8 +181,9 @@
202530
+
+6.1
-6.1
@@ -208,8 +213,9 @@
202530
+
+8.1
-8.1
diff --git a/inst/tinytest/_tinysnapshot/facet_by.svg b/inst/tinytest/_tinysnapshot/facet_by.svg
index d9a25480..682abb6a 100644
--- a/inst/tinytest/_tinysnapshot/facet_by.svg
+++ b/inst/tinytest/_tinysnapshot/facet_by.svg
@@ -64,8 +64,9 @@
202530
+
+4
-4
@@ -95,8 +96,9 @@
202530
+
+6
-6
@@ -126,8 +128,9 @@
202530
+
+8
-8
diff --git a/inst/tinytest/_tinysnapshot/facet_by_equal.svg b/inst/tinytest/_tinysnapshot/facet_by_equal.svg
index f436418d..2868b527 100644
--- a/inst/tinytest/_tinysnapshot/facet_by_equal.svg
+++ b/inst/tinytest/_tinysnapshot/facet_by_equal.svg
@@ -66,8 +66,9 @@
202530
+
+4
-4
@@ -97,8 +98,9 @@
202530
+
+6
-6
@@ -128,8 +130,9 @@
202530
+
+8
-8
diff --git a/inst/tinytest/_tinysnapshot/facet_density.svg b/inst/tinytest/_tinysnapshot/facet_density.svg
index 78defdce..05919557 100644
--- a/inst/tinytest/_tinysnapshot/facet_density.svg
+++ b/inst/tinytest/_tinysnapshot/facet_density.svg
@@ -56,8 +56,9 @@
0.050.100.15
+
+4
-4
@@ -91,8 +92,9 @@
0.050.100.15
+
+6
-6
@@ -126,8 +128,9 @@
0.050.100.15
+
+8
-8
diff --git a/inst/tinytest/_tinysnapshot/facet_density_by.svg b/inst/tinytest/_tinysnapshot/facet_density_by.svg
index 98ec4043..e1d14c68 100644
--- a/inst/tinytest/_tinysnapshot/facet_density_by.svg
+++ b/inst/tinytest/_tinysnapshot/facet_density_by.svg
@@ -66,8 +66,9 @@
0.10.20.3
+
+4
-4
@@ -99,8 +100,9 @@
0.10.20.3
+
+6
-6
@@ -132,8 +134,9 @@
0.10.20.3
+
+8
-8
diff --git a/inst/tinytest/_tinysnapshot/facet_density_by_equal.svg b/inst/tinytest/_tinysnapshot/facet_density_by_equal.svg
index 2544d4cb..cc9059ea 100644
--- a/inst/tinytest/_tinysnapshot/facet_density_by_equal.svg
+++ b/inst/tinytest/_tinysnapshot/facet_density_by_equal.svg
@@ -70,8 +70,9 @@
0.050.100.15
+
+4
-4
@@ -105,8 +106,9 @@
0.050.100.15
+
+6
-6
@@ -140,8 +142,9 @@
0.050.100.15
+
+8
-8
diff --git a/inst/tinytest/_tinysnapshot/facet_density_fancy.svg b/inst/tinytest/_tinysnapshot/facet_density_fancy.svg
index e0cc73b8..18f2d32a 100644
--- a/inst/tinytest/_tinysnapshot/facet_density_fancy.svg
+++ b/inst/tinytest/_tinysnapshot/facet_density_fancy.svg
@@ -68,6 +68,8 @@
0.10.20.3
+
+4
@@ -85,9 +87,6 @@
-
-4
-
@@ -109,6 +108,8 @@
253035
+
+6
@@ -126,9 +127,6 @@
-
-6
-
@@ -150,6 +148,8 @@
253035
+
+8
@@ -167,9 +167,6 @@
-
-8
-
diff --git a/inst/tinytest/_tinysnapshot/facet_density_fancy_formula.svg b/inst/tinytest/_tinysnapshot/facet_density_fancy_formula.svg
new file mode 100644
index 00000000..5f8efb0c
--- /dev/null
+++ b/inst/tinytest/_tinysnapshot/facet_density_fancy_formula.svg
@@ -0,0 +1,208 @@
+
+
diff --git a/inst/tinytest/_tinysnapshot/facet_fancy.svg b/inst/tinytest/_tinysnapshot/facet_fancy.svg
index 76b6fd21..903a3159 100644
--- a/inst/tinytest/_tinysnapshot/facet_fancy.svg
+++ b/inst/tinytest/_tinysnapshot/facet_fancy.svg
@@ -66,6 +66,8 @@
202530
+
+4
@@ -83,9 +85,6 @@
-
-4
-
@@ -103,6 +102,8 @@
345
+
+6
@@ -120,9 +121,6 @@
-
-6
-
@@ -140,6 +138,8 @@
345
+
+8
@@ -157,9 +157,6 @@
-
-8
-
diff --git a/inst/tinytest/_tinysnapshot/facet_fmar_args.svg b/inst/tinytest/_tinysnapshot/facet_fmar_args.svg
index 2d1231bc..cbaee23e 100644
--- a/inst/tinytest/_tinysnapshot/facet_fmar_args.svg
+++ b/inst/tinytest/_tinysnapshot/facet_fmar_args.svg
@@ -52,8 +52,9 @@
202530
+
+4.0
-4.0
@@ -83,8 +84,9 @@
202530
+
+6.0
-6.0
@@ -114,8 +116,9 @@
202530
+
+8.0
-8.0
@@ -145,8 +148,9 @@
202530
+
+4.1
-4.1
@@ -176,8 +180,9 @@
202530
+
+6.1
-6.1
@@ -207,8 +212,9 @@
202530
+
+8.1
-8.1
diff --git a/inst/tinytest/_tinysnapshot/facet_fmar_par2.svg b/inst/tinytest/_tinysnapshot/facet_fmar_par2.svg
index 2d1231bc..cbaee23e 100644
--- a/inst/tinytest/_tinysnapshot/facet_fmar_par2.svg
+++ b/inst/tinytest/_tinysnapshot/facet_fmar_par2.svg
@@ -52,8 +52,9 @@
202530
+
+4.0
-4.0
@@ -83,8 +84,9 @@
202530
+
+6.0
-6.0
@@ -114,8 +116,9 @@
202530
+
+8.0
-8.0
@@ -145,8 +148,9 @@
202530
+
+4.1
-4.1
@@ -176,8 +180,9 @@
202530
+
+6.1
-6.1
@@ -207,8 +212,9 @@
202530
+
+8.1
-8.1
diff --git a/inst/tinytest/_tinysnapshot/facet_formula.svg b/inst/tinytest/_tinysnapshot/facet_formula.svg
new file mode 100644
index 00000000..b6a4a374
--- /dev/null
+++ b/inst/tinytest/_tinysnapshot/facet_formula.svg
@@ -0,0 +1,168 @@
+
+
diff --git a/inst/tinytest/_tinysnapshot/facet_grid.svg b/inst/tinytest/_tinysnapshot/facet_grid.svg
new file mode 100644
index 00000000..b7234a06
--- /dev/null
+++ b/inst/tinytest/_tinysnapshot/facet_grid.svg
@@ -0,0 +1,270 @@
+
+
diff --git a/inst/tinytest/_tinysnapshot/facet_grid_fancy.svg b/inst/tinytest/_tinysnapshot/facet_grid_fancy.svg
new file mode 100644
index 00000000..e13a1898
--- /dev/null
+++ b/inst/tinytest/_tinysnapshot/facet_grid_fancy.svg
@@ -0,0 +1,343 @@
+
+
diff --git a/inst/tinytest/_tinysnapshot/facet_interaction.svg b/inst/tinytest/_tinysnapshot/facet_interaction.svg
index 46c649ec..b9d74f05 100644
--- a/inst/tinytest/_tinysnapshot/facet_interaction.svg
+++ b/inst/tinytest/_tinysnapshot/facet_interaction.svg
@@ -53,8 +53,9 @@
202530
+
+4.0
-4.0
@@ -84,8 +85,9 @@
202530
+
+6.0
-6.0
@@ -115,8 +117,9 @@
202530
+
+8.0
-8.0
@@ -146,8 +149,9 @@
202530
+
+4.1
-4.1
@@ -177,8 +181,9 @@
202530
+
+6.1
-6.1
@@ -208,8 +213,9 @@
202530
+
+8.1
-8.1
diff --git a/inst/tinytest/_tinysnapshot/facet_interaction_newline.svg b/inst/tinytest/_tinysnapshot/facet_interaction_newline.svg
index 0dea041a..82d4ed84 100644
--- a/inst/tinytest/_tinysnapshot/facet_interaction_newline.svg
+++ b/inst/tinytest/_tinysnapshot/facet_interaction_newline.svg
@@ -26,11 +26,11 @@
mpg
-
-
+
+
-
+
@@ -42,27 +42,28 @@
345
-
-
-
-
-
-
-10
-15
-20
-25
-30
-
-4
-0
+
+
+
+
+
+
+10
+15
+20
+25
+30
+
+4
+0
+
-
-
+
+
-
+
@@ -74,27 +75,28 @@
345
-
-
-
-
-
-
-10
-15
-20
-25
-30
-
-6
-0
+
+
+
+
+
+
+10
+15
+20
+25
+30
+
+6
+0
+
-
-
+
+
-
+
@@ -106,27 +108,28 @@
345
-
-
-
-
-
-
-10
-15
-20
-25
-30
-
-8
-0
+
+
+
+
+
+
+10
+15
+20
+25
+30
+
+8
+0
+
-
-
+
+
-
+
@@ -138,27 +141,28 @@
345
-
-
-
-
-
-
-10
-15
-20
-25
-30
-
-4
-1
+
+
+
+
+
+
+10
+15
+20
+25
+30
+
+4
+1
+
-
-
+
+
-
+
@@ -170,27 +174,28 @@
345
-
-
-
-
-
-
-10
-15
-20
-25
-30
-
-6
-1
+
+
+
+
+
+
+10
+15
+20
+25
+30
+
+6
+1
+
-
-
+
+
-
+
@@ -202,64 +207,65 @@
345
-
-
-
-
-
-
-10
-15
-20
-25
-30
-
-8
-1
+
+
+
+
+
+
+10
+15
+20
+25
+30
+
+8
+1
+
-
-
-
-
+
+
+
+
-
-
-
-
-
+
+
+
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
-
-
-
-
+
+
+
+
-
-
-
+
+
+
diff --git a/inst/tinytest/_tinysnapshot/facet_ribbon.svg b/inst/tinytest/_tinysnapshot/facet_ribbon.svg
index c88aa6b6..6d012722 100644
--- a/inst/tinytest/_tinysnapshot/facet_ribbon.svg
+++ b/inst/tinytest/_tinysnapshot/facet_ribbon.svg
@@ -52,8 +52,9 @@
202530
+
+4
-4
@@ -83,8 +84,9 @@
202530
+
+6
-6
@@ -114,8 +116,9 @@
202530
+
+8
-8
diff --git a/inst/tinytest/_tinysnapshot/facet_ribbon_add.svg b/inst/tinytest/_tinysnapshot/facet_ribbon_add.svg
index 0a3f1aa4..85e9f330 100644
--- a/inst/tinytest/_tinysnapshot/facet_ribbon_add.svg
+++ b/inst/tinytest/_tinysnapshot/facet_ribbon_add.svg
@@ -52,8 +52,9 @@
202530
+
+4
-4
@@ -83,8 +84,9 @@
202530
+
+6
-6
@@ -114,8 +116,9 @@
202530
+
+8
-8
diff --git a/inst/tinytest/_tinysnapshot/facet_ribbon_by.svg b/inst/tinytest/_tinysnapshot/facet_ribbon_by.svg
index 24fb14ed..2e5b5b79 100644
--- a/inst/tinytest/_tinysnapshot/facet_ribbon_by.svg
+++ b/inst/tinytest/_tinysnapshot/facet_ribbon_by.svg
@@ -68,8 +68,9 @@
253035
+
+4
-4
@@ -101,8 +102,9 @@
253035
+
+6
-6
@@ -134,8 +136,9 @@
253035
+
+8
-8
diff --git a/inst/tinytest/_tinysnapshot/facet_ribbon_by_equal.svg b/inst/tinytest/_tinysnapshot/facet_ribbon_by_equal.svg
index 86aadc21..11e499ea 100644
--- a/inst/tinytest/_tinysnapshot/facet_ribbon_by_equal.svg
+++ b/inst/tinytest/_tinysnapshot/facet_ribbon_by_equal.svg
@@ -69,8 +69,9 @@
202530
+
+4
-4
@@ -100,8 +101,9 @@
202530
+
+6
-6
@@ -131,8 +133,9 @@
202530
+
+8
-8
diff --git a/inst/tinytest/_tinysnapshot/facet_ribbon_fancy_add.svg b/inst/tinytest/_tinysnapshot/facet_ribbon_fancy_add.svg
index 8986f407..0a5be5d6 100644
--- a/inst/tinytest/_tinysnapshot/facet_ribbon_fancy_add.svg
+++ b/inst/tinytest/_tinysnapshot/facet_ribbon_fancy_add.svg
@@ -66,6 +66,8 @@
202530
+
+4
@@ -84,9 +86,6 @@
-
-4
-
@@ -115,6 +114,8 @@
202530
+
+6
@@ -133,9 +134,6 @@
-
-6
-
@@ -164,6 +162,8 @@
202530
+
+8
@@ -182,9 +182,6 @@
-
-8
-
diff --git a/inst/tinytest/test-facet.R b/inst/tinytest/test-facet.R
index 697dc42b..8588997a 100644
--- a/inst/tinytest/test-facet.R
+++ b/inst/tinytest/test-facet.R
@@ -118,7 +118,8 @@ if (getRversion() <= "4.3.2") {
mtcars,
plot2(
x = wt, y = mpg,
- by = am, facet = cyl,
+ by = am,
+ facet = cyl, facet.args = list(bg = "grey90"),
pch = 19, palette = "dark2",
grid = TRUE, frame = FALSE,
main = "Car efficiency",
@@ -323,11 +324,11 @@ if (getRversion() <= "4.3.2") {
plot2(
x = mpg,
type = "density",
- by = am, facet = cyl,
+ by = am,
+ facet = cyl, facet.args = list(bg = "grey90"),
fill = "by", palette = "dark2",
grid = TRUE, frame = FALSE,
main = "Car efficiency",
- # xlab = "Weight", ylab = "MPG",
legend = list(title = "Transmission"),
sub = "Notes: Broken out by cylinder and transmission"
)
@@ -337,6 +338,96 @@ if (getRversion() <= "4.3.2") {
}
+#
+## facet (one-sided) formula versions
+
+f = function() {
+ plot2(
+ mpg ~ wt, data = mtcars,
+ facet = ~cyl
+ )
+}
+expect_snapshot_plot(f, label = "facet_formula")
+
+f = function() {
+ plot2(
+ mpg ~ wt, data = mtcars,
+ facet = ~am
+ )
+}
+expect_snapshot_plot(f, label = "facet_1x2_formula")
+
+f = function() {
+ plot2(
+ mpg ~ wt, data = mtcars,
+ facet = ~am,
+ facet.args = list(ncol = 1)
+ )
+}
+expect_snapshot_plot(f, label = "facet_2x1_formula")
+
+f = function() {
+ plot2(
+ mpg ~ wt, data = mtcars,
+ facet = ~am:vs
+ )
+}
+expect_snapshot_plot(f, label = "facet_2x2_formula")
+
+## Skip failing test in R devel due to some minor esoteric difference coming up
+## in R 4.4.0. Can revert once it reaches release for local testing.
+if (getRversion() <= "4.3.2") {
+ f = function() {
+ plot2(
+ ~ mpg | am, mtcars,
+ type = "density",
+ facet = ~cyl,
+ fill = "by", palette = "dark2",
+ grid = TRUE, frame = FALSE,
+ main = "Car efficiency",
+ legend = list(title = "Transmission"),
+ sub = "Notes: Broken out by cylinder and transmission"
+ )
+ }
+ expect_snapshot_plot(f, label = "facet_density_fancy_formula")
+}
+
+
+#
+## facet grid (two-sided formula)
+
+f = function() {
+ plot2(
+ mpg ~ wt, data = mtcars,
+ facet = am ~ cyl,
+ main = "facet grid",
+ sub = "Notes: Transmission (rows) vs Cylinders (cols)"
+ )
+}
+expect_snapshot_plot(f, label = "facet_grid")
+
+
+## Skip failing test in R devel due to some minor esoteric difference coming up
+## in R 4.4.0. Can revert once it reaches release for local testing.
+if (getRversion() <= "4.3.2") {
+ f = function() {
+ plot2(
+ mpg ~ wt | factor(gear), data = mtcars,
+ facet = am ~ cyl,
+ facet.args = list(bg = "grey90"),
+ pch = 19, palette = "classic",
+ legend = list(title = "Gears"),
+ main = "facet grid (fancy)",
+ sub = "Notes: Transmission (rows) vs Cylinders (cols)",
+ grid = TRUE, frame = FALSE,
+ xlim = c(1,6), ylim = c(10,35)
+ )
+ }
+ expect_snapshot_plot(f, label = "facet_grid_fancy")
+}
+
+
+
#
# restore original par settings
#
diff --git a/man/figures/README-quickstart-4.png b/man/figures/README-quickstart-4.png
index a07a97ab..21ef456e 100644
Binary files a/man/figures/README-quickstart-4.png and b/man/figures/README-quickstart-4.png differ
diff --git a/man/par2.Rd b/man/par2.Rd
index b372e486..73eaa86e 100644
--- a/man/par2.Rd
+++ b/man/par2.Rd
@@ -21,6 +21,21 @@ parameters can be set or queried at the same time, as a list.
\tabular{lll}{
+\code{facet.cex} \tab\tab Expansion factor for facet titles. Defaults to \code{1}.\cr
+\tab\tab\cr
+\tab\tab\cr
+\code{facet.font} \tab\tab An integer corresponding to the desired font face for facet titles. For most font families and graphics devices, one of four possible values: \code{1} (regular), \code{2} (bold), \code{3} (italic), or \code{4} (bold italic). Defaults to \code{NULL}, which is equivalent to \code{1} (i.e., regular).\cr
+\tab\tab\cr
+\tab\tab\cr
+\code{facet.col} \tab\tab Character or integer specifying the facet text colour. If an integer, will correspond to the user's default global colour palette (see \code{\link[grDevices]{palette}}). Defaults to \code{NULL}, which is equivalent to "black".\cr
+\tab\tab\cr
+\tab\tab\cr
+\code{facet.bg} \tab\tab Character or integer specifying the facet background colour. If an integer, will correspond to the user's default colour palette (see \code{\link[grDevices]{palette}}). Passed \code{\link[graphics]{rect}}. Defaults to \code{NULL} (none).\cr
+\tab\tab\cr
+\tab\tab\cr
+\code{facet.border} \tab\tab Character or integer specifying the facet border colour. If an integer, will correspond to the users default colour palette (see \code{\link[grDevices]{palette}}). Passed \code{\link[graphics]{rect}}. Defaults to \code{NA} (none).\cr
+\tab\tab\cr
+\tab\tab\cr
\code{fmar} \tab\tab A numeric vector of form \code{c(b,l,t,r)} for controlling the (base) margin padding, in terms of lines, between the individual facets in a faceted plot. Defaults to \code{c(1,1,1,1)}, i.e. a single line of padding around each facet. If more that three facets are detected, the \code{fmar} parameter is scaled by 0.75 (i.e., three-quarters) to reduce the excess whitespace that would otherwise arise due to the absent axes lines and labels. (An exception is made for 2x2 plots to better match the \code{cex} expansion logic of the base graphics system under this particular layout.) Similarly, note that an extra 0.5 lines is subtracted from each side of the facet padding for plots that aren't framed, to reduce excess whitespace.\cr
\tab\tab\cr
\tab\tab\cr
diff --git a/man/plot2.Rd b/man/plot2.Rd
index fad7e622..7db755e1 100644
--- a/man/plot2.Rd
+++ b/man/plot2.Rd
@@ -98,38 +98,65 @@ plot2(x, ...)
}
\arguments{
\item{x, y}{the x and y arguments provide the x and y coordinates for the
-plot. Any reasonable way of defining the coordinates is acceptable. See
-the function xy.coords for details. If supplied separately, they must be
-of the same length.}
+plot. Any reasonable way of defining the coordinates is acceptable; most
+likely the names of existing vectors or columns of data frames. See the
+'Examples' section below, or the function
+\code{\link[grDevices]{xy.coords}} for details. If supplied separately, \code{x}
+and \code{y} must be of the same length.}
-\item{...}{other \code{graphical} parameters (see \code{par} and also the "Details"
-section of \code{plot}).}
+\item{...}{other graphical parameters. See \code{\link[graphics]{par}} or
+the "Details" section of \code{\link[graphics]{plot}}.}
\item{by}{grouping variable(s). By default, groups will be represented
through colouring of the plot elements. However, this can be turned off
and other plot parameters (e.g., line types) can also take on grouping
behaviour via the special "by" keyword. See Examples.}
-\item{facet}{the faceting variable that you want arrange separate plot
-windows by. To facet by multiple variables, simply interact them, e.g.
-with \code{interaction(facet_var1, facet_var2)}. Also accepts the special "by"
-convenience keyword, in which case facets will match the grouping
-variable(s) above.}
+\item{facet}{the faceting variable(s) that you want arrange separate plot
+windows by. Can be specified in various ways:
+\itemize{
+\item In "atomic" form, e.g. \code{facet = fvar}. To facet by multiple variables in
+atomic form, simply interact them, e.g.
+\code{interaction(fvar1, fvar2)} or \code{factor(fvar1):factor(fvar2)}.
+\item As a one-sided formula, e.g. \code{facet = ~fvar}. Multiple variables can be
+specified in the formula RHS, e.g. \code{~fvar1 + fvar2} or \code{~fvar1:fvar2}. Note
+that these multi-variable cases are \emph{all} treated equivalently and
+converted to \code{interaction(fvar1, fvar2, ...)} internally. (No distinction
+is made between different types of binary operators, for example, and so
+\code{f1+f2} is treated the same as \code{f1:f2}, is treated the same as \code{f1*f2},
+etc.)
+\item As a two-side formula, e.g. \code{facet = fvar1 ~ fvar2}. In this case, the
+facet windows are arranged in a fixed grid layout, with the formula LHS
+defining the facet rows and the RHS defining the facet columns. At present
+only single variables on each side of the formula are well supported. (We
+don't recommend trying to use multiple variables on either the LHS or RHS
+of the two-sided formula case.)
+\item As a special \code{"by"} convenience keyword, in which case facets will match
+the grouping variable(s) passed to \code{by} above.
+}}
\item{facet.args}{an optional list of arguments for controlling faceting
-behaviour. (Ignored if \code{facet} is NULL.) Currently only the following are
-supported:
+behaviour. (Ignored if \code{facet} is NULL.) Supported arguments are as
+follows:
\itemize{
\item \code{nrow}, \code{ncol} for overriding the default "square" facet window
-arrangement. Only one of these should be specified; if not then the former
-will supersede the latter.
+arrangement. Only one of these should be specified, but \code{nrow} will take
+precedence if both are specified together. Ignored if a two-sided formula
+is passed to the main \code{facet} argument, since the layout is arranged in a
+fixed grid.
\item \code{fmar} a vector of form \code{c(b,l,t,r)} for controlling the base margin
between facets in terms of lines. Defaults to the value of \code{par2("fmar")},
which should be \code{c(1,1,1,1)}, i.e. a single line of padding around each
individual facet, assuming it hasn't been overridden by the user as part
-their global \code{par2} settings. Note some automatic adjustments are made for
-certain layouts, and depending on whether the plot is framed or not, to
-reduce excess whitespace. See \code{\link[plot2]{par2}} for more details.
+their global \code{\link[plot2]{par2}} settings. Note some automatic
+adjustments are made for certain layouts, and depending on whether the plot
+is framed or not, to reduce excess whitespace. See
+\code{\link[plot2]{par2}} for more details.
+\item \code{cex}, \code{font}, \code{col}, \code{bg}, \code{border} for adjusting the facet title text
+and background. Default values for these arguments are inherited from
+\code{\link[plot2]{par2}} (where they take a "facet." prefix, e.g.
+\code{par2("facet.cex")}). The latter function can also be used to set these
+features globally for all \code{plot2} plots.
}}
\item{data}{a data.frame (or list) from which the variables in formula
@@ -299,8 +326,8 @@ should not be specified in the same call.}
when extracting the data from \code{formula} and \code{data}.}
}
\description{
-Extends base R's
-default plotting function, particularly as it applies to scatter and
+Extends base R's graphics system,
+particularly as it applies to scatter and
line plots with grouped data. For example, \code{plot2} makes it easy to plot
different categories of a dataset in a single function call and highlight
these categories (groups) using modern colour palettes. Coincident with
@@ -308,7 +335,8 @@ this grouping support, \code{plot2} also produces automatic legends with scope
for further customization. While the package also offers several other
enhancements, it tries as far as possible to be a drop-in replacement
for the equivalent base plot function. Users should generally be able to
-swap a valid \code{plot} call with \code{plot2} without any changes to the output.
+swap a valid \code{\link[graphics]{plot}} call with \code{plot2} without any
+changes to the output.
}
\examples{
@@ -328,17 +356,23 @@ par(op)
# Unlike vanilla plot, however, plot2 allows you to characterize groups
# (using either the `by` argument or equivalent `|` formula syntax).
-# Notice that we also get an automatic legend.
-plot2(airquality$Day, airquality$Temp, by = airquality$Month)
-plot2(Temp ~ Day | Month, airquality)
+aq = transform(
+ airquality,
+ Month = factor(Month, labels = month.abb[unique(Month)])
+)
+
+with(aq, plot2(Day, Temp, by = Month)) ## atomic method
+plot2(Temp ~ Day | Month, data = aq) ## formula method
+
+# Notice that we also get an automatic legend.
# Use standard base plotting arguments to adjust features of your plot.
# For example, change `pch` (plot character) to get filled points.
plot2(
Temp ~ Day | Month,
- data = airquality,
+ data = aq,
pch = 16
)
@@ -347,7 +381,7 @@ plot2(
plot2(
Temp ~ Day | Month,
- data = airquality,
+ data = aq,
type = "l"
)
@@ -357,55 +391,56 @@ plot2(
plot2(
~ Temp | Month,
- data = airquality,
+ data = aq,
type = "density",
fill = "by"
)
# Facet plots are supported too. Facets can be drawn on their own...
-with(
- airquality,
- plot2(
- x = Day, y = Temp,
- facet = factor(Month, labels = month.abb[unique(Month)]),
+plot2(
+ Temp ~ Day,
+ facet = ~ Month,
+ data = aq,
type = "area",
main = "Temperatures by month"
- )
)
# ... or combined/contrasted with the by (colour) grouping.
-airquality2 = transform(airquality, Summer = Month \%in\% 6:8)
-with(
- airquality2,
- plot2(
- x = Day, y = Temp,
- by = Summer,
- facet = factor(Month, labels = month.abb[unique(Month)]),
+aq = transform(aq, Summer = Month \%in\% c("Jun", "Jul", "Aug"))
+plot2(
+ Temp ~ Day | Summer,
+ facet = ~ Month,
+ data = aq,
type = "area",
palette = "dark2",
main = "Temperatures by month and season"
- )
)
# Users can override the default square window arrangement by passing `nrow`
# or `ncol` to the helper facet.args argument. Note that we can also reduce
# axis label repetition across facets by turning the plot frame off.
-airquality2 = transform(airquality, Summer = Month \%in\% 6:8)
-with(
- airquality2,
- plot2(
- x = Day, y = Temp,
- by = Summer,
- facet = factor(Month, labels = month.abb[unique(Month)]),
- facet.args = list(nrow = 1),
- frame = FALSE,
+plot2(
+ Temp ~ Day | Summer,
+ facet = ~ Month, facet.args = list(nrow = 1),
+ data = aq,
type = "area",
palette = "dark2",
+ frame = FALSE,
main = "Temperatures by month and season"
- )
+)
+
+# Use a two-sided formula to arrange the facet windows in a fixed grid.
+# LHS -> facet rows; RHS -> facet columns
+
+aq$hot = ifelse(aq$Temp>=75, "hot", "cold")
+aq$windy = ifelse(aq$Wind>=15, "windy", "calm")
+plot2(
+ Temp ~ Day,
+ facet = windy ~ hot,
+ data = aq
)
# The (automatic) legend position and look can be customized using
@@ -415,7 +450,7 @@ with(
plot2(
Temp ~ Day | Month,
- data = airquality,
+ data = aq,
type = "l",
legend = legend("bottom!", title = "Month of the year", bty = "o")
)
@@ -428,7 +463,7 @@ plot2(
plot2(
Temp ~ Day | Month,
- data = airquality,
+ data = aq,
type = "l",
palette = "tableau"
)
@@ -439,7 +474,7 @@ plot2(
par(family = "HersheySans", las = 1)
plot2(
Temp ~ Day | Month,
- data = airquality,
+ data = aq,
type = "b", pch = 16,
palette = palette.colors(palette = "tableau", alpha = 0.5),
main = "Daily temperatures by month",
diff --git a/vignettes/get_started.Rmd b/vignettes/get_started.Rmd
index e7abbc87..0ecd16a8 100644
--- a/vignettes/get_started.Rmd
+++ b/vignettes/get_started.Rmd
@@ -239,7 +239,8 @@ with(
## Facets
Alongside the standard "by" grouping approach that we have seen thus far,
-**plot2** also supports faceted plots.
+**plot2** also supports faceted plots. Mirroring the main `plot2` function, the
+`facet` argument accepts both atomic and formula methods.
```{r facet_simple}
with(
@@ -277,10 +278,11 @@ with(
```
Here's a slightly fancier version where we combine facets with (by) colour
-grouping, and also add the original values to our model predictions. For this
-particular example, we'll use the `facet = "by"` convenience shorthand to facet
-along the same month variable as the colour grouping. But you can easily specify
-different `by` and `facet` variables if that's what your data support.
+grouping, add a background fill to the facet text, and also add back the
+original values to our model predictions. For this particular example, we'll use
+the `facet = "by"` convenience shorthand to facet along the same month variable
+as the colour grouping. But you can easily specify different `by` and `facet`
+variables if that's what your data support.
```{r facet_fancy}
# Plot the original points
@@ -288,7 +290,8 @@ with(
aq,
plot2(
x = Day, y = Temp,
- by = Month, facet = "by",
+ by = Month,
+ facet = "by", facet.args = list(bg = "grey90"),
palette = "dark2",
grid = TRUE, frame = FALSE, ylim = c(50, 100),
main = "Actual and predicted air temperatures"
@@ -308,6 +311,25 @@ with(
)
```
+Again, the `facet` argument also accepts a formula interface. One particular use
+case is for two-sided formulas, which arranges the facet layout in a fixed grid
+arrangement. Here's a simple (if contrived) example.
+
+```{r facet_grid}
+aq$hot = ifelse(aq$Temp>=75, "hot", "cold")
+aq$windy = ifelse(aq$Wind>=15, "windy", "calm")
+
+plot2(
+ Temp ~ Day, data = aq,
+ facet = windy ~ hot,
+ # the rest of these arguments are optional...
+ facet.args = list(col = "white", bg = "black"),
+ pch = 16, col = "dodgerblue",
+ grid = TRUE, frame = FALSE, ylim = c(50, 100),
+ main = "Daily temperatures vs. wind"
+)
+```
+
## Customization
Customizing your plots further is straightforward, whether that is done by